2 * Copyright 2018 Intel Corporation, Inc
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 uuid "github.com/hashicorp/go-uuid"
21 vaultapi "github.com/hashicorp/vault/api"
32 // Vault is the main Struct used in Backend to initialize the struct
40 vaultClient *vaultapi.Client
41 vaultMountPrefix string
43 internalDomainMounted bool
44 vaultTempTokenTTL time.Time
50 // initVaultClient will create the initial
51 // Vault strcuture and populate it with the
52 // right values and it will also create
54 func (v *Vault) initVaultClient() error {
56 vaultCFG := vaultapi.DefaultConfig()
57 vaultCFG.Address = v.vaultAddress
58 client, err := vaultapi.NewClient(vaultCFG)
59 if smslogger.CheckError(err, "Create new vault client") != nil {
63 v.initRoleDone = false
64 v.policyName = "smsvaultpolicy"
65 v.vaultClient = client
66 v.vaultMountPrefix = "sms"
67 v.internalDomain = "smsinternaldomain"
68 v.internalDomainMounted = false
73 // Init will initialize the vault connection
74 // It will also initialize vault if it is not
75 // already initialized.
76 // The initial policy will also be created
77 func (v *Vault) Init() error {
80 // Initialize vault if it is not already
81 // Returns immediately if it is initialized
85 if smslogger.CheckError(err, "InitRole First Attempt") != nil {
86 smslogger.WriteInfo("InitRole will try again later")
92 // GetStatus returns the current seal status of vault
93 func (v *Vault) GetStatus() (bool, error) {
95 sys := v.vaultClient.Sys()
96 sealStatus, err := sys.SealStatus()
97 if smslogger.CheckError(err, "Getting Status") != nil {
98 return false, errors.New("Error getting status")
101 return sealStatus.Sealed, nil
104 // RegisterQuorum registers the PGP public key for a quorum client
105 // We will return a shard to the client that is registering
106 func (v *Vault) RegisterQuorum(pgpkey string) (string, error) {
112 smslogger.WriteError("Invalid operation in RegisterQuorum")
113 return "", errors.New("Invalid operation")
117 sh, v.shards = v.shards[len(v.shards)-1], v.shards[:len(v.shards)-1]
118 if len(v.shards) == 0 {
122 // Decrypt with SMS pgp Key
123 sh, _ = smsauth.DecryptPGPString(sh, v.prkey)
124 // Encrypt with Quorum client pgp key
125 sh, _ = smsauth.EncryptPGPString(sh, pgpkey)
130 // Unseal is a passthrough API that allows any
131 // unseal or initialization processes for the backend
132 func (v *Vault) Unseal(shard string) error {
134 sys := v.vaultClient.Sys()
135 _, err := sys.Unseal(shard)
136 if smslogger.CheckError(err, "Unseal Operation") != nil {
137 return errors.New("Unable to execute unseal operation with specified shard")
143 // GetSecret returns a secret mounted on a particular domain name
144 // The secret itself is referenced via its name which translates to
145 // a mount path in vault
146 func (v *Vault) GetSecret(dom string, name string) (Secret, error) {
148 err := v.checkToken()
149 if smslogger.CheckError(err, "Tocken Check") != nil {
150 return Secret{}, errors.New("Token check failed")
153 dom = strings.TrimSpace(dom)
154 dom = v.vaultMountPrefix + "/" + dom
156 sec, err := v.vaultClient.Logical().Read(dom + "/" + name)
157 if smslogger.CheckError(err, "Read Secret") != nil {
158 return Secret{}, errors.New("Unable to read Secret at provided path")
161 // sec and err are nil in the case where a path does not exist
163 smslogger.WriteWarn("Vault read was empty. Invalid Path")
164 return Secret{}, errors.New("Secret not found at the provided path")
167 return Secret{Name: name, Values: sec.Data}, nil
170 // ListSecret returns a list of secret names on a particular domain
171 // The values of the secret are not returned
172 func (v *Vault) ListSecret(dom string) ([]string, error) {
174 err := v.checkToken()
175 if smslogger.CheckError(err, "Token Check") != nil {
176 return nil, errors.New("Token check failed")
179 dom = strings.TrimSpace(dom)
180 dom = v.vaultMountPrefix + "/" + dom
182 sec, err := v.vaultClient.Logical().List(dom)
183 if smslogger.CheckError(err, "Read Secret") != nil {
184 return nil, errors.New("Unable to read Secret at provided path")
187 // sec and err are nil in the case where a path does not exist
189 smslogger.WriteWarn("Vaultclient returned empty data")
190 return nil, errors.New("Secret not found at the provided path")
193 val, ok := sec.Data["keys"].([]interface{})
195 smslogger.WriteError("Secret not found at the provided path")
196 return nil, errors.New("Secret not found at the provided path")
199 retval := make([]string, len(val))
200 for i, v := range val {
201 retval[i] = fmt.Sprint(v)
207 // Mounts the internal Domain if its not already mounted
208 func (v *Vault) mountInternalDomain(name string) error {
210 if v.internalDomainMounted {
214 name = strings.TrimSpace(name)
215 mountPath := v.vaultMountPrefix + "/" + name
216 mountInput := &vaultapi.MountInput{
218 Description: "Mount point for domain: " + name,
221 Config: vaultapi.MountConfigInput{},
224 err := v.vaultClient.Sys().Mount(mountPath, mountInput)
225 if smslogger.CheckError(err, "Mount internal Domain") != nil {
226 if strings.Contains(err.Error(), "existing mount") {
227 // It is already mounted
228 v.internalDomainMounted = true
231 // Ran into some other error mounting it.
232 return errors.New("Unable to mount internal Domain")
235 v.internalDomainMounted = true
239 // Stores the UUID created for secretdomain in vault
240 // under v.vaultMountPrefix / smsinternal domain
241 func (v *Vault) storeUUID(uuid string, name string) error {
243 // Check if token is still valid
244 err := v.checkToken()
245 if smslogger.CheckError(err, "Token Check") != nil {
246 return errors.New("Token Check failed")
249 err = v.mountInternalDomain(v.internalDomain)
250 if smslogger.CheckError(err, "Mount Internal Domain") != nil {
256 Values: map[string]interface{}{
261 err = v.CreateSecret(v.internalDomain, secret)
262 if smslogger.CheckError(err, "Write UUID to domain") != nil {
269 // CreateSecretDomain mounts the kv backend on a path with the given name
270 func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) {
272 // Check if token is still valid
273 err := v.checkToken()
274 if smslogger.CheckError(err, "Token Check") != nil {
275 return SecretDomain{}, errors.New("Token Check failed")
278 name = strings.TrimSpace(name)
279 mountPath := v.vaultMountPrefix + "/" + name
280 mountInput := &vaultapi.MountInput{
282 Description: "Mount point for domain: " + name,
285 Config: vaultapi.MountConfigInput{},
288 err = v.vaultClient.Sys().Mount(mountPath, mountInput)
289 if smslogger.CheckError(err, "Create Domain") != nil {
290 return SecretDomain{}, errors.New("Unable to create Secret Domain")
293 uuid, _ := uuid.GenerateUUID()
294 err = v.storeUUID(uuid, name)
295 if smslogger.CheckError(err, "Store UUID") != nil {
296 // Mount was successful at this point.
297 // Rollback the mount operation since we could not
298 // store the UUID for the mount.
299 v.vaultClient.Sys().Unmount(mountPath)
300 return SecretDomain{}, errors.New("Unable to store Secret Domain UUID. Retry")
303 return SecretDomain{uuid, name}, nil
306 // CreateSecret creates a secret mounted on a particular domain name
307 // The secret itself is mounted on a path specified by name
308 func (v *Vault) CreateSecret(dom string, sec Secret) error {
310 err := v.checkToken()
311 if smslogger.CheckError(err, "Token Check") != nil {
312 return errors.New("Token check failed")
315 dom = strings.TrimSpace(dom)
316 dom = v.vaultMountPrefix + "/" + dom
318 // Vault return is empty on successful write
319 // TODO: Check if values is not empty
320 _, err = v.vaultClient.Logical().Write(dom+"/"+sec.Name, sec.Values)
321 if smslogger.CheckError(err, "Create Secret") != nil {
322 return errors.New("Unable to create Secret at provided path")
328 // DeleteSecretDomain deletes a secret domain which translates to
329 // an unmount operation on the given path in Vault
330 func (v *Vault) DeleteSecretDomain(dom string) error {
332 err := v.checkToken()
333 if smslogger.CheckError(err, "Token Check") != nil {
334 return errors.New("Token Check Failed")
337 dom = strings.TrimSpace(dom)
338 mountPath := v.vaultMountPrefix + "/" + dom
340 err = v.vaultClient.Sys().Unmount(mountPath)
341 if smslogger.CheckError(err, "Delete Domain") != nil {
342 return errors.New("Unable to delete domain specified")
348 // DeleteSecret deletes a secret mounted on the path provided
349 func (v *Vault) DeleteSecret(dom string, name string) error {
351 err := v.checkToken()
352 if smslogger.CheckError(err, "Token Check") != nil {
353 return errors.New("Token check failed")
356 dom = strings.TrimSpace(dom)
357 dom = v.vaultMountPrefix + "/" + dom
359 // Vault return is empty on successful delete
360 _, err = v.vaultClient.Logical().Delete(dom + "/" + name)
361 if smslogger.CheckError(err, "Delete Secret") != nil {
362 return errors.New("Unable to delete Secret at provided path")
368 // initRole is called only once during SMS bring up
369 // It initially creates a role and secret id associated with
370 // that role. Later restarts will use the existing role-id
371 // and secret-id stored on disk
372 func (v *Vault) initRole() error {
378 // Use the root token once here
379 v.vaultClient.SetToken(v.vaultToken)
380 defer v.vaultClient.ClearToken()
382 // Check if roleID and secretID has already been created
383 rID, error := smsauth.ReadFromFile("auth/role")
385 smslogger.WriteWarn("Unable to find RoleID. Generating...")
387 sID, error := smsauth.ReadFromFile("auth/secret")
389 smslogger.WriteWarn("Unable to find secretID. Generating...")
393 v.initRoleDone = true
398 rules := `path "sms/*" { capabilities = ["create", "read", "update", "delete", "list"] }
399 path "sys/mounts/sms*" { capabilities = ["update","delete","create"] }`
400 err := v.vaultClient.Sys().PutPolicy(v.policyName, rules)
401 if smslogger.CheckError(err, "Creating Policy") != nil {
402 return errors.New("Unable to create policy for approle creation")
405 //Check if applrole is mounted
406 authMounts, err := v.vaultClient.Sys().ListAuth()
407 if smslogger.CheckError(err, "Mount Auth Backend") != nil {
408 return errors.New("Unable to get mounted auth backends")
411 approleMounted := false
412 for k, v := range authMounts {
413 if v.Type == "approle" && k == "approle/" {
414 approleMounted = true
419 // Mount approle in case its not already mounted
421 v.vaultClient.Sys().EnableAuth("approle", "approle", "")
424 rName := v.vaultMountPrefix + "-role"
425 data := map[string]interface{}{
427 "policies": [2]string{"default", v.policyName},
431 v.vaultClient.Logical().Write("auth/approle/role/"+rName, data)
432 sec, err := v.vaultClient.Logical().Read("auth/approle/role/" + rName + "/role-id")
433 if smslogger.CheckError(err, "Create RoleID") != nil {
434 return errors.New("Unable to create role ID for approle")
436 v.roleID = sec.Data["role_id"].(string)
438 // Create a secret-id to go with it
439 sec, err = v.vaultClient.Logical().Write("auth/approle/role/"+rName+"/secret-id",
440 map[string]interface{}{})
441 if smslogger.CheckError(err, "Create SecretID") != nil {
442 return errors.New("Unable to create secret ID for role")
445 v.secretID = sec.Data["secret_id"].(string)
446 v.initRoleDone = true
448 * Revoke the Root token.
449 * If a new Root Token is needed, it will need to be created
450 * using the unseal shards.
452 err = v.vaultClient.Auth().Token().RevokeSelf(v.vaultToken)
453 if smslogger.CheckError(err, "Revoke Root Token") != nil {
454 smslogger.WriteWarn("Unable to Revoke Token")
456 // Revoked successfully and clear it
460 // Store the role-id and secret-id
461 // We will need this if SMS restarts
462 smsauth.WriteToFile(v.roleID, "auth/role")
463 smsauth.WriteToFile(v.secretID, "auth/secret")
468 // Function checkToken() gets called multiple times to create
470 func (v *Vault) checkToken() error {
475 // Init Role if it is not yet done
476 // Role needs to be created before token can be created
479 smslogger.WriteError(err.Error())
480 return errors.New("Unable to initRole in checkToken")
483 // Return immediately if token still has life
484 if v.vaultClient.Token() != "" &&
485 time.Since(v.vaultTempTokenTTL) < time.Minute*50 {
489 // Create a temporary token using our roleID and secretID
490 out, err := v.vaultClient.Logical().Write("auth/approle/login",
491 map[string]interface{}{"role_id": v.roleID, "secret_id": v.secretID})
492 if smslogger.CheckError(err, "Create Temp Token") != nil {
493 return errors.New("Unable to create Temporary Token for Role")
496 tok, err := out.TokenID()
498 v.vaultTempTokenTTL = time.Now()
499 v.vaultClient.SetToken(tok)
503 // vaultInit() is used to initialize the vault in cases where it is not
504 // initialized. This happens once during intial bring up.
505 func (v *Vault) initializeVault() error {
507 // Check for vault init status and don't exit till it is initialized
509 init, err := v.vaultClient.Sys().InitStatus()
510 if smslogger.CheckError(err, "Get Vault Init Status") != nil {
511 smslogger.WriteInfo("Trying again in 10s...")
512 time.Sleep(time.Second * 10)
515 // Did not get any error
517 smslogger.WriteInfo("Vault is already Initialized")
521 // init status is false
522 // break out of loop and finish initialization
523 smslogger.WriteInfo("Vault is not initialized. Initializing...")
527 // Hardcoded this to 3. We should make this configurable
529 initReq := &vaultapi.InitRequest{
534 pbkey, prkey, err := smsauth.GeneratePGPKeyPair()
536 if smslogger.CheckError(err, "Generating PGP Keys") != nil {
537 smslogger.WriteError("Error Generating PGP Keys. Vault Init will not use encryption!")
539 initReq.PGPKeys = []string{pbkey, pbkey, pbkey}
540 initReq.RootTokenPGPKey = pbkey
543 resp, err := v.vaultClient.Sys().Init(initReq)
544 if smslogger.CheckError(err, "Initialize Vault") != nil {
545 return errors.New("FATAL: Unable to initialize Vault")
550 v.shards = resp.KeysB64
551 v.vaultToken, _ = smsauth.DecryptPGPString(resp.RootToken, prkey)
555 return errors.New("FATAL: Init response was empty")