Adding token creation for operations 47/32447/3
authorKiran <kiran.k.kamineni@intel.com>
Wed, 21 Feb 2018 21:16:53 +0000 (13:16 -0800)
committerKiran <kiran.k.kamineni@intel.com>
Thu, 22 Feb 2018 00:13:45 +0000 (16:13 -0800)
Secret domain creation and secret creation is controlled
using approle authentication within the sms service
A temporary token with a short ttl is created and used
for adding domains and secrets into vault right now
Root token is used only once during the initial bring up
Also fixing unit test for backend.go

Issue-ID: AAF-99
Change-Id: I1352dadb32b54caaef86c7795601bf04d657dc3b
Signed-off-by: Kiran <kiran.k.kamineni@intel.com>
sms-service/src/sms/Gopkg.lock
sms-service/src/sms/Gopkg.toml
sms-service/src/sms/auth/auth_test.go
sms-service/src/sms/backend/backend.go
sms-service/src/sms/backend/backend_test.go
sms-service/src/sms/backend/vault.go
sms-service/src/sms/config/config.go
sms-service/src/sms/handler/handler.go
sms-service/src/sms/smsconfig.json

index da2fafd..b69eb59 100644 (file)
   packages = ["."]
   revision = "6bb64b370b90e7ef1fa532be9e591a81c3493e00"
 
+[[projects]]
+  branch = "master"
+  name = "github.com/hashicorp/go-uuid"
+  packages = ["."]
+  revision = "64130c7a86d732268a38cb04cfbaf0cc987fda98"
+
 [[projects]]
   branch = "master"
   name = "github.com/hashicorp/hcl"
 [solve-meta]
   analyzer-name = "dep"
   analyzer-version = 1
-  inputs-digest = "04ef42d3e34fec943a6bbcbde4a3caea30a3ca59d53faf7c99aa63094bea4e8f"
+  inputs-digest = "c49b267ee83a36c520ae964867f725fc7d934296f38240152e6eec2842846ac0"
   solver-name = "gps-cdcl"
   solver-version = 1
index af2ce87..6e40849 100644 (file)
@@ -27,3 +27,7 @@
 [[constraint]]
   name = "github.com/hashicorp/vault"
   version = "0.9.3"
+
+[[constraint]]
+  branch = "master"
+  name = "github.com/hashicorp/go-uuid"
index 15f0cf3..1cacfe6 100644 (file)
@@ -35,8 +35,8 @@ func TestGetTLSConfig(t *testing.T) {
                if int(actual) != expected {
                        t.Errorf("Test Failed due to version mismatch")
                }
-       if tlsConfig == nil {
-               t.Errorf("Test Failed due to GetTLSConfig returned nil")
-               }
+               if tlsConfig == nil {
+                       t.Errorf("Test Failed due to GetTLSConfig returned nil")
+               }
        }
 }
index 5611f37..2536fe1 100644 (file)
 
 package backend
 
+import (
+       smsconfig "sms/config"
+)
+
 // SecretDomain is where Secrets are stored.
 // A single domain can have any number of secrets
 type SecretDomain struct {
@@ -32,8 +36,8 @@ type SecretKeyValue struct {
 // Secret is the struct that defines the structure of a secret
 // A single Secret can have any number of SecretKeyValue pairs
 type Secret struct {
-       Name   string           `json:"name"`
-       Values []SecretKeyValue `json:"values"`
+       Name   string            `json:"name"`
+       Values map[string]string `json:"values"`
 }
 
 // SecretBackend interface that will be implemented for various secret backends
@@ -53,7 +57,11 @@ type SecretBackend interface {
 
 // InitSecretBackend returns an interface implementation
 func InitSecretBackend() (SecretBackend, error) {
-       backendImpl := &Vault{}
+       backendImpl := &Vault{
+               vaultAddress: smsconfig.SMSConfig.VaultAddress,
+               vaultToken:   smsconfig.SMSConfig.VaultToken,
+       }
+
        err := backendImpl.Init()
        if err != nil {
                return nil, err
index a3a616c..92ca971 100644 (file)
@@ -22,14 +22,16 @@ import (
 )
 
 func TestInitSecretBackend(t *testing.T) {
-       smsconfig.SMSConfig = &smsconfig.SMSConfiguration{VaultAddress: "http://localhost:8200"}
+       smsconfig.SMSConfig = &smsconfig.SMSConfiguration{
+               VaultAddress: "http://localhost:8200",
+       }
        sec, err := InitSecretBackend()
-       // We don't expect an error to be returned as Init only creates a client
-       // Does not expect backend to be running
-       if err != nil {
+       // We expect an error to be returned as Init expects
+       // backend to be running
+       if err == nil {
                t.Fatal("InitSecretBackend : error creating")
        }
-       if sec == nil {
-               t.Fatal("InitSecretBackend: returned SecretBackend was nil")
+       if sec != nil {
+               t.Fatal("InitSecretBackend: returned SecretBackend was *NOT* nil, expected nil")
        }
 }
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
+}
index e1c1b86..b7f97d2 100644 (file)
@@ -21,12 +21,16 @@ import (
        "os"
 )
 
+// SMSConfiguration loads up all the values that are used to configure
+// backend implementations
+// TODO: Review these and see if they can be created/discovered dynamically
 type SMSConfiguration struct {
        CAFile     string `json:"cafile"`
        ServerCert string `json:"servercert"`
        ServerKey  string `json:"serverkey"`
 
        VaultAddress string `json:"vaultaddress"`
+       VaultToken   string `json:"vaulttoken"`
 }
 
 // SMSConfig is the structure that stores the configuration
index 7a3b7dc..f287263 100644 (file)
@@ -21,19 +21,19 @@ import (
        "github.com/gorilla/mux"
        "net/http"
 
-       "sms/backend"
+       smsbackend "sms/backend"
 )
 
 // handler stores two interface implementations that implement
 // the backend functionality
 type handler struct {
-       secretBackend backend.SecretBackend
-       loginBackend  backend.LoginBackend
+       secretBackend smsbackend.SecretBackend
+       loginBackend  smsbackend.LoginBackend
 }
 
 // createSecretDomainHandler creates a secret domain with a name provided
 func (h handler) createSecretDomainHandler(w http.ResponseWriter, r *http.Request) {
-       var d backend.SecretDomain
+       var d smsbackend.SecretDomain
 
        err := json.NewDecoder(r.Body).Decode(&d)
        if err != nil {
@@ -41,7 +41,17 @@ func (h handler) createSecretDomainHandler(w http.ResponseWriter, r *http.Reques
                return
        }
 
-       h.secretBackend.CreateSecretDomain(d.Name)
+       dom, err := h.secretBackend.CreateSecretDomain(d.Name)
+       if err != nil {
+               http.Error(w, err.Error(), 400)
+               return
+       }
+
+       err = json.NewEncoder(w).Encode(dom)
+       if err != nil {
+               http.Error(w, err.Error(), 400)
+               return
+       }
 }
 
 // getSecretDomainHandler returns list of secret domains
@@ -63,10 +73,12 @@ func (h handler) deleteSecretDomainHandler(w http.ResponseWriter, r *http.Reques
 
 // createSecretHandler handles creation of secrets on a given domain name
 func (h handler) createSecretHandler(w http.ResponseWriter, r *http.Request) {
+       // Get domain name from URL
        vars := mux.Vars(r)
        domName := vars["domName"]
 
-       var b backend.Secret
+       // Get secrets to be stored from body
+       var b smsbackend.Secret
        err := json.NewDecoder(r.Body).Decode(&b)
        if err != nil {
                http.Error(w, err.Error(), 400)
@@ -121,9 +133,19 @@ func (h handler) loginHandler(w http.ResponseWriter, r *http.Request) {
 
 }
 
+// initSMSHandler
+func (h handler) initSMSHandler(w http.ResponseWriter, r *http.Request) {
+
+}
+
+// unsealHandler
+func (h handler) unsealHandler(w http.ResponseWriter, r *http.Request) {
+
+}
+
 // CreateRouter returns an http.Handler for the registered URLs
 // Takes an interface implementation as input
-func CreateRouter(b backend.SecretBackend) http.Handler {
+func CreateRouter(b smsbackend.SecretBackend) http.Handler {
        h := handler{secretBackend: b}
 
        // Create a new mux to handle URL endpoints
@@ -131,7 +153,11 @@ func CreateRouter(b backend.SecretBackend) http.Handler {
 
        router.HandleFunc("/v1/sms/login", h.loginHandler).Methods("POST")
 
+       // Initialization APIs which will be used by quorum client
+       // to unseal and to provide root token to sms service
        router.HandleFunc("/v1/sms/status", h.statusHandler).Methods("GET")
+       router.HandleFunc("/v1/sms/unseal", h.unsealHandler).Methods("POST")
+       router.HandleFunc("/v1/sms/init", h.initSMSHandler).Methods("POST")
 
        router.HandleFunc("/v1/sms/domain", h.createSecretDomainHandler).Methods("POST")
        router.HandleFunc("/v1/sms/domain/{domName}", h.getSecretDomainHandler).Methods("GET")
index ddb89d3..b683bf8 100644 (file)
@@ -1,7 +1,8 @@
 {
-    "cafile": "auth/selfsignedca.pem",
+    "cafile":     "auth/selfsignedca.pem",
     "servercert": "auth/server_cat.cert",
-    "serverkey": "auth/server.key",
+    "serverkey":  "auth/server.key",
 
-    "vaultaddress": "http://localhost:8200"
+    "vaultaddress":     "http://localhost:8200",
+    "vaulttoken":       "1ee03564-80d8-2080-2c77-0bb097cba512"
 }