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