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