Adding deletedomain implementation
[aaf/sms.git] / sms-service / src / sms / backend / vault.go
1 /*
2  * Copyright 2018 Intel Corporation, Inc
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package backend
18
19 import (
20         uuid "github.com/hashicorp/go-uuid"
21         vaultapi "github.com/hashicorp/vault/api"
22
23         "errors"
24         "fmt"
25         "log"
26         "strings"
27         "sync"
28         "time"
29 )
30
31 // Vault is the main Struct used in Backend to initialize the struct
32 type Vault struct {
33         vaultAddress   string
34         vaultToken     string
35         vaultMount     string
36         vaultTempToken string
37
38         vaultClient       *vaultapi.Client
39         engineType        string
40         policyName        string
41         roleID            string
42         secretID          string
43         vaultTempTokenTTL time.Time
44
45         tokenLock sync.Mutex
46 }
47
48 // Init will initialize the vault connection
49 // It will also create the initial policy if it does not exist
50 // TODO: Check to see if we need to wait for vault to be running
51 func (v *Vault) Init() error {
52         vaultCFG := vaultapi.DefaultConfig()
53         vaultCFG.Address = v.vaultAddress
54         client, err := vaultapi.NewClient(vaultCFG)
55         if err != nil {
56                 return err
57         }
58
59         v.engineType = "kv"
60         v.policyName = "smsvaultpolicy"
61         v.vaultMount = "sms"
62         v.vaultClient = client
63
64         // Check if vault is ready and unsealed
65         seal, err := v.GetStatus()
66         if err != nil {
67                 return err
68         }
69         if seal == true {
70                 return fmt.Errorf("Vault is still sealed. Unseal before use")
71         }
72
73         err = v.initRole()
74         if err != nil {
75                 log.Fatalln("Unable to initRole in Vault. Exiting...")
76         }
77
78         v.checkToken()
79         return nil
80 }
81
82 // GetStatus returns the current seal status of vault
83 func (v *Vault) GetStatus() (bool, error) {
84         sys := v.vaultClient.Sys()
85         sealStatus, err := sys.SealStatus()
86         if err != nil {
87                 return false, err
88         }
89
90         return sealStatus.Sealed, nil
91 }
92
93 // GetSecretDomain returns any information related to the secretDomain
94 // More information can be added in the future with updates to the struct
95 func (v *Vault) GetSecretDomain(name string) (SecretDomain, error) {
96         return SecretDomain{}, nil
97 }
98
99 // GetSecret returns a secret mounted on a particular domain name
100 // The secret itself is referenced via its name which translates to
101 // a mount path in vault
102 func (v *Vault) GetSecret(dom string, name string) (Secret, error) {
103         err := v.checkToken()
104         if err != nil {
105                 return Secret{}, errors.New("Token check returned error: " + err.Error())
106         }
107
108         dom = v.vaultMount + "/" + dom
109
110         sec, err := v.vaultClient.Logical().Read(dom + "/" + name)
111         if err != nil {
112                 return Secret{}, errors.New("Unable to read Secret at provided path")
113         }
114
115         // sec and err are nil in the case where a path does not exist
116         if sec == nil {
117                 return Secret{}, errors.New("Secret not found at the provided path")
118         }
119
120         return Secret{Name: name, Values: sec.Data}, nil
121 }
122
123 // ListSecret returns a list of secret names on a particular domain
124 // The values of the secret are not returned
125 func (v *Vault) ListSecret(dom string) ([]string, error) {
126         err := v.checkToken()
127         if err != nil {
128                 return nil, errors.New("Token check returned error: " + err.Error())
129         }
130
131         dom = v.vaultMount + "/" + dom
132
133         sec, err := v.vaultClient.Logical().List(dom)
134         if err != nil {
135                 return nil, errors.New("Unable to read Secret at provided path")
136         }
137
138         // sec and err are nil in the case where a path does not exist
139         if sec == nil {
140                 return nil, errors.New("Secret not found at the provided path")
141         }
142
143         val, ok := sec.Data["keys"].([]interface{})
144         if !ok {
145                 return nil, errors.New("Secret not found at the provided path")
146         }
147
148         retval := make([]string, len(val))
149         for i, v := range val {
150                 retval[i] = fmt.Sprint(v)
151         }
152
153         return retval, nil
154 }
155
156 // CreateSecretDomain mounts the kv backend on a path with the given name
157 func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) {
158         // Check if token is still valid
159         err := v.checkToken()
160         if err != nil {
161                 return SecretDomain{}, err
162         }
163
164         name = strings.TrimSpace(name)
165         mountPath := v.vaultMount + "/" + name
166         mountInput := &vaultapi.MountInput{
167                 Type:        v.engineType,
168                 Description: "Mount point for domain: " + name,
169                 Local:       false,
170                 SealWrap:    false,
171                 Config:      vaultapi.MountConfigInput{},
172         }
173
174         err = v.vaultClient.Sys().Mount(mountPath, mountInput)
175         if err != nil {
176                 return SecretDomain{}, err
177         }
178
179         uuid, _ := uuid.GenerateUUID()
180         return SecretDomain{uuid, name}, nil
181 }
182
183 // CreateSecret creates a secret mounted on a particular domain name
184 // The secret itself is mounted on a path specified by name
185 func (v *Vault) CreateSecret(dom string, sec Secret) error {
186         err := v.checkToken()
187         if err != nil {
188                 return errors.New("Token checking returned an error" + err.Error())
189         }
190
191         dom = v.vaultMount + "/" + dom
192
193         // Vault return is empty on successful write
194         _, err = v.vaultClient.Logical().Write(dom+"/"+sec.Name, sec.Values)
195         if err != nil {
196                 return errors.New("Unable to create Secret at provided path")
197         }
198
199         return nil
200 }
201
202 // DeleteSecretDomain deletes a secret domain which translates to
203 // an unmount operation on the given path in Vault
204 func (v *Vault) DeleteSecretDomain(name string) error {
205         err := v.checkToken()
206         if err != nil {
207                 return err
208         }
209
210         name = strings.TrimSpace(name)
211         mountPath := v.vaultMount + "/" + name
212
213         err = v.vaultClient.Sys().Unmount(mountPath)
214         if err != nil {
215                 return errors.New("Unable to delete domain specified")
216         }
217
218         return nil
219 }
220
221 // DeleteSecret deletes a secret mounted on the path provided
222 func (v *Vault) DeleteSecret(dom string, name string) error {
223         err := v.checkToken()
224         if err != nil {
225                 return errors.New("Token checking returned an error" + err.Error())
226         }
227
228         dom = v.vaultMount + "/" + dom
229
230         // Vault return is empty on successful delete
231         _, err = v.vaultClient.Logical().Delete(dom + "/" + name)
232         if err != nil {
233                 return errors.New("Unable to delete Secret at provided path")
234         }
235
236         return nil
237 }
238
239 // initRole is called only once during the service bring up
240 func (v *Vault) initRole() error {
241         // Use the root token once here
242         v.vaultClient.SetToken(v.vaultToken)
243         defer v.vaultClient.ClearToken()
244
245         rules := `path "sms/*" { capabilities = ["create", "read", "update", "delete", "list"] }
246                         path "sys/mounts/sms*" { capabilities = ["update","delete","create"] }`
247         err := v.vaultClient.Sys().PutPolicy(v.policyName, rules)
248         if err != nil {
249                 return errors.New("Unable to create policy for approle creation")
250         }
251
252         rName := v.vaultMount + "-role"
253         data := map[string]interface{}{
254                 "token_ttl": "60m",
255                 "policies":  [2]string{"default", v.policyName},
256         }
257
258         // Delete role if it already exists
259         _, err = v.vaultClient.Logical().Delete("auth/approle/role/" + rName)
260         if err != nil {
261                 return errors.New("Unable to delete existing role")
262         }
263
264         //Check if approle is mounted
265         authMounts, err := v.vaultClient.Sys().ListAuth()
266         if err != nil {
267                 return errors.New("Unable to get mounted auth backends")
268         }
269
270         approleMounted := false
271         for k, v := range authMounts {
272                 if v.Type == "approle" && k == "approle/" {
273                         approleMounted = true
274                         break
275                 }
276         }
277
278         // Mount approle in case its not already mounted
279         if !approleMounted {
280                 v.vaultClient.Sys().EnableAuth("approle", "approle", "")
281         }
282
283         // Create a role-id
284         v.vaultClient.Logical().Write("auth/approle/role/"+rName, data)
285         sec, err := v.vaultClient.Logical().Read("auth/approle/role/" + rName + "/role-id")
286         if err != nil {
287                 return errors.New("Unable to create role ID for approle")
288         }
289         v.roleID = sec.Data["role_id"].(string)
290
291         // Create a secret-id to go with it
292         sec, err = v.vaultClient.Logical().Write("auth/approle/role/"+rName+"/secret-id",
293                 map[string]interface{}{})
294         if err != nil {
295                 return errors.New("Unable to create secret ID for role")
296         }
297
298         v.secretID = sec.Data["secret_id"].(string)
299
300         return nil
301 }
302
303 // Function checkToken() gets called multiple times to create
304 // temporary tokens
305 func (v *Vault) checkToken() error {
306         v.tokenLock.Lock()
307         defer v.tokenLock.Unlock()
308
309         // Return immediately if token still has life
310         if v.vaultClient.Token() != "" &&
311                 time.Since(v.vaultTempTokenTTL) < time.Minute*50 {
312                 return nil
313         }
314
315         // Create a temporary token using our roleID and secretID
316         out, err := v.vaultClient.Logical().Write("auth/approle/login",
317                 map[string]interface{}{"role_id": v.roleID, "secret_id": v.secretID})
318         if err != nil {
319                 return err
320         }
321
322         tok, err := out.TokenID()
323
324         v.vaultTempToken = tok
325         v.vaultTempTokenTTL = time.Now()
326         v.vaultClient.SetToken(v.vaultTempToken)
327         return nil
328 }