Get Secret implementation
[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         "log"
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         vaultAddress   string
34         vaultToken     string
35         vaultMount     string
36         vaultTempToken string
37
38         vaultClient       *vaultapi.Client
39         engineType        string
40         policyName        string
41         roleID            string
42         secretID          string
43         vaultTempTokenTTL time.Time
44
45         tokenLock sync.Mutex
46 }
47
48 // Init will initialize the vault connection
49 // It will also create the initial policy if it does not exist
50 // TODO: Check to see if we need to wait for vault to be running
51 func (v *Vault) Init() error {
52         vaultCFG := vaultapi.DefaultConfig()
53         vaultCFG.Address = v.vaultAddress
54         client, err := vaultapi.NewClient(vaultCFG)
55         if err != nil {
56                 return err
57         }
58
59         v.engineType = "kv"
60         v.policyName = "smsvaultpolicy"
61         v.vaultMount = "sms"
62         v.vaultClient = client
63
64         // Check if vault is ready and unsealed
65         seal, err := v.GetStatus()
66         if err != nil {
67                 return err
68         }
69         if seal == true {
70                 return fmt.Errorf("Vault is still sealed. Unseal before use")
71         }
72
73         v.initRole()
74         v.checkToken()
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                 return false, err
84         }
85
86         return sealStatus.Sealed, nil
87 }
88
89 // GetSecretDomain returns any information related to the secretDomain
90 // More information can be added in the future with updates to the struct
91 func (v *Vault) GetSecretDomain(name string) (SecretDomain, error) {
92         return SecretDomain{}, nil
93 }
94
95 // GetSecret returns a secret mounted on a particular domain name
96 // The secret itself is referenced via its name which translates to
97 // a mount path in vault
98 func (v *Vault) GetSecret(dom string, name string) (Secret, error) {
99         err := v.checkToken()
100         if err != nil {
101                 return Secret{}, errors.New("Token check returned error: " + err.Error())
102         }
103
104         dom = v.vaultMount + "/" + dom
105
106         sec, err := v.vaultClient.Logical().Read(dom + "/" + name)
107         if err != nil {
108                 return Secret{}, errors.New("unable to read Secret at provided path")
109         }
110
111         // sec and err are nil in the case where a path does not exist
112         if sec == nil {
113                 return Secret{}, errors.New("Secret not found at the provided path")
114         }
115
116         return Secret{Name: name, Values: sec.Data}, nil
117 }
118
119 // CreateSecretDomain mounts the kv backend on a path with the given name
120 func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) {
121         // Check if token is still valid
122         err := v.checkToken()
123         if err != nil {
124                 return SecretDomain{}, err
125         }
126
127         name = strings.TrimSpace(name)
128         mountPath := v.vaultMount + "/" + name
129         mountInput := &vaultapi.MountInput{
130                 Type:        v.engineType,
131                 Description: "Mount point for domain: " + name,
132                 Local:       false,
133                 SealWrap:    false,
134                 Config:      vaultapi.MountConfigInput{},
135         }
136
137         err = v.vaultClient.Sys().Mount(mountPath, mountInput)
138         if err != nil {
139                 return SecretDomain{}, err
140         }
141
142         uuid, _ := uuid.GenerateUUID()
143         return SecretDomain{uuid, name}, nil
144 }
145
146 // CreateSecret creates a secret mounted on a particular domain name
147 // The secret itself is mounted on a path specified by name
148 func (v *Vault) CreateSecret(dom string, sec Secret) error {
149         err := v.checkToken()
150         if err != nil {
151                 return errors.New("Token checking returned an error" + err.Error())
152         }
153
154         dom = v.vaultMount + "/" + dom
155
156         // Vault write return is empty on successful write
157         _, err = v.vaultClient.Logical().Write(dom+"/"+sec.Name, sec.Values)
158         if err != nil {
159                 return errors.New("Unable to create Secret at provided path")
160         }
161
162         return nil
163 }
164
165 // DeleteSecretDomain deletes a secret domain which translates to
166 // an unmount operation on the given path in Vault
167 func (v *Vault) DeleteSecretDomain(name string) error {
168
169         return nil
170 }
171
172 // DeleteSecret deletes a secret mounted on the path provided
173 func (v *Vault) DeleteSecret(dom string, name string) error {
174
175         return nil
176 }
177
178 // initRole is called only once during the service bring up
179 func (v *Vault) initRole() error {
180         // Use the root token once here
181         v.vaultClient.SetToken(v.vaultToken)
182         defer v.vaultClient.ClearToken()
183
184         rules := `path "sms/*" { capabilities = ["create", "read", "update", "delete", "list"] }
185                         path "sys/mounts/sms*" { capabilities = ["update","delete","create"] }`
186         v.vaultClient.Sys().PutPolicy(v.policyName, rules)
187
188         rName := v.vaultMount + "-role"
189         data := map[string]interface{}{
190                 "token_ttl": "60m",
191                 "policies":  [2]string{"default", v.policyName},
192         }
193
194         // Delete role if it already exists
195         v.vaultClient.Logical().Delete("auth/approle/role/" + rName)
196
197         // Mount approle in case its not already mounted
198         v.vaultClient.Sys().EnableAuth("approle", "approle", "")
199
200         // Create a role-id
201         v.vaultClient.Logical().Write("auth/approle/role/"+rName, data)
202         sec, err := v.vaultClient.Logical().Read("auth/approle/role/" + rName + "/role-id")
203         if err != nil {
204                 log.Fatal(err)
205         }
206         v.roleID = sec.Data["role_id"].(string)
207
208         // Create a secret-id to go with it
209         sec, _ = v.vaultClient.Logical().Write("auth/approle/role/"+rName+"/secret-id",
210                 map[string]interface{}{})
211         v.secretID = sec.Data["secret_id"].(string)
212
213         return nil
214 }
215
216 // Function checkToken() gets called multiple times to create
217 // temporary tokens
218 func (v *Vault) checkToken() error {
219         v.tokenLock.Lock()
220         defer v.tokenLock.Unlock()
221
222         // Return immediately if token still has life
223         if v.vaultClient.Token() != "" &&
224                 time.Since(v.vaultTempTokenTTL) < time.Minute*50 {
225                 return nil
226         }
227
228         // Create a temporary token using our roleID and secretID
229         out, err := v.vaultClient.Logical().Write("auth/approle/login",
230                 map[string]interface{}{"role_id": v.roleID, "secret_id": v.secretID})
231         if err != nil {
232                 return err
233         }
234
235         tok, err := out.TokenID()
236
237         v.vaultTempToken = tok
238         v.vaultTempTokenTTL = time.Now()
239         v.vaultClient.SetToken(v.vaultTempToken)
240         return nil
241 }