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 if strings.Contains(err.Error(), "existing mount") {
291 //It is already mounted
292 return SecretDomain{}, errors.New("existing domain")
294 return SecretDomain{}, errors.New("Unable to create Secret Domain")
297 uuid, _ := uuid.GenerateUUID()
298 err = v.storeUUID(uuid, name)
299 if smslogger.CheckError(err, "Store UUID") != nil {
300 // Mount was successful at this point.
301 // Rollback the mount operation since we could not
302 // store the UUID for the mount.
303 v.vaultClient.Sys().Unmount(mountPath)
304 return SecretDomain{}, errors.New("Unable to store Secret Domain UUID. Retry")
307 return SecretDomain{uuid, name}, nil
310 // CreateSecret creates a secret mounted on a particular domain name
311 // The secret itself is mounted on a path specified by name
312 func (v *Vault) CreateSecret(dom string, sec Secret) error {
314 err := v.checkToken()
315 if smslogger.CheckError(err, "Token Check") != nil {
316 return errors.New("Token check failed")
319 dom = strings.TrimSpace(dom)
320 dom = v.vaultMountPrefix + "/" + dom
322 // Vault return is empty on successful write
323 // TODO: Check if values is not empty
324 _, err = v.vaultClient.Logical().Write(dom+"/"+sec.Name, sec.Values)
325 if smslogger.CheckError(err, "Create Secret") != nil {
326 return errors.New("Unable to create Secret at provided path")
332 // DeleteSecretDomain deletes a secret domain which translates to
333 // an unmount operation on the given path in Vault
334 func (v *Vault) DeleteSecretDomain(dom string) error {
336 err := v.checkToken()
337 if smslogger.CheckError(err, "Token Check") != nil {
338 return errors.New("Token Check Failed")
341 dom = strings.TrimSpace(dom)
342 mountPath := v.vaultMountPrefix + "/" + dom
344 err = v.vaultClient.Sys().Unmount(mountPath)
345 if smslogger.CheckError(err, "Delete Domain") != nil {
346 return errors.New("Unable to delete domain specified")
352 // DeleteSecret deletes a secret mounted on the path provided
353 func (v *Vault) DeleteSecret(dom string, name string) error {
355 err := v.checkToken()
356 if smslogger.CheckError(err, "Token Check") != nil {
357 return errors.New("Token check failed")
360 dom = strings.TrimSpace(dom)
361 dom = v.vaultMountPrefix + "/" + dom
363 // Vault return is empty on successful delete
364 _, err = v.vaultClient.Logical().Delete(dom + "/" + name)
365 if smslogger.CheckError(err, "Delete Secret") != nil {
366 return errors.New("Unable to delete Secret at provided path")
372 // initRole is called only once during SMS bring up
373 // It initially creates a role and secret id associated with
374 // that role. Later restarts will use the existing role-id
375 // and secret-id stored on disk
376 func (v *Vault) initRole() error {
382 // Use the root token once here
383 v.vaultClient.SetToken(v.vaultToken)
384 defer v.vaultClient.ClearToken()
386 // Check if roleID and secretID has already been created
387 rID, error := smsauth.ReadFromFile("auth/role")
389 smslogger.WriteWarn("Unable to find RoleID. Generating...")
391 sID, error := smsauth.ReadFromFile("auth/secret")
393 smslogger.WriteWarn("Unable to find secretID. Generating...")
397 v.initRoleDone = true
402 rules := `path "sms/*" { capabilities = ["create", "read", "update", "delete", "list"] }
403 path "sys/mounts/sms*" { capabilities = ["update","delete","create"] }`
404 err := v.vaultClient.Sys().PutPolicy(v.policyName, rules)
405 if smslogger.CheckError(err, "Creating Policy") != nil {
406 return errors.New("Unable to create policy for approle creation")
409 //Check if applrole is mounted
410 authMounts, err := v.vaultClient.Sys().ListAuth()
411 if smslogger.CheckError(err, "Mount Auth Backend") != nil {
412 return errors.New("Unable to get mounted auth backends")
415 approleMounted := false
416 for k, v := range authMounts {
417 if v.Type == "approle" && k == "approle/" {
418 approleMounted = true
423 // Mount approle in case its not already mounted
425 v.vaultClient.Sys().EnableAuth("approle", "approle", "")
428 rName := v.vaultMountPrefix + "-role"
429 data := map[string]interface{}{
431 "policies": [2]string{"default", v.policyName},
435 v.vaultClient.Logical().Write("auth/approle/role/"+rName, data)
436 sec, err := v.vaultClient.Logical().Read("auth/approle/role/" + rName + "/role-id")
437 if smslogger.CheckError(err, "Create RoleID") != nil {
438 return errors.New("Unable to create role ID for approle")
440 v.roleID = sec.Data["role_id"].(string)
442 // Create a secret-id to go with it
443 sec, err = v.vaultClient.Logical().Write("auth/approle/role/"+rName+"/secret-id",
444 map[string]interface{}{})
445 if smslogger.CheckError(err, "Create SecretID") != nil {
446 return errors.New("Unable to create secret ID for role")
449 v.secretID = sec.Data["secret_id"].(string)
450 v.initRoleDone = true
452 * Revoke the Root token.
453 * If a new Root Token is needed, it will need to be created
454 * using the unseal shards.
456 err = v.vaultClient.Auth().Token().RevokeSelf(v.vaultToken)
457 if smslogger.CheckError(err, "Revoke Root Token") != nil {
458 smslogger.WriteWarn("Unable to Revoke Token")
460 // Revoked successfully and clear it
464 // Store the role-id and secret-id
465 // We will need this if SMS restarts
466 smsauth.WriteToFile(v.roleID, "auth/role")
467 smsauth.WriteToFile(v.secretID, "auth/secret")
472 // Function checkToken() gets called multiple times to create
474 func (v *Vault) checkToken() error {
479 // Init Role if it is not yet done
480 // Role needs to be created before token can be created
483 smslogger.WriteError(err.Error())
484 return errors.New("Unable to initRole in checkToken")
487 // Return immediately if token still has life
488 if v.vaultClient.Token() != "" &&
489 time.Since(v.vaultTempTokenTTL) < time.Minute*50 {
493 // Create a temporary token using our roleID and secretID
494 out, err := v.vaultClient.Logical().Write("auth/approle/login",
495 map[string]interface{}{"role_id": v.roleID, "secret_id": v.secretID})
496 if smslogger.CheckError(err, "Create Temp Token") != nil {
497 return errors.New("Unable to create Temporary Token for Role")
500 tok, err := out.TokenID()
502 v.vaultTempTokenTTL = time.Now()
503 v.vaultClient.SetToken(tok)
507 // vaultInit() is used to initialize the vault in cases where it is not
508 // initialized. This happens once during intial bring up.
509 func (v *Vault) initializeVault() error {
511 // Check for vault init status and don't exit till it is initialized
513 init, err := v.vaultClient.Sys().InitStatus()
514 if smslogger.CheckError(err, "Get Vault Init Status") != nil {
515 smslogger.WriteInfo("Trying again in 10s...")
516 time.Sleep(time.Second * 10)
519 // Did not get any error
521 smslogger.WriteInfo("Vault is already Initialized")
525 // init status is false
526 // break out of loop and finish initialization
527 smslogger.WriteInfo("Vault is not initialized. Initializing...")
531 // Hardcoded this to 3. We should make this configurable
533 initReq := &vaultapi.InitRequest{
538 pbkey, prkey, err := smsauth.GeneratePGPKeyPair()
540 if smslogger.CheckError(err, "Generating PGP Keys") != nil {
541 smslogger.WriteError("Error Generating PGP Keys. Vault Init will not use encryption!")
543 initReq.PGPKeys = []string{pbkey, pbkey, pbkey}
544 initReq.RootTokenPGPKey = pbkey
547 resp, err := v.vaultClient.Sys().Init(initReq)
548 if smslogger.CheckError(err, "Initialize Vault") != nil {
549 return errors.New("FATAL: Unable to initialize Vault")
554 v.shards = resp.KeysB64
555 v.vaultToken, _ = smsauth.DecryptPGPString(resp.RootToken, prkey)
559 return errors.New("FATAL: Init response was empty")