From: Kiran Date: Wed, 21 Feb 2018 21:16:53 +0000 (-0800) Subject: Adding token creation for operations X-Git-Tag: 2.0.0-ONAP~83 X-Git-Url: https://gerrit.onap.org/r/gitweb?p=aaf%2Fsms.git;a=commitdiff_plain;h=735839b4257e0341ab2225e763247e848bb31696 Adding token creation for operations 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 --- diff --git a/sms-service/src/sms/Gopkg.lock b/sms-service/src/sms/Gopkg.lock index da2fafd..b69eb59 100644 --- a/sms-service/src/sms/Gopkg.lock +++ b/sms-service/src/sms/Gopkg.lock @@ -49,6 +49,12 @@ packages = ["."] revision = "6bb64b370b90e7ef1fa532be9e591a81c3493e00" +[[projects]] + branch = "master" + name = "github.com/hashicorp/go-uuid" + packages = ["."] + revision = "64130c7a86d732268a38cb04cfbaf0cc987fda98" + [[projects]] branch = "master" name = "github.com/hashicorp/hcl" @@ -129,6 +135,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "04ef42d3e34fec943a6bbcbde4a3caea30a3ca59d53faf7c99aa63094bea4e8f" + inputs-digest = "c49b267ee83a36c520ae964867f725fc7d934296f38240152e6eec2842846ac0" solver-name = "gps-cdcl" solver-version = 1 diff --git a/sms-service/src/sms/Gopkg.toml b/sms-service/src/sms/Gopkg.toml index af2ce87..6e40849 100644 --- a/sms-service/src/sms/Gopkg.toml +++ b/sms-service/src/sms/Gopkg.toml @@ -27,3 +27,7 @@ [[constraint]] name = "github.com/hashicorp/vault" version = "0.9.3" + +[[constraint]] + branch = "master" + name = "github.com/hashicorp/go-uuid" diff --git a/sms-service/src/sms/auth/auth_test.go b/sms-service/src/sms/auth/auth_test.go index 15f0cf3..1cacfe6 100644 --- a/sms-service/src/sms/auth/auth_test.go +++ b/sms-service/src/sms/auth/auth_test.go @@ -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") + } } } diff --git a/sms-service/src/sms/backend/backend.go b/sms-service/src/sms/backend/backend.go index 5611f37..2536fe1 100644 --- a/sms-service/src/sms/backend/backend.go +++ b/sms-service/src/sms/backend/backend.go @@ -16,6 +16,10 @@ 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 diff --git a/sms-service/src/sms/backend/backend_test.go b/sms-service/src/sms/backend/backend_test.go index a3a616c..92ca971 100644 --- a/sms-service/src/sms/backend/backend_test.go +++ b/sms-service/src/sms/backend/backend_test.go @@ -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") } } diff --git a/sms-service/src/sms/backend/vault.go b/sms-service/src/sms/backend/vault.go index f3e2ac1..c912dae 100644 --- a/sms-service/src/sms/backend/vault.go +++ b/sms-service/src/sms/backend/vault.go @@ -17,28 +17,60 @@ 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 +} diff --git a/sms-service/src/sms/config/config.go b/sms-service/src/sms/config/config.go index e1c1b86..b7f97d2 100644 --- a/sms-service/src/sms/config/config.go +++ b/sms-service/src/sms/config/config.go @@ -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 diff --git a/sms-service/src/sms/handler/handler.go b/sms-service/src/sms/handler/handler.go index 7a3b7dc..f287263 100644 --- a/sms-service/src/sms/handler/handler.go +++ b/sms-service/src/sms/handler/handler.go @@ -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") diff --git a/sms-service/src/sms/smsconfig.json b/sms-service/src/sms/smsconfig.json index ddb89d3..b683bf8 100644 --- a/sms-service/src/sms/smsconfig.json +++ b/sms-service/src/sms/smsconfig.json @@ -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" }