ed05835acce6fa848e24308b336932cf9ef37400
[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         smsauth "sms/auth"
23         smslogger "sms/log"
24
25         "errors"
26         "fmt"
27         "strings"
28         "sync"
29         "time"
30 )
31
32 // Vault is the main Struct used in Backend to initialize the struct
33 type Vault struct {
34         sync.Mutex
35         initRoleDone          bool
36         policyName            string
37         roleID                string
38         secretID              string
39         vaultAddress          string
40         vaultClient           *vaultapi.Client
41         vaultMountPrefix      string
42         internalDomain        string
43         internalDomainMounted bool
44         vaultTempTokenTTL     time.Time
45         vaultToken            string
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                 smslogger.WriteError(err.Error())
57                 return errors.New("Unable to create new vault client")
58         }
59
60         v.initRoleDone = false
61         v.policyName = "smsvaultpolicy"
62         v.vaultClient = client
63         v.vaultMountPrefix = "sms"
64         v.internalDomain = "smsinternaldomain"
65         v.internalDomainMounted = false
66
67         err = v.initRole()
68         if err != nil {
69                 smslogger.WriteError(err.Error())
70                 smslogger.WriteInfo("InitRole will try again later")
71         }
72
73         return nil
74 }
75
76 // GetStatus returns the current seal status of vault
77 func (v *Vault) GetStatus() (bool, error) {
78         sys := v.vaultClient.Sys()
79         sealStatus, err := sys.SealStatus()
80         if err != nil {
81                 smslogger.WriteError(err.Error())
82                 return false, errors.New("Error getting status")
83         }
84         return sealStatus.Sealed, nil
85 }
86
87 // Unseal is a passthrough API that allows any
88 // unseal or initialization processes for the backend
89 func (v *Vault) Unseal(shard string) error {
90         sys := v.vaultClient.Sys()
91         _, err := sys.Unseal(shard)
92         if err != nil {
93                 smslogger.WriteError(err.Error())
94                 return errors.New("Unable to execute unseal operation with specified shard")
95         }
96
97         return nil
98 }
99
100 // GetSecret returns a secret mounted on a particular domain name
101 // The secret itself is referenced via its name which translates to
102 // a mount path in vault
103 func (v *Vault) GetSecret(dom string, name string) (Secret, error) {
104         err := v.checkToken()
105         if err != nil {
106                 smslogger.WriteError(err.Error())
107                 return Secret{}, errors.New("Token check failed")
108         }
109
110         dom = v.vaultMountPrefix + "/" + dom
111
112         sec, err := v.vaultClient.Logical().Read(dom + "/" + name)
113         if err != nil {
114                 smslogger.WriteError(err.Error())
115                 return Secret{}, errors.New("Unable to read Secret at provided path")
116         }
117
118         // sec and err are nil in the case where a path does not exist
119         if sec == nil {
120                 smslogger.WriteWarn("Vault read was empty. Invalid Path")
121                 return Secret{}, errors.New("Secret not found at the provided path")
122         }
123
124         return Secret{Name: name, Values: sec.Data}, nil
125 }
126
127 // ListSecret returns a list of secret names on a particular domain
128 // The values of the secret are not returned
129 func (v *Vault) ListSecret(dom string) ([]string, error) {
130         err := v.checkToken()
131         if err != nil {
132                 smslogger.WriteError(err.Error())
133                 return nil, errors.New("Token check failed")
134         }
135
136         dom = v.vaultMountPrefix + "/" + dom
137
138         sec, err := v.vaultClient.Logical().List(dom)
139         if err != nil {
140                 smslogger.WriteError(err.Error())
141                 return nil, errors.New("Unable to read Secret at provided path")
142         }
143
144         // sec and err are nil in the case where a path does not exist
145         if sec == nil {
146                 smslogger.WriteWarn("Vaultclient returned empty data")
147                 return nil, errors.New("Secret not found at the provided path")
148         }
149
150         val, ok := sec.Data["keys"].([]interface{})
151         if !ok {
152                 smslogger.WriteError("Secret not found at the provided path")
153                 return nil, errors.New("Secret not found at the provided path")
154         }
155
156         retval := make([]string, len(val))
157         for i, v := range val {
158                 retval[i] = fmt.Sprint(v)
159         }
160
161         return retval, nil
162 }
163
164 // Mounts the internal Domain if its not already mounted
165 func (v *Vault) mountInternalDomain(name string) error {
166         if v.internalDomainMounted {
167                 return nil
168         }
169
170         name = strings.TrimSpace(name)
171         mountPath := v.vaultMountPrefix + "/" + name
172         mountInput := &vaultapi.MountInput{
173                 Type:        "kv",
174                 Description: "Mount point for domain: " + name,
175                 Local:       false,
176                 SealWrap:    false,
177                 Config:      vaultapi.MountConfigInput{},
178         }
179
180         err := v.vaultClient.Sys().Mount(mountPath, mountInput)
181         if err != nil {
182                 if strings.Contains(err.Error(), "existing mount") {
183                         // It is already mounted
184                         v.internalDomainMounted = true
185                         return nil
186                 }
187                 // Ran into some other error mounting it.
188                 smslogger.WriteError(err.Error())
189                 return errors.New("Unable to mount internal Domain")
190         }
191
192         v.internalDomainMounted = true
193         return nil
194 }
195
196 // Stores the UUID created for secretdomain in vault
197 // under v.vaultMountPrefix / smsinternal domain
198 func (v *Vault) storeUUID(uuid string, name string) error {
199         // Check if token is still valid
200         err := v.checkToken()
201         if err != nil {
202                 smslogger.WriteError(err.Error())
203                 return errors.New("Token Check failed")
204         }
205
206         err = v.mountInternalDomain(v.internalDomain)
207         if err != nil {
208                 smslogger.WriteError("Could not mount internal domain")
209                 return err
210         }
211
212         secret := Secret{
213                 Name: name,
214                 Values: map[string]interface{}{
215                         "uuid": uuid,
216                 },
217         }
218
219         err = v.CreateSecret(v.internalDomain, secret)
220         if err != nil {
221                 smslogger.WriteError("Unable to write UUID to internal domain")
222                 return err
223         }
224
225         return nil
226 }
227
228 // CreateSecretDomain mounts the kv backend on a path with the given name
229 func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) {
230         // Check if token is still valid
231         err := v.checkToken()
232         if err != nil {
233                 smslogger.WriteError(err.Error())
234                 return SecretDomain{}, errors.New("Token Check failed")
235         }
236
237         name = strings.TrimSpace(name)
238         mountPath := v.vaultMountPrefix + "/" + name
239         mountInput := &vaultapi.MountInput{
240                 Type:        "kv",
241                 Description: "Mount point for domain: " + name,
242                 Local:       false,
243                 SealWrap:    false,
244                 Config:      vaultapi.MountConfigInput{},
245         }
246
247         err = v.vaultClient.Sys().Mount(mountPath, mountInput)
248         if err != nil {
249                 smslogger.WriteError(err.Error())
250                 return SecretDomain{}, errors.New("Unable to create Secret Domain")
251         }
252
253         uuid, _ := uuid.GenerateUUID()
254         err = v.storeUUID(uuid, name)
255         if err != nil {
256                 // Mount was successful at this point.
257                 // Rollback the mount operation since we could not
258                 // store the UUID for the mount.
259                 v.vaultClient.Sys().Unmount(mountPath)
260                 return SecretDomain{}, errors.New("Unable to store Secret Domain UUID. Retry.")
261         }
262
263         return SecretDomain{uuid, name}, nil
264 }
265
266 // CreateSecret creates a secret mounted on a particular domain name
267 // The secret itself is mounted on a path specified by name
268 func (v *Vault) CreateSecret(dom string, sec Secret) error {
269         err := v.checkToken()
270         if err != nil {
271                 smslogger.WriteError(err.Error())
272                 return errors.New("Token check failed")
273         }
274
275         dom = v.vaultMountPrefix + "/" + dom
276
277         // Vault return is empty on successful write
278         // TODO: Check if values is not empty
279         _, err = v.vaultClient.Logical().Write(dom+"/"+sec.Name, sec.Values)
280         if err != nil {
281                 smslogger.WriteError(err.Error())
282                 return errors.New("Unable to create Secret at provided path")
283         }
284
285         return nil
286 }
287
288 // DeleteSecretDomain deletes a secret domain which translates to
289 // an unmount operation on the given path in Vault
290 func (v *Vault) DeleteSecretDomain(name string) error {
291         err := v.checkToken()
292         if err != nil {
293                 smslogger.WriteError(err.Error())
294                 return errors.New("Token Check Failed")
295         }
296
297         name = strings.TrimSpace(name)
298         mountPath := v.vaultMountPrefix + "/" + name
299
300         err = v.vaultClient.Sys().Unmount(mountPath)
301         if err != nil {
302                 smslogger.WriteError(err.Error())
303                 return errors.New("Unable to delete domain specified")
304         }
305
306         return nil
307 }
308
309 // DeleteSecret deletes a secret mounted on the path provided
310 func (v *Vault) DeleteSecret(dom string, name string) error {
311         err := v.checkToken()
312         if err != nil {
313                 smslogger.WriteError(err.Error())
314                 return errors.New("Token check failed")
315         }
316
317         dom = v.vaultMountPrefix + "/" + dom
318
319         // Vault return is empty on successful delete
320         _, err = v.vaultClient.Logical().Delete(dom + "/" + name)
321         if err != nil {
322                 smslogger.WriteError(err.Error())
323                 return errors.New("Unable to delete Secret at provided path")
324         }
325
326         return nil
327 }
328
329 // initRole is called only once during the service bring up
330 func (v *Vault) initRole() error {
331         // Use the root token once here
332         v.vaultClient.SetToken(v.vaultToken)
333         defer v.vaultClient.ClearToken()
334
335         rules := `path "sms/*" { capabilities = ["create", "read", "update", "delete", "list"] }
336                         path "sys/mounts/sms*" { capabilities = ["update","delete","create"] }`
337         err := v.vaultClient.Sys().PutPolicy(v.policyName, rules)
338         if err != nil {
339                 smslogger.WriteError(err.Error())
340                 return errors.New("Unable to create policy for approle creation")
341         }
342
343         rName := v.vaultMountPrefix + "-role"
344         data := map[string]interface{}{
345                 "token_ttl": "60m",
346                 "policies":  [2]string{"default", v.policyName},
347         }
348
349         //Check if applrole is mounted
350         authMounts, err := v.vaultClient.Sys().ListAuth()
351         if err != nil {
352                 smslogger.WriteError(err.Error())
353                 return errors.New("Unable to get mounted auth backends")
354         }
355
356         approleMounted := false
357         for k, v := range authMounts {
358                 if v.Type == "approle" && k == "approle/" {
359                         approleMounted = true
360                         break
361                 }
362         }
363
364         // Mount approle in case its not already mounted
365         if !approleMounted {
366                 v.vaultClient.Sys().EnableAuth("approle", "approle", "")
367         }
368
369         // Create a role-id
370         v.vaultClient.Logical().Write("auth/approle/role/"+rName, data)
371         sec, err := v.vaultClient.Logical().Read("auth/approle/role/" + rName + "/role-id")
372         if err != nil {
373                 smslogger.WriteError(err.Error())
374                 return errors.New("Unable to create role ID for approle")
375         }
376         v.roleID = sec.Data["role_id"].(string)
377
378         // Create a secret-id to go with it
379         sec, err = v.vaultClient.Logical().Write("auth/approle/role/"+rName+"/secret-id",
380                 map[string]interface{}{})
381         if err != nil {
382                 smslogger.WriteError(err.Error())
383                 return errors.New("Unable to create secret ID for role")
384         }
385
386         v.secretID = sec.Data["secret_id"].(string)
387         v.initRoleDone = true
388         return nil
389 }
390
391 // Function checkToken() gets called multiple times to create
392 // temporary tokens
393 func (v *Vault) checkToken() error {
394         v.Lock()
395         defer v.Unlock()
396
397         // Init Role if it is not yet done
398         // Role needs to be created before token can be created
399         if v.initRoleDone == false {
400                 err := v.initRole()
401                 if err != nil {
402                         smslogger.WriteError(err.Error())
403                         return errors.New("Unable to initRole in checkToken")
404                 }
405         }
406
407         // Return immediately if token still has life
408         if v.vaultClient.Token() != "" &&
409                 time.Since(v.vaultTempTokenTTL) < time.Minute*50 {
410                 return nil
411         }
412
413         // Create a temporary token using our roleID and secretID
414         out, err := v.vaultClient.Logical().Write("auth/approle/login",
415                 map[string]interface{}{"role_id": v.roleID, "secret_id": v.secretID})
416         if err != nil {
417                 smslogger.WriteError(err.Error())
418                 return errors.New("Unable to create Temporary Token for Role")
419         }
420
421         tok, err := out.TokenID()
422
423         v.vaultTempTokenTTL = time.Now()
424         v.vaultClient.SetToken(tok)
425         return nil
426 }
427
428 // vaultInit() is used to initialize the vault in cases where it is not
429 // initialized. This happens once during intial bring up.
430 func (v *Vault) initializeVault() error {
431         initReq := &vaultapi.InitRequest{
432                 SecretShares:    5,
433                 SecretThreshold: 3,
434         }
435
436         pbkey, _, err := smsauth.GeneratePGPKeyPair()
437         if err != nil {
438                 smslogger.WriteError("Error Generating PGP Keys. Vault Init will not use encryption!")
439         } else {
440                 initReq.PGPKeys = []string{pbkey, pbkey, pbkey, pbkey, pbkey}
441                 initReq.RootTokenPGPKey = pbkey
442         }
443
444         resp, err := v.vaultClient.Sys().Init(initReq)
445         if err != nil {
446                 smslogger.WriteError(err.Error())
447                 return errors.New("FATAL: Unable to initialize Vault")
448         }
449
450         if resp != nil {
451                 //v.writeUnsealShards(resp.KeysB64)
452                 v.vaultToken = resp.RootToken
453                 return nil
454         }
455
456         return errors.New("FATAL: Init response was empty")
457 }