diff options
-rw-r--r-- | sms-service/src/sms/Gopkg.lock | 8 | ||||
-rw-r--r-- | sms-service/src/sms/Gopkg.toml | 4 | ||||
-rw-r--r-- | sms-service/src/sms/auth/auth_test.go | 6 | ||||
-rw-r--r-- | sms-service/src/sms/backend/backend.go | 14 | ||||
-rw-r--r-- | sms-service/src/sms/backend/backend_test.go | 14 | ||||
-rw-r--r-- | sms-service/src/sms/backend/vault.go | 129 | ||||
-rw-r--r-- | sms-service/src/sms/config/config.go | 4 | ||||
-rw-r--r-- | sms-service/src/sms/handler/handler.go | 40 | ||||
-rw-r--r-- | sms-service/src/sms/smsconfig.json | 7 |
9 files changed, 197 insertions, 29 deletions
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 @@ -51,6 +51,12 @@ [[projects]] branch = "master" + name = "github.com/hashicorp/go-uuid" + packages = ["."] + revision = "64130c7a86d732268a38cb04cfbaf0cc987fda98" + +[[projects]] + branch = "master" name = "github.com/hashicorp/hcl" packages = [ ".", @@ -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" } |