Adding token creation for operations
[aaf/sms.git] / sms-service / src / sms / backend / vault.go
index f3e2ac1..c912dae 100644 (file)
 package backend
 
 import (
+       uuid "github.com/hashicorp/go-uuid"
        vaultapi "github.com/hashicorp/vault/api"
 
-       smsconfig "sms/config"
+       "fmt"
+       "log"
+       "strings"
+       "sync"
+       "time"
 )
 
 // Vault is the main Struct used in Backend to initialize the struct
 type Vault struct {
-       vaultClient *vaultapi.Client
+       vaultAddress   string
+       vaultToken     string
+       vaultMount     string
+       vaultTempToken string
+
+       vaultClient       *vaultapi.Client
+       engineType        string
+       policyName        string
+       roleID            string
+       secretID          string
+       vaultTempTokenTTL time.Time
+
+       tokenLock sync.Mutex
 }
 
 // Init will initialize the vault connection
+// It will also create the initial policy if it does not exist
 // TODO: Check to see if we need to wait for vault to be running
 func (v *Vault) Init() error {
        vaultCFG := vaultapi.DefaultConfig()
-       vaultCFG.Address = smsconfig.SMSConfig.VaultAddress
-
+       vaultCFG.Address = v.vaultAddress
        client, err := vaultapi.NewClient(vaultCFG)
        if err != nil {
                return err
        }
 
+       v.engineType = "kv"
+       v.policyName = "smsvaultpolicy"
+       v.vaultMount = "sms"
        v.vaultClient = client
+
+       // Check if vault is ready and unsealed
+       seal, err := v.GetStatus()
+       if err != nil {
+               return err
+       }
+       if seal == true {
+               return fmt.Errorf("Vault is still sealed. Unseal before use")
+       }
+
+       v.initRole()
+       v.checkToken()
        return nil
 }
 
@@ -56,7 +88,6 @@ func (v *Vault) GetStatus() (bool, error) {
 // 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
 }
 
@@ -70,8 +101,29 @@ func (v *Vault) GetSecret(dom string, sec string) (Secret, error) {
 
 // CreateSecretDomain mounts the kv backend on a path with the given name
 func (v *Vault) CreateSecretDomain(name string) (SecretDomain, error) {
+       // Check if token is still valid
+       err := v.checkToken()
+       if err != nil {
+               return SecretDomain{}, err
+       }
 
-       return SecretDomain{}, nil
+       name = strings.TrimSpace(name)
+       mountPath := v.vaultMount + "/" + name
+       mountInput := &vaultapi.MountInput{
+               Type:        v.engineType,
+               Description: "Mount point for domain: " + name,
+               Local:       false,
+               SealWrap:    false,
+               Config:      vaultapi.MountConfigInput{},
+       }
+
+       err = v.vaultClient.Sys().Mount(mountPath, mountInput)
+       if err != nil {
+               return SecretDomain{}, err
+       }
+
+       uuid, _ := uuid.GenerateUUID()
+       return SecretDomain{uuid, name}, nil
 }
 
 // CreateSecret creates a secret mounted on a particular domain name
@@ -93,3 +145,68 @@ func (v *Vault) DeleteSecret(dom string, name string) error {
 
        return nil
 }
+
+// initRole is called only once during the service bring up
+func (v *Vault) initRole() error {
+       // Use the root token once here
+       v.vaultClient.SetToken(v.vaultToken)
+       defer v.vaultClient.ClearToken()
+
+       rules := `path "sms/*" { capabilities = ["create", "read", "update", "delete", "list"] }
+                       path "sys/mounts/sms*" { capabilities = ["update","delete","create"] }`
+       v.vaultClient.Sys().PutPolicy(v.policyName, rules)
+
+       rName := v.vaultMount + "-role"
+       data := map[string]interface{}{
+               "token_ttl": "60m",
+               "policies":  [2]string{"default", v.policyName},
+       }
+
+       // Delete role if it already exists
+       v.vaultClient.Logical().Delete("auth/approle/role/" + rName)
+
+       // Mount approle in case its not already mounted
+       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)
+       }
+       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",
+               map[string]interface{}{})
+       v.secretID = sec.Data["secret_id"].(string)
+
+       return nil
+}
+
+// Function checkToken() gets called multiple times to create
+// temporary tokens
+func (v *Vault) checkToken() error {
+       v.tokenLock.Lock()
+       defer v.tokenLock.Unlock()
+
+       // Return immediately if token still has life
+       if v.vaultClient.Token() != "" &&
+               time.Since(v.vaultTempTokenTTL) < time.Minute*50 {
+               return nil
+       }
+
+       // Create a temporary token using our roleID and secretID
+       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
+       }
+
+       tok, err := out.TokenID()
+
+       v.vaultTempToken = tok
+       v.vaultTempTokenTTL = time.Now()
+       v.vaultClient.SetToken(v.vaultTempToken)
+       return nil
+}