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