Log errors to log file
[aaf/sms.git] / sms-service / src / sms / backend / vault.go
index c912dae..a4ebaaa 100644 (file)
@@ -19,9 +19,10 @@ package backend
 import (
        uuid "github.com/hashicorp/go-uuid"
        vaultapi "github.com/hashicorp/vault/api"
+       smslogger "sms/log"
 
+       "errors"
        "fmt"
-       "log"
        "strings"
        "sync"
        "time"
@@ -29,19 +30,17 @@ import (
 
 // Vault is the main Struct used in Backend to initialize the struct
 type Vault struct {
-       vaultAddress   string
-       vaultToken     string
-       vaultMount     string
-       vaultTempToken string
-
-       vaultClient       *vaultapi.Client
        engineType        string
+       initRoleDone      bool
        policyName        string
        roleID            string
        secretID          string
+       tokenLock         sync.Mutex
+       vaultAddress      string
+       vaultClient       *vaultapi.Client
+       vaultMount        string
        vaultTempTokenTTL time.Time
-
-       tokenLock sync.Mutex
+       vaultToken        string
 }
 
 // Init will initialize the vault connection
@@ -52,25 +51,22 @@ func (v *Vault) Init() error {
        vaultCFG.Address = v.vaultAddress
        client, err := vaultapi.NewClient(vaultCFG)
        if err != nil {
-               return err
+               smslogger.WriteError(err.Error())
+               return errors.New("Unable to create new vault client")
        }
 
        v.engineType = "kv"
+       v.initRoleDone = false
        v.policyName = "smsvaultpolicy"
-       v.vaultMount = "sms"
        v.vaultClient = client
+       v.vaultMount = "sms"
 
-       // Check if vault is ready and unsealed
-       seal, err := v.GetStatus()
+       err = v.initRole()
        if err != nil {
-               return err
-       }
-       if seal == true {
-               return fmt.Errorf("Vault is still sealed. Unseal before use")
+               smslogger.WriteError(err.Error())
+               smslogger.WriteInfo("InitRole will try again later")
        }
 
-       v.initRole()
-       v.checkToken()
        return nil
 }
 
@@ -79,24 +75,88 @@ func (v *Vault) GetStatus() (bool, error) {
        sys := v.vaultClient.Sys()
        sealStatus, err := sys.SealStatus()
        if err != nil {
-               return false, err
+               smslogger.WriteError(err.Error())
+               return false, errors.New("Error getting status")
        }
 
        return sealStatus.Sealed, nil
 }
 
-// GetSecretDomain returns any information related to the secretDomain
-// More information can be added in the future with updates to the struct
-func (v *Vault) GetSecretDomain(name string) (SecretDomain, error) {
-       return SecretDomain{}, nil
+// Unseal is a passthrough API that allows any
+// unseal or initialization processes for the backend
+func (v *Vault) Unseal(shard string) error {
+       sys := v.vaultClient.Sys()
+       _, err := sys.Unseal(shard)
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return errors.New("Unable to execute unseal operation with specified shard")
+       }
+
+       return nil
 }
 
 // GetSecret returns a secret mounted on a particular domain name
 // The secret itself is referenced via its name which translates to
 // a mount path in vault
-func (v *Vault) GetSecret(dom string, sec string) (Secret, error) {
+func (v *Vault) GetSecret(dom string, name string) (Secret, error) {
+       err := v.checkToken()
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return Secret{}, errors.New("Token check failed")
+       }
+
+       dom = v.vaultMount + "/" + dom
+
+       sec, err := v.vaultClient.Logical().Read(dom + "/" + name)
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return Secret{}, errors.New("Unable to read Secret at provided path")
+       }
+
+       // sec and err are nil in the case where a path does not exist
+       if sec == nil {
+               smslogger.WriteWarn("Vault read was empty. Invalid Path")
+               return Secret{}, errors.New("Secret not found at the provided path")
+       }
+
+       return Secret{Name: name, Values: sec.Data}, nil
+}
+
+// ListSecret returns a list of secret names on a particular domain
+// The values of the secret are not returned
+func (v *Vault) ListSecret(dom string) ([]string, error) {
+       err := v.checkToken()
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return nil, errors.New("Token check failed")
+       }
+
+       dom = v.vaultMount + "/" + dom
+
+       sec, err := v.vaultClient.Logical().List(dom)
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return nil, errors.New("Unable to read Secret at provided path")
+       }
+
+       // sec and err are nil in the case where a path does not exist
+       if sec == nil {
+               smslogger.WriteWarn("Vaultclient returned empty data")
+               return nil, errors.New("Secret not found at the provided path")
+       }
+
+       val, ok := sec.Data["keys"].([]interface{})
+       if !ok {
+               smslogger.WriteError("Secret not found at the provided path")
+               return nil, errors.New("Secret not found at the provided path")
+       }
+
+       retval := make([]string, len(val))
+       for i, v := range val {
+               retval[i] = fmt.Sprint(v)
+       }
 
-       return Secret{}, nil
+       return retval, nil
 }
 
 // CreateSecretDomain mounts the kv backend on a path with the given name
@@ -104,7 +164,8 @@ func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) {
        // Check if token is still valid
        err := v.checkToken()
        if err != nil {
-               return SecretDomain{}, err
+               smslogger.WriteError(err.Error())
+               return SecretDomain{}, errors.New("Token Check failed")
        }
 
        name = strings.TrimSpace(name)
@@ -119,7 +180,8 @@ func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) {
 
        err = v.vaultClient.Sys().Mount(mountPath, mountInput)
        if err != nil {
-               return SecretDomain{}, err
+               smslogger.WriteError(err.Error())
+               return SecretDomain{}, errors.New("Unable to create Secret Domain")
        }
 
        uuid, _ := uuid.GenerateUUID()
@@ -128,20 +190,63 @@ func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) {
 
 // CreateSecret creates a secret mounted on a particular domain name
 // The secret itself is mounted on a path specified by name
-func (v *Vault) CreateSecret(dom string, sec Secret) (Secret, error) {
+func (v *Vault) CreateSecret(dom string, sec Secret) error {
+       err := v.checkToken()
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return errors.New("Token check failed")
+       }
+
+       dom = v.vaultMount + "/" + dom
+
+       // Vault return is empty on successful write
+       // TODO: Check if values is not empty
+       _, err = v.vaultClient.Logical().Write(dom+"/"+sec.Name, sec.Values)
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return errors.New("Unable to create Secret at provided path")
+       }
 
-       return Secret{}, nil
+       return nil
 }
 
 // DeleteSecretDomain deletes a secret domain which translates to
 // an unmount operation on the given path in Vault
 func (v *Vault) DeleteSecretDomain(name string) error {
+       err := v.checkToken()
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return errors.New("Token Check Failed")
+       }
+
+       name = strings.TrimSpace(name)
+       mountPath := v.vaultMount + "/" + name
+
+       err = v.vaultClient.Sys().Unmount(mountPath)
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return errors.New("Unable to delete domain specified")
+       }
 
        return nil
 }
 
 // DeleteSecret deletes a secret mounted on the path provided
 func (v *Vault) DeleteSecret(dom string, name string) error {
+       err := v.checkToken()
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return errors.New("Token check failed")
+       }
+
+       dom = v.vaultMount + "/" + dom
+
+       // Vault return is empty on successful delete
+       _, err = v.vaultClient.Logical().Delete(dom + "/" + name)
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return errors.New("Unable to delete Secret at provided path")
+       }
 
        return nil
 }
@@ -154,7 +259,11 @@ func (v *Vault) initRole() error {
 
        rules := `path "sms/*" { capabilities = ["create", "read", "update", "delete", "list"] }
                        path "sys/mounts/sms*" { capabilities = ["update","delete","create"] }`
-       v.vaultClient.Sys().PutPolicy(v.policyName, rules)
+       err := v.vaultClient.Sys().PutPolicy(v.policyName, rules)
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return errors.New("Unable to create policy for approle creation")
+       }
 
        rName := v.vaultMount + "-role"
        data := map[string]interface{}{
@@ -162,25 +271,45 @@ func (v *Vault) initRole() error {
                "policies":  [2]string{"default", v.policyName},
        }
 
-       // Delete role if it already exists
-       v.vaultClient.Logical().Delete("auth/approle/role/" + rName)
+       //Check if applrole is mounted
+       authMounts, err := v.vaultClient.Sys().ListAuth()
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return errors.New("Unable to get mounted auth backends")
+       }
+
+       approleMounted := false
+       for k, v := range authMounts {
+               if v.Type == "approle" && k == "approle/" {
+                       approleMounted = true
+                       break
+               }
+       }
 
        // Mount approle in case its not already mounted
-       v.vaultClient.Sys().EnableAuth("approle", "approle", "")
+       if !approleMounted {
+               v.vaultClient.Sys().EnableAuth("approle", "approle", "")
+       }
 
        // Create a role-id
        v.vaultClient.Logical().Write("auth/approle/role/"+rName, data)
        sec, err := v.vaultClient.Logical().Read("auth/approle/role/" + rName + "/role-id")
        if err != nil {
-               log.Fatal(err)
+               smslogger.WriteError(err.Error())
+               return errors.New("Unable to create role ID for approle")
        }
        v.roleID = sec.Data["role_id"].(string)
 
        // Create a secret-id to go with it
-       sec, _ = v.vaultClient.Logical().Write("auth/approle/role/"+rName+"/secret-id",
+       sec, err = v.vaultClient.Logical().Write("auth/approle/role/"+rName+"/secret-id",
                map[string]interface{}{})
-       v.secretID = sec.Data["secret_id"].(string)
+       if err != nil {
+               smslogger.WriteError(err.Error())
+               return errors.New("Unable to create secret ID for role")
+       }
 
+       v.secretID = sec.Data["secret_id"].(string)
+       v.initRoleDone = true
        return nil
 }
 
@@ -190,6 +319,16 @@ func (v *Vault) checkToken() error {
        v.tokenLock.Lock()
        defer v.tokenLock.Unlock()
 
+       // Init Role if it is not yet done
+       // Role needs to be created before token can be created
+       if v.initRoleDone == false {
+               err := v.initRole()
+               if err != nil {
+                       smslogger.WriteError(err.Error())
+                       return errors.New("Unable to initRole in checkToken")
+               }
+       }
+
        // Return immediately if token still has life
        if v.vaultClient.Token() != "" &&
                time.Since(v.vaultTempTokenTTL) < time.Minute*50 {
@@ -200,13 +339,13 @@ func (v *Vault) checkToken() error {
        out, err := v.vaultClient.Logical().Write("auth/approle/login",
                map[string]interface{}{"role_id": v.roleID, "secret_id": v.secretID})
        if err != nil {
-               return err
+               smslogger.WriteError(err.Error())
+               return errors.New("Unable to create Temporary Token for Role")
        }
 
        tok, err := out.TokenID()
 
-       v.vaultTempToken = tok
        v.vaultTempTokenTTL = time.Now()
-       v.vaultClient.SetToken(v.vaultTempToken)
+       v.vaultClient.SetToken(tok)
        return nil
 }