diff --git a/nomad/server.go b/nomad/server.go index 9ba4045bd..53a59e593 100644 --- a/nomad/server.go +++ b/nomad/server.go @@ -26,6 +26,7 @@ import ( "github.com/hashicorp/raft" "github.com/hashicorp/raft-boltdb" "github.com/hashicorp/serf/serf" + vaultapi "github.com/hashicorp/vault/api" ) const ( @@ -139,6 +140,9 @@ type Server struct { // consulSyncer advertises this Nomad Agent with Consul consulSyncer *consul.Syncer + // vault is the client for communicating with Vault. + vault *vaultapi.Client + // Worker used for processing workers []*Worker @@ -201,6 +205,27 @@ func NewServer(config *Config, consulSyncer *consul.Syncer, logger *log.Logger) shutdownCh: make(chan struct{}), } + // Get the Vault API configuration + c, err := config.VaultConfig.ApiConfig(true) + if err != nil { + s.logger.Printf("[ERR] nomad: failed to create Vault API config: %v", err) + return nil, fmt.Errorf("Failed to create Vault API config: %v", err) + } + + // Create the Vault API client + v, err := vaultapi.NewClient(c) + if err != nil { + s.logger.Printf("[ERR] nomad: failed to create Vault API client: %v", err) + return nil, fmt.Errorf("Failed to create Vault API client: %v", err) + } + + // Set the wrapping function such that token creation is wrapped + v.SetWrappingLookupFunc(config.VaultConfig.GetWrappingFn()) + + // Set the token and store the client + v.SetToken(config.VaultConfig.PeriodicToken) + s.vault = v + // Create the periodic dispatcher for launching periodic jobs. s.periodicDispatcher = NewPeriodicDispatch(s.logger, s) diff --git a/nomad/server_test.go b/nomad/server_test.go index 0312ae0a4..3bedb0483 100644 --- a/nomad/server_test.go +++ b/nomad/server_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/hashicorp/nomad/command/agent/consul" + "github.com/hashicorp/nomad/nomad/structs/config" "github.com/hashicorp/nomad/testutil" ) @@ -132,3 +133,39 @@ func TestServer_Regions(t *testing.T) { t.Fatalf("err: %v", err) }) } + +func TestServer_VaultAPIClient(t *testing.T) { + // Create a server with a Vault Config + token := "123" + role := "foo" + s1 := testServer(t, func(c *Config) { + c.VaultConfig.TokenRoleName = role + c.VaultConfig.PeriodicToken = token + }) + defer s1.Shutdown() + + if s1.vault == nil { + t.Fatalf("expected a Vault API client") + } + + if act := s1.vault.Token(); act != token { + t.Fatalf("Vault client not set up correctly; got %v; want %v for token", act, token) + } + + createPath := "/v1/auth/token/create/" + role + wReq := s1.vault.NewRequest("POST", createPath) + if wReq.WrapTTL != config.VaultTokenCreateTTL { + t.Fatalf("Bad WrapTTL; got %q; want %q", wReq.WrapTTL, config.VaultTokenCreateTTL) + } + + wReq = s1.vault.NewRequest("GET", createPath) + if wReq.WrapTTL != "" { + t.Fatalf("Bad WrapTTL; got %q; want %q", wReq.WrapTTL, "") + } + + badPath := "/v1/auth/token/lookup-self" + wReq = s1.vault.NewRequest("PUT", badPath) + if wReq.WrapTTL != "" { + t.Fatalf("Bad WrapTTL; got %q; want %q", wReq.WrapTTL, "") + } +} diff --git a/nomad/structs/config/vault.go b/nomad/structs/config/vault.go index b149c14a7..b2c6334dd 100644 --- a/nomad/structs/config/vault.go +++ b/nomad/structs/config/vault.go @@ -1,6 +1,16 @@ package config -import vault "github.com/hashicorp/vault/api" +import ( + "fmt" + + vault "github.com/hashicorp/vault/api" +) + +const ( + // VaultTokenCreateTTL is the duration the wrapped token for the client is + // valid for. The units are in seconds. + VaultTokenCreateTTL = "60s" +) // VaultConfig contains the configuration information necessary to // communicate with Vault in order to: @@ -135,3 +145,16 @@ func (c *VaultConfig) Copy() *VaultConfig { *nc = *c return nc } + +// GetWrappingFn returns an appropriate wrapping function for Nomad +func (c *VaultConfig) GetWrappingFn() func(operation, path string) string { + createPath := fmt.Sprintf("auth/token/create/%s", c.TokenRoleName) + return func(operation, path string) string { + // Only wrap the token create operation + if operation != "POST" || path != createPath { + return "" + } + + return VaultTokenCreateTTL + } +}