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