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
[[constraint]]
name = "github.com/hashicorp/vault"
version = "0.9.3"
+
+[[constraint]]
+ branch = "master"
+ name = "github.com/hashicorp/go-uuid"
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")
+ }
}
}
package backend
+import (
+ smsconfig "sms/config"
+)
+
// SecretDomain is where Secrets are stored.
// A single domain can have any number of secrets
type SecretDomain 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
// 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
)
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")
}
}
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
}
// 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
}
// 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
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
+}
"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
"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 {
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
// 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)
}
+// 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
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")
{
- "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"
}