Remove expired certs and key, use them from oom.
[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                 if strings.Contains(err.Error(), "existing mount") {
291                         //It is already mounted
292                         return SecretDomain{}, errors.New("existing domain")
293                 }
294                 return SecretDomain{}, errors.New("Unable to create Secret Domain")
295         }
296
297         uuid, _ := uuid.GenerateUUID()
298         err = v.storeUUID(uuid, name)
299         if smslogger.CheckError(err, "Store UUID") != nil {
300                 // Mount was successful at this point.
301                 // Rollback the mount operation since we could not
302                 // store the UUID for the mount.
303                 v.vaultClient.Sys().Unmount(mountPath)
304                 return SecretDomain{}, errors.New("Unable to store Secret Domain UUID. Retry")
305         }
306
307         return SecretDomain{uuid, name}, nil
308 }
309
310 // CreateSecret creates a secret mounted on a particular domain name
311 // The secret itself is mounted on a path specified by name
312 func (v *Vault) CreateSecret(dom string, sec Secret) error {
313
314         err := v.checkToken()
315         if smslogger.CheckError(err, "Token Check") != nil {
316                 return errors.New("Token check failed")
317         }
318
319         dom = strings.TrimSpace(dom)
320         dom = v.vaultMountPrefix + "/" + dom
321
322         // Vault return is empty on successful write
323         // TODO: Check if values is not empty
324         _, err = v.vaultClient.Logical().Write(dom+"/"+sec.Name, sec.Values)
325         if smslogger.CheckError(err, "Create Secret") != nil {
326                 return errors.New("Unable to create Secret at provided path")
327         }
328
329         return nil
330 }
331
332 // DeleteSecretDomain deletes a secret domain which translates to
333 // an unmount operation on the given path in Vault
334 func (v *Vault) DeleteSecretDomain(dom string) error {
335
336         err := v.checkToken()
337         if smslogger.CheckError(err, "Token Check") != nil {
338                 return errors.New("Token Check Failed")
339         }
340
341         dom = strings.TrimSpace(dom)
342         mountPath := v.vaultMountPrefix + "/" + dom
343
344         err = v.vaultClient.Sys().Unmount(mountPath)
345         if smslogger.CheckError(err, "Delete Domain") != nil {
346                 return errors.New("Unable to delete domain specified")
347         }
348
349         return nil
350 }
351
352 // DeleteSecret deletes a secret mounted on the path provided
353 func (v *Vault) DeleteSecret(dom string, name string) error {
354
355         err := v.checkToken()
356         if smslogger.CheckError(err, "Token Check") != nil {
357                 return errors.New("Token check failed")
358         }
359
360         dom = strings.TrimSpace(dom)
361         dom = v.vaultMountPrefix + "/" + dom
362
363         // Vault return is empty on successful delete
364         _, err = v.vaultClient.Logical().Delete(dom + "/" + name)
365         if smslogger.CheckError(err, "Delete Secret") != nil {
366                 return errors.New("Unable to delete Secret at provided path")
367         }
368
369         return nil
370 }
371
372 // initRole is called only once during SMS bring up
373 // It initially creates a role and secret id associated with
374 // that role. Later restarts will use the existing role-id
375 // and secret-id stored on disk
376 func (v *Vault) initRole() error {
377
378         if v.initRoleDone {
379                 return nil
380         }
381
382         // Use the root token once here
383         v.vaultClient.SetToken(v.vaultToken)
384         defer v.vaultClient.ClearToken()
385
386         // Check if roleID and secretID has already been created
387         rID, error := smsauth.ReadFromFile("auth/role")
388         if error != nil {
389                 smslogger.WriteWarn("Unable to find RoleID. Generating...")
390         } else {
391                 sID, error := smsauth.ReadFromFile("auth/secret")
392                 if error != nil {
393                         smslogger.WriteWarn("Unable to find secretID. Generating...")
394                 } else {
395                         v.roleID = rID
396                         v.secretID = sID
397                         v.initRoleDone = true
398                         return nil
399                 }
400         }
401
402         rules := `path "sms/*" { capabilities = ["create", "read", "update", "delete", "list"] }
403                         path "sys/mounts/sms*" { capabilities = ["update","delete","create"] }`
404         err := v.vaultClient.Sys().PutPolicy(v.policyName, rules)
405         if smslogger.CheckError(err, "Creating Policy") != nil {
406                 return errors.New("Unable to create policy for approle creation")
407         }
408
409         //Check if applrole is mounted
410         authMounts, err := v.vaultClient.Sys().ListAuth()
411         if smslogger.CheckError(err, "Mount Auth Backend") != nil {
412                 return errors.New("Unable to get mounted auth backends")
413         }
414
415         approleMounted := false
416         for k, v := range authMounts {
417                 if v.Type == "approle" && k == "approle/" {
418                         approleMounted = true
419                         break
420                 }
421         }
422
423         // Mount approle in case its not already mounted
424         if !approleMounted {
425                 v.vaultClient.Sys().EnableAuth("approle", "approle", "")
426         }
427
428         rName := v.vaultMountPrefix + "-role"
429         data := map[string]interface{}{
430                 "token_ttl": "60m",
431                 "policies":  [2]string{"default", v.policyName},
432         }
433
434         // Create a role-id
435         v.vaultClient.Logical().Write("auth/approle/role/"+rName, data)
436         sec, err := v.vaultClient.Logical().Read("auth/approle/role/" + rName + "/role-id")
437         if smslogger.CheckError(err, "Create RoleID") != nil {
438                 return errors.New("Unable to create role ID for approle")
439         }
440         v.roleID = sec.Data["role_id"].(string)
441
442         // Create a secret-id to go with it
443         sec, err = v.vaultClient.Logical().Write("auth/approle/role/"+rName+"/secret-id",
444                 map[string]interface{}{})
445         if smslogger.CheckError(err, "Create SecretID") != nil {
446                 return errors.New("Unable to create secret ID for role")
447         }
448
449         v.secretID = sec.Data["secret_id"].(string)
450         v.initRoleDone = true
451         /*
452         * Revoke the Root token.
453         * If a new Root Token is needed, it will need to be created
454         * using the unseal shards.
455          */
456         err = v.vaultClient.Auth().Token().RevokeSelf(v.vaultToken)
457         if smslogger.CheckError(err, "Revoke Root Token") != nil {
458                 smslogger.WriteWarn("Unable to Revoke Token")
459         } else {
460                 // Revoked successfully and clear it
461                 v.vaultToken = ""
462         }
463
464         // Store the role-id and secret-id
465         // We will need this if SMS restarts
466         smsauth.WriteToFile(v.roleID, "auth/role")
467         smsauth.WriteToFile(v.secretID, "auth/secret")
468
469         return nil
470 }
471
472 // Function checkToken() gets called multiple times to create
473 // temporary tokens
474 func (v *Vault) checkToken() error {
475
476         v.Lock()
477         defer v.Unlock()
478
479         // Init Role if it is not yet done
480         // Role needs to be created before token can be created
481         err := v.initRole()
482         if err != nil {
483                 smslogger.WriteError(err.Error())
484                 return errors.New("Unable to initRole in checkToken")
485         }
486
487         // Return immediately if token still has life
488         if v.vaultClient.Token() != "" &&
489                 time.Since(v.vaultTempTokenTTL) < time.Minute*50 {
490                 return nil
491         }
492
493         // Create a temporary token using our roleID and secretID
494         out, err := v.vaultClient.Logical().Write("auth/approle/login",
495                 map[string]interface{}{"role_id": v.roleID, "secret_id": v.secretID})
496         if smslogger.CheckError(err, "Create Temp Token") != nil {
497                 return errors.New("Unable to create Temporary Token for Role")
498         }
499
500         tok, err := out.TokenID()
501
502         v.vaultTempTokenTTL = time.Now()
503         v.vaultClient.SetToken(tok)
504         return nil
505 }
506
507 // vaultInit() is used to initialize the vault in cases where it is not
508 // initialized. This happens once during intial bring up.
509 func (v *Vault) initializeVault() error {
510
511         // Check for vault init status and don't exit till it is initialized
512         for {
513                 init, err := v.vaultClient.Sys().InitStatus()
514                 if smslogger.CheckError(err, "Get Vault Init Status") != nil {
515                         smslogger.WriteInfo("Trying again in 10s...")
516                         time.Sleep(time.Second * 10)
517                         continue
518                 }
519                 // Did not get any error
520                 if init == true {
521                         smslogger.WriteInfo("Vault is already Initialized")
522                         return nil
523                 }
524
525                 // init status is false
526                 // break out of loop and finish initialization
527                 smslogger.WriteInfo("Vault is not initialized. Initializing...")
528                 break
529         }
530
531         // Hardcoded this to 3. We should make this configurable
532         // in the future
533         initReq := &vaultapi.InitRequest{
534                 SecretShares:    3,
535                 SecretThreshold: 3,
536         }
537
538         pbkey, prkey, err := smsauth.GeneratePGPKeyPair()
539
540         if smslogger.CheckError(err, "Generating PGP Keys") != nil {
541                 smslogger.WriteError("Error Generating PGP Keys. Vault Init will not use encryption!")
542         } else {
543                 initReq.PGPKeys = []string{pbkey, pbkey, pbkey}
544                 initReq.RootTokenPGPKey = pbkey
545         }
546
547         resp, err := v.vaultClient.Sys().Init(initReq)
548         if smslogger.CheckError(err, "Initialize Vault") != nil {
549                 return errors.New("FATAL: Unable to initialize Vault")
550         }
551
552         if resp != nil {
553                 v.prkey = prkey
554                 v.shards = resp.KeysB64
555                 v.vaultToken, _ = smsauth.DecryptPGPString(resp.RootToken, prkey)
556                 return nil
557         }
558
559         return errors.New("FATAL: Init response was empty")
560 }