diff --git a/command/agent.go b/command/agent.go index 5fa18f16bb..f3b40fcb3f 100644 --- a/command/agent.go +++ b/command/agent.go @@ -339,22 +339,6 @@ func (c *AgentCommand) Run(args []string) int { return 1 } - var inmemSink sink.Sink - if config.Cache.UseAutoAuthToken { - cacheLogger.Debug("auto-auth token is allowed to be used; configuring inmem sink") - inmemSink, err = inmem.New(&sink.SinkConfig{ - Logger: cacheLogger, - }) - if err != nil { - c.UI.Error(fmt.Sprintf("Error creating inmem sink for cache: %v", err)) - return 1 - } - sinks = append(sinks, &sink.SinkConfig{ - Logger: cacheLogger, - Sink: inmemSink, - }) - } - // Create the API proxier apiProxy, err := cache.NewAPIProxy(&cache.APIProxyConfig{ Client: client, @@ -378,6 +362,22 @@ func (c *AgentCommand) Run(args []string) int { return 1 } + var inmemSink sink.Sink + if config.Cache.UseAutoAuthToken { + cacheLogger.Debug("auto-auth token is allowed to be used; configuring inmem sink") + inmemSink, err = inmem.New(&sink.SinkConfig{ + Logger: cacheLogger, + }, leaseCache) + if err != nil { + c.UI.Error(fmt.Sprintf("Error creating inmem sink for cache: %v", err)) + return 1 + } + sinks = append(sinks, &sink.SinkConfig{ + Logger: cacheLogger, + Sink: inmemSink, + }) + } + // Create a muxer and add paths relevant for the lease cache layer mux := http.NewServeMux() mux.Handle(consts.AgentPathCacheClear, leaseCache.HandleCacheClear(ctx)) diff --git a/command/agent/cache/lease_cache.go b/command/agent/cache/lease_cache.go index 86911bcc19..a39f5739a5 100644 --- a/command/agent/cache/lease_cache.go +++ b/command/agent/cache/lease_cache.go @@ -17,6 +17,7 @@ import ( hclog "github.com/hashicorp/go-hclog" "github.com/hashicorp/vault/api" cachememdb "github.com/hashicorp/vault/command/agent/cache/cachememdb" + "github.com/hashicorp/vault/helper/base62" "github.com/hashicorp/vault/helper/consts" "github.com/hashicorp/vault/helper/cryptoutil" "github.com/hashicorp/vault/helper/jsonutil" @@ -264,7 +265,7 @@ func (c *LeaseCache) Send(ctx context.Context, req *SendRequest) (*SendResponse, entry.TokenParent = req.Token } - renewCtxInfo = c.createCtxInfo(parentCtx, secret.Auth.ClientToken) + renewCtxInfo = c.createCtxInfo(parentCtx) index.Token = secret.Auth.ClientToken index.TokenAccessor = secret.Auth.Accessor @@ -316,7 +317,7 @@ func (c *LeaseCache) Send(ctx context.Context, req *SendRequest) (*SendResponse, return resp, nil } -func (c *LeaseCache) createCtxInfo(ctx context.Context, token string) *ContextInfo { +func (c *LeaseCache) createCtxInfo(ctx context.Context) *ContextInfo { if ctx == nil { ctx = c.baseCtxInfo.Ctx } @@ -759,3 +760,61 @@ func deriveNamespaceAndRevocationPath(req *SendRequest) (string, string) { return namespace, fmt.Sprintf("/v1%s", nonVersionedPath) } + +// RegisterAutoAuthToken adds the provided auto-token into the cache. This is +// primarily used to register the auto-auth token and should only be called +// within a sink's WriteToken func. +func (c *LeaseCache) RegisterAutoAuthToken(token string) error { + // Get the token from the cache + oldIndex, err := c.db.Get(cachememdb.IndexNameToken, token) + if err != nil { + return err + } + + // If the index is found, defer its cancelFunc + if oldIndex != nil { + defer oldIndex.RenewCtxInfo.CancelFunc() + } + + // The following randomly generated values are required for index stored by + // the cache, but are not actually used. We use random values to prevent + // accidental access. + id, err := base62.Random(5) + if err != nil { + return err + } + namespace, err := base62.Random(5) + if err != nil { + return err + } + requestPath, err := base62.Random(5) + if err != nil { + return err + } + + index := &cachememdb.Index{ + ID: id, + Token: token, + Namespace: namespace, + RequestPath: requestPath, + } + + // Derive a context off of the lease cache's base context + ctxInfo := c.createCtxInfo(nil) + + index.RenewCtxInfo = &cachememdb.ContextInfo{ + Ctx: ctxInfo.Ctx, + CancelFunc: ctxInfo.CancelFunc, + DoneCh: ctxInfo.DoneCh, + } + + // Store the index in the cache + c.logger.Debug("storing auto-auth token into the cache") + err = c.db.Set(index) + if err != nil { + c.logger.Error("failed to cache the auto-auth token", "error", err) + return err + } + + return nil +} diff --git a/command/agent/cache_end_to_end_test.go b/command/agent/cache_end_to_end_test.go index f9727c8c5b..ba6f9c2877 100644 --- a/command/agent/cache_end_to_end_test.go +++ b/command/agent/cache_end_to_end_test.go @@ -3,7 +3,6 @@ package agent import ( "context" "fmt" - "github.com/hashicorp/vault/helper/consts" "io/ioutil" "net" "net/http" @@ -21,12 +20,23 @@ import ( "github.com/hashicorp/vault/command/agent/sink" "github.com/hashicorp/vault/command/agent/sink/file" "github.com/hashicorp/vault/command/agent/sink/inmem" + "github.com/hashicorp/vault/helper/consts" "github.com/hashicorp/vault/helper/logging" vaulthttp "github.com/hashicorp/vault/http" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/vault" ) +const policyAutoAuthAppRole = ` +path "/kv/*" { + capabilities = ["sudo", "create", "read", "update", "delete", "list"] +} + +path "/auth/token/create" { + capabilities = ["create", "update"] +} +` + func TestCache_UsingAutoAuthToken(t *testing.T) { var err error logger := logging.NewVaultLogger(log.Trace) @@ -34,6 +44,9 @@ func TestCache_UsingAutoAuthToken(t *testing.T) { DisableMlock: true, DisableCache: true, Logger: log.NewNullLogger(), + LogicalBackends: map[string]logical.Factory{ + "kv": vault.LeasedPassthroughBackendFactory, + }, CredentialBackends: map[string]logical.Factory{ "approle": credAppRole.Factory, }, @@ -58,6 +71,28 @@ func TestCache_UsingAutoAuthToken(t *testing.T) { defer os.Setenv(api.EnvVaultCACert, os.Getenv(api.EnvVaultCACert)) os.Setenv(api.EnvVaultCACert, fmt.Sprintf("%s/ca_cert.pem", cluster.TempDir)) + err = client.Sys().Mount("kv", &api.MountInput{ + Type: "kv", + }) + if err != nil { + t.Fatal(err) + } + + // Create a secret in the backend + _, err = client.Logical().Write("kv/foo", map[string]interface{}{ + "value": "bar", + "ttl": "1h", + }) + if err != nil { + t.Fatal(err) + } + + // Add an kv-admin policy + if err := client.Sys().PutPolicy("test-autoauth", policyAutoAuthAppRole); err != nil { + t.Fatal(err) + } + + // Enable approle err = client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{ Type: "approle", }) @@ -69,6 +104,7 @@ func TestCache_UsingAutoAuthToken(t *testing.T) { "bind_secret_id": "true", "token_ttl": "3s", "token_max_ttl": "10s", + "policies": []string{"test-autoauth"}, }) if err != nil { t.Fatal(err) @@ -127,6 +163,29 @@ func TestCache_UsingAutoAuthToken(t *testing.T) { "remove_secret_id_file_after_reading": true, } + cacheLogger := logging.NewVaultLogger(hclog.Trace).Named("cache") + + // Create the API proxier + apiProxy, err := cache.NewAPIProxy(&cache.APIProxyConfig{ + Client: client, + Logger: cacheLogger.Named("apiproxy"), + }) + if err != nil { + t.Fatal(err) + } + + // Create the lease cache proxier and set its underlying proxier to + // the API proxier. + leaseCache, err := cache.NewLeaseCache(&cache.LeaseCacheConfig{ + Client: client, + BaseContext: ctx, + Proxier: apiProxy, + Logger: cacheLogger.Named("leasecache"), + }) + if err != nil { + t.Fatal(err) + } + am, err := agentapprole.NewApproleAuthMethod(&auth.AuthConfig{ Logger: logger.Named("auth.approle"), MountPath: "auth/approle", @@ -166,7 +225,7 @@ func TestCache_UsingAutoAuthToken(t *testing.T) { Logger: logger.Named("sink.inmem"), } - inmemSink, err := inmem.New(inmemSinkConfig) + inmemSink, err := inmem.New(inmemSinkConfig, leaseCache) if err != nil { t.Fatal(err) } @@ -239,29 +298,6 @@ func TestCache_UsingAutoAuthToken(t *testing.T) { defer listener.Close() - cacheLogger := logging.NewVaultLogger(hclog.Trace).Named("cache") - - // Create the API proxier - apiProxy, err := cache.NewAPIProxy(&cache.APIProxyConfig{ - Client: client, - Logger: cacheLogger.Named("apiproxy"), - }) - if err != nil { - t.Fatal(err) - } - - // Create the lease cache proxier and set its underlying proxier to - // the API proxier. - leaseCache, err := cache.NewLeaseCache(&cache.LeaseCacheConfig{ - Client: client, - BaseContext: ctx, - Proxier: apiProxy, - Logger: cacheLogger.Named("leasecache"), - }) - if err != nil { - t.Fatal(err) - } - // Create a muxer and add paths relevant for the lease cache layer mux := http.NewServeMux() mux.Handle(consts.AgentPathCacheClear, leaseCache.HandleCacheClear(ctx)) @@ -295,4 +331,51 @@ func TestCache_UsingAutoAuthToken(t *testing.T) { if resp == nil { t.Fatalf("failed to use the auto-auth token to perform lookup-self") } + + // The following block tests lease creation caching using the auto-auth token. + { + resp, err = testClient.Logical().Read("kv/foo") + if err != nil { + t.Fatal(err) + } + + origReqID := resp.RequestID + + resp, err = testClient.Logical().Read("kv/foo") + if err != nil { + t.Fatal(err) + } + + // Sleep for a bit to allow renewer logic to kick in + time.Sleep(20 * time.Millisecond) + + cacheReqID := resp.RequestID + + if origReqID != cacheReqID { + t.Fatalf("request ID mismatch, expected second request to be a cached response: %s != %s", origReqID, cacheReqID) + } + } + + // The following block tests auth token creation caching (child, non-orphan + // tokens) using the auto-auth token. + { + resp, err = testClient.Logical().Write("auth/token/create", nil) + if err != nil { + t.Fatal(err) + } + origReqID := resp.RequestID + + // Sleep for a bit to allow renewer logic to kick in + time.Sleep(20 * time.Millisecond) + + resp, err = testClient.Logical().Write("auth/token/create", nil) + if err != nil { + t.Fatal(err) + } + cacheReqID := resp.RequestID + + if origReqID != cacheReqID { + t.Fatalf("request ID mismatch, expected second request to be a cached response: %s != %s", origReqID, cacheReqID) + } + } } diff --git a/command/agent/sink/inmem/inmem_sink.go b/command/agent/sink/inmem/inmem_sink.go index 0d016d9504..d382eb8054 100644 --- a/command/agent/sink/inmem/inmem_sink.go +++ b/command/agent/sink/inmem/inmem_sink.go @@ -4,27 +4,37 @@ import ( "errors" hclog "github.com/hashicorp/go-hclog" + "github.com/hashicorp/vault/command/agent/cache" "github.com/hashicorp/vault/command/agent/sink" ) // inmemSink retains the auto-auth token in memory and exposes it via // sink.SinkReader interface. type inmemSink struct { - logger hclog.Logger - token string + logger hclog.Logger + token string + leaseCache *cache.LeaseCache } -func New(conf *sink.SinkConfig) (sink.Sink, error) { +// New creates a new instance of inmemSink. +func New(conf *sink.SinkConfig, leaseCache *cache.LeaseCache) (sink.Sink, error) { if conf.Logger == nil { return nil, errors.New("nil logger provided") } + return &inmemSink{ - logger: conf.Logger, + logger: conf.Logger, + leaseCache: leaseCache, }, nil } func (s *inmemSink) WriteToken(token string) error { s.token = token + + if s.leaseCache != nil { + s.leaseCache.RegisterAutoAuthToken(token) + } + return nil }