Revert change to UUID and go back to domain names
[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         shards                []string
47         prkey                 string
48 }
49
50 // initVaultClient will create the initial
51 // Vault strcuture and populate it with the
52 // right values and it will also create
53 // a vault client
54 func (v *Vault) initVaultClient() error {
55
56         vaultCFG := vaultapi.DefaultConfig()
57         vaultCFG.Address = v.vaultAddress
58         client, err := vaultapi.NewClient(vaultCFG)
59         if smslogger.CheckError(err, "Create new vault client") != nil {
60                 return err
61         }
62
63         v.initRoleDone = false
64         v.policyName = "smsvaultpolicy"
65         v.vaultClient = client
66         v.vaultMountPrefix = "sms"
67         v.internalDomain = "smsinternaldomain"
68         v.internalDomainMounted = false
69         v.prkey = ""
70         return nil
71 }
72
73 // Init will initialize the vault connection
74 // It will also initialize vault if it is not
75 // already initialized.
76 // The initial policy will also be created
77 func (v *Vault) Init() error {
78
79         v.initVaultClient()
80         // Initialize vault if it is not already
81         // Returns immediately if it is initialized
82         v.initializeVault()
83
84         err := v.initRole()
85         if smslogger.CheckError(err, "InitRole First Attempt") != nil {
86                 smslogger.WriteInfo("InitRole will try again later")
87         }
88
89         return nil
90 }
91
92 // GetStatus returns the current seal status of vault
93 func (v *Vault) GetStatus() (bool, error) {
94
95         sys := v.vaultClient.Sys()
96         sealStatus, err := sys.SealStatus()
97         if smslogger.CheckError(err, "Getting Status") != nil {
98                 return false, errors.New("Error getting status")
99         }
100
101         return sealStatus.Sealed, nil
102 }
103
104 // RegisterQuorum registers the PGP public key for a quorum client
105 // We will return a shard to the client that is registering
106 func (v *Vault) RegisterQuorum(pgpkey string) (string, error) {
107
108         v.Lock()
109         defer v.Unlock()
110
111         if v.shards == nil {
112                 smslogger.WriteError("Invalid operation in RegisterQuorum")
113                 return "", errors.New("Invalid operation")
114         }
115         // Pop the slice
116         var sh string
117         sh, v.shards = v.shards[len(v.shards)-1], v.shards[:len(v.shards)-1]
118         if len(v.shards) == 0 {
119                 v.shards = nil
120         }
121
122         // Decrypt with SMS pgp Key
123         sh, _ = smsauth.DecryptPGPString(sh, v.prkey)
124         // Encrypt with Quorum client pgp key
125         sh, _ = smsauth.EncryptPGPString(sh, pgpkey)
126
127         return sh, nil
128 }
129
130 // Unseal is a passthrough API that allows any
131 // unseal or initialization processes for the backend
132 func (v *Vault) Unseal(shard string) error {
133
134         sys := v.vaultClient.Sys()
135         _, err := sys.Unseal(shard)
136         if smslogger.CheckError(err, "Unseal Operation") != nil {
137                 return errors.New("Unable to execute unseal operation with specified shard")
138         }
139
140         return nil
141 }
142
143 // GetSecret returns a secret mounted on a particular domain name
144 // The secret itself is referenced via its name which translates to
145 // a mount path in vault
146 func (v *Vault) GetSecret(dom string, name string) (Secret, error) {
147
148         err := v.checkToken()
149         if smslogger.CheckError(err, "Tocken Check") != nil {
150                 return Secret{}, errors.New("Token check failed")
151         }
152
153         dom = strings.TrimSpace(dom)
154         dom = v.vaultMountPrefix + "/" + dom
155
156         sec, err := v.vaultClient.Logical().Read(dom + "/" + name)
157         if smslogger.CheckError(err, "Read Secret") != nil {
158                 return Secret{}, errors.New("Unable to read Secret at provided path")
159         }
160
161         // sec and err are nil in the case where a path does not exist
162         if sec == nil {
163                 smslogger.WriteWarn("Vault read was empty. Invalid Path")
164                 return Secret{}, errors.New("Secret not found at the provided path")
165         }
166
167         return Secret{Name: name, Values: sec.Data}, nil
168 }
169
170 // ListSecret returns a list of secret names on a particular domain
171 // The values of the secret are not returned
172 func (v *Vault) ListSecret(dom string) ([]string, error) {
173
174         err := v.checkToken()
175         if smslogger.CheckError(err, "Token Check") != nil {
176                 return nil, errors.New("Token check failed")
177         }
178
179         dom = strings.TrimSpace(dom)
180         dom = v.vaultMountPrefix + "/" + dom
181
182         sec, err := v.vaultClient.Logical().List(dom)
183         if smslogger.CheckError(err, "Read Secret") != nil {
184                 return nil, errors.New("Unable to read Secret at provided path")
185         }
186
187         // sec and err are nil in the case where a path does not exist
188         if sec == nil {
189                 smslogger.WriteWarn("Vaultclient returned empty data")
190                 return nil, errors.New("Secret not found at the provided path")
191         }
192
193         val, ok := sec.Data["keys"].([]interface{})
194         if !ok {
195                 smslogger.WriteError("Secret not found at the provided path")
196                 return nil, errors.New("Secret not found at the provided path")
197         }
198
199         retval := make([]string, len(val))
200         for i, v := range val {
201                 retval[i] = fmt.Sprint(v)
202         }
203
204         return retval, nil
205 }
206
207 // Mounts the internal Domain if its not already mounted
208 func (v *Vault) mountInternalDomain(name string) error {
209
210         if v.internalDomainMounted {
211                 return nil
212         }
213
214         name = strings.TrimSpace(name)
215         mountPath := v.vaultMountPrefix + "/" + name
216         mountInput := &vaultapi.MountInput{
217                 Type:        "kv",
218                 Description: "Mount point for domain: " + name,
219                 Local:       false,
220                 SealWrap:    false,
221                 Config:      vaultapi.MountConfigInput{},
222         }
223
224         err := v.vaultClient.Sys().Mount(mountPath, mountInput)
225         if smslogger.CheckError(err, "Mount internal Domain") != nil {
226                 if strings.Contains(err.Error(), "existing mount") {
227                         // It is already mounted
228                         v.internalDomainMounted = true
229                         return nil
230                 }
231                 // Ran into some other error mounting it.
232                 return errors.New("Unable to mount internal Domain")
233         }
234
235         v.internalDomainMounted = true
236         return nil
237 }
238
239 // Stores the UUID created for secretdomain in vault
240 // under v.vaultMountPrefix / smsinternal domain
241 func (v *Vault) storeUUID(uuid string, name string) error {
242
243         // Check if token is still valid
244         err := v.checkToken()
245         if smslogger.CheckError(err, "Token Check") != nil {
246                 return errors.New("Token Check failed")
247         }
248
249         err = v.mountInternalDomain(v.internalDomain)
250         if smslogger.CheckError(err, "Mount Internal Domain") != nil {
251                 return err
252         }
253
254         secret := Secret{
255                 Name: name,
256                 Values: map[string]interface{}{
257                         "uuid": uuid,
258                 },
259         }
260
261         err = v.CreateSecret(v.internalDomain, secret)
262         if smslogger.CheckError(err, "Write UUID to domain") != nil {
263                 return err
264         }
265
266         return nil
267 }
268
269 // CreateSecretDomain mounts the kv backend on a path with the given name
270 func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) {
271
272         // Check if token is still valid
273         err := v.checkToken()
274         if smslogger.CheckError(err, "Token Check") != nil {
275                 return SecretDomain{}, errors.New("Token Check failed")
276         }
277
278         name = strings.TrimSpace(name)
279         mountPath := v.vaultMountPrefix + "/" + name
280         mountInput := &vaultapi.MountInput{
281                 Type:        "kv",
282                 Description: "Mount point for domain: " + name,
283                 Local:       false,
284                 SealWrap:    false,
285                 Config:      vaultapi.MountConfigInput{},
286         }
287
288         err = v.vaultClient.Sys().Mount(mountPath, mountInput)
289         if smslogger.CheckError(err, "Create Domain") != nil {
290                 return SecretDomain{}, errors.New("Unable to create Secret Domain")
291         }
292
293         uuid, _ := uuid.GenerateUUID()
294         err = v.storeUUID(uuid, name)
295         if smslogger.CheckError(err, "Store UUID") != nil {
296                 // Mount was successful at this point.
297                 // Rollback the mount operation since we could not
298                 // store the UUID for the mount.
299                 v.vaultClient.Sys().Unmount(mountPath)
300                 return SecretDomain{}, errors.New("Unable to store Secret Domain UUID. Retry")
301         }
302
303         return SecretDomain{uuid, name}, nil
304 }
305
306 // CreateSecret creates a secret mounted on a particular domain name
307 // The secret itself is mounted on a path specified by name
308 func (v *Vault) CreateSecret(dom string, sec Secret) error {
309
310         err := v.checkToken()
311         if smslogger.CheckError(err, "Token Check") != nil {
312                 return errors.New("Token check failed")
313         }
314
315         dom = strings.TrimSpace(dom)
316         dom = v.vaultMountPrefix + "/" + dom
317
318         // Vault return is empty on successful write
319         // TODO: Check if values is not empty
320         _, err = v.vaultClient.Logical().Write(dom+"/"+sec.Name, sec.Values)
321         if smslogger.CheckError(err, "Create Secret") != nil {
322                 return errors.New("Unable to create Secret at provided path")
323         }
324
325         return nil
326 }
327
328 // DeleteSecretDomain deletes a secret domain which translates to
329 // an unmount operation on the given path in Vault
330 func (v *Vault) DeleteSecretDomain(dom string) error {
331
332         err := v.checkToken()
333         if smslogger.CheckError(err, "Token Check") != nil {
334                 return errors.New("Token Check Failed")
335         }
336
337         dom = strings.TrimSpace(dom)
338         mountPath := v.vaultMountPrefix + "/" + dom
339
340         err = v.vaultClient.Sys().Unmount(mountPath)
341         if smslogger.CheckError(err, "Delete Domain") != nil {
342                 return errors.New("Unable to delete domain specified")
343         }
344
345         return nil
346 }
347
348 // DeleteSecret deletes a secret mounted on the path provided
349 func (v *Vault) DeleteSecret(dom string, name string) error {
350
351         err := v.checkToken()
352         if smslogger.CheckError(err, "Token Check") != nil {
353                 return errors.New("Token check failed")
354         }
355
356         dom = strings.TrimSpace(dom)
357         dom = v.vaultMountPrefix + "/" + dom
358
359         // Vault return is empty on successful delete
360         _, err = v.vaultClient.Logical().Delete(dom + "/" + name)
361         if smslogger.CheckError(err, "Delete Secret") != nil {
362                 return errors.New("Unable to delete Secret at provided path")
363         }
364
365         return nil
366 }
367
368 // initRole is called only once during SMS bring up
369 // It initially creates a role and secret id associated with
370 // that role. Later restarts will use the existing role-id
371 // and secret-id stored on disk
372 func (v *Vault) initRole() error {
373
374         if v.initRoleDone {
375                 return nil
376         }
377
378         // Use the root token once here
379         v.vaultClient.SetToken(v.vaultToken)
380         defer v.vaultClient.ClearToken()
381
382         // Check if roleID and secretID has already been created
383         rID, error := smsauth.ReadFromFile("auth/role")
384         if error != nil {
385                 smslogger.WriteWarn("Unable to find RoleID. Generating...")
386         } else {
387                 sID, error := smsauth.ReadFromFile("auth/secret")
388                 if error != nil {
389                         smslogger.WriteWarn("Unable to find secretID. Generating...")
390                 } else {
391                         v.roleID = rID
392                         v.secretID = sID
393                         v.initRoleDone = true
394                         return nil
395                 }
396         }
397
398         rules := `path "sms/*" { capabilities = ["create", "read", "update", "delete", "list"] }
399                         path "sys/mounts/sms*" { capabilities = ["update","delete","create"] }`
400         err := v.vaultClient.Sys().PutPolicy(v.policyName, rules)
401         if smslogger.CheckError(err, "Creating Policy") != nil {
402                 return errors.New("Unable to create policy for approle creation")
403         }
404
405         //Check if applrole is mounted
406         authMounts, err := v.vaultClient.Sys().ListAuth()
407         if smslogger.CheckError(err, "Mount Auth Backend") != nil {
408                 return errors.New("Unable to get mounted auth backends")
409         }
410
411         approleMounted := false
412         for k, v := range authMounts {
413                 if v.Type == "approle" && k == "approle/" {
414                         approleMounted = true
415                         break
416                 }
417         }
418
419         // Mount approle in case its not already mounted
420         if !approleMounted {
421                 v.vaultClient.Sys().EnableAuth("approle", "approle", "")
422         }
423
424         rName := v.vaultMountPrefix + "-role"
425         data := map[string]interface{}{
426                 "token_ttl": "60m",
427                 "policies":  [2]string{"default", v.policyName},
428         }
429
430         // Create a role-id
431         v.vaultClient.Logical().Write("auth/approle/role/"+rName, data)
432         sec, err := v.vaultClient.Logical().Read("auth/approle/role/" + rName + "/role-id")
433         if smslogger.CheckError(err, "Create RoleID") != nil {
434                 return errors.New("Unable to create role ID for approle")
435         }
436         v.roleID = sec.Data["role_id"].(string)
437
438         // Create a secret-id to go with it
439         sec, err = v.vaultClient.Logical().Write("auth/approle/role/"+rName+"/secret-id",
440                 map[string]interface{}{})
441         if smslogger.CheckError(err, "Create SecretID") != nil {
442                 return errors.New("Unable to create secret ID for role")
443         }
444
445         v.secretID = sec.Data["secret_id"].(string)
446         v.initRoleDone = true
447         /*
448         * Revoke the Root token.
449         * If a new Root Token is needed, it will need to be created
450         * using the unseal shards.
451          */
452         err = v.vaultClient.Auth().Token().RevokeSelf(v.vaultToken)
453         if smslogger.CheckError(err, "Revoke Root Token") != nil {
454                 smslogger.WriteWarn("Unable to Revoke Token")
455         } else {
456                 // Revoked successfully and clear it
457                 v.vaultToken = ""
458         }
459
460         // Store the role-id and secret-id
461         // We will need this if SMS restarts
462         smsauth.WriteToFile(v.roleID, "auth/role")
463         smsauth.WriteToFile(v.secretID, "auth/secret")
464
465         return nil
466 }
467
468 // Function checkToken() gets called multiple times to create
469 // temporary tokens
470 func (v *Vault) checkToken() error {
471
472         v.Lock()
473         defer v.Unlock()
474
475         // Init Role if it is not yet done
476         // Role needs to be created before token can be created
477         err := v.initRole()
478         if err != nil {
479                 smslogger.WriteError(err.Error())
480                 return errors.New("Unable to initRole in checkToken")
481         }
482
483         // Return immediately if token still has life
484         if v.vaultClient.Token() != "" &&
485                 time.Since(v.vaultTempTokenTTL) < time.Minute*50 {
486                 return nil
487         }
488
489         // Create a temporary token using our roleID and secretID
490         out, err := v.vaultClient.Logical().Write("auth/approle/login",
491                 map[string]interface{}{"role_id": v.roleID, "secret_id": v.secretID})
492         if smslogger.CheckError(err, "Create Temp Token") != nil {
493                 return errors.New("Unable to create Temporary Token for Role")
494         }
495
496         tok, err := out.TokenID()
497
498         v.vaultTempTokenTTL = time.Now()
499         v.vaultClient.SetToken(tok)
500         return nil
501 }
502
503 // vaultInit() is used to initialize the vault in cases where it is not
504 // initialized. This happens once during intial bring up.
505 func (v *Vault) initializeVault() error {
506
507         // Check for vault init status and don't exit till it is initialized
508         for {
509                 init, err := v.vaultClient.Sys().InitStatus()
510                 if smslogger.CheckError(err, "Get Vault Init Status") != nil {
511                         smslogger.WriteInfo("Trying again in 10s...")
512                         time.Sleep(time.Second * 10)
513                         continue
514                 }
515                 // Did not get any error
516                 if init == true {
517                         smslogger.WriteInfo("Vault is already Initialized")
518                         return nil
519                 }
520
521                 // init status is false
522                 // break out of loop and finish initialization
523                 smslogger.WriteInfo("Vault is not initialized. Initializing...")
524                 break
525         }
526
527         // Hardcoded this to 3. We should make this configurable
528         // in the future
529         initReq := &vaultapi.InitRequest{
530                 SecretShares:    3,
531                 SecretThreshold: 3,
532         }
533
534         pbkey, prkey, err := smsauth.GeneratePGPKeyPair()
535
536         if smslogger.CheckError(err, "Generating PGP Keys") != nil {
537                 smslogger.WriteError("Error Generating PGP Keys. Vault Init will not use encryption!")
538         } else {
539                 initReq.PGPKeys = []string{pbkey, pbkey, pbkey}
540                 initReq.RootTokenPGPKey = pbkey
541         }
542
543         resp, err := v.vaultClient.Sys().Init(initReq)
544         if smslogger.CheckError(err, "Initialize Vault") != nil {
545                 return errors.New("FATAL: Unable to initialize Vault")
546         }
547
548         if resp != nil {
549                 v.prkey = prkey
550                 v.shards = resp.KeysB64
551                 v.vaultToken, _ = smsauth.DecryptPGPString(resp.RootToken, prkey)
552                 return nil
553         }
554
555         return errors.New("FATAL: Init response was empty")
556 }