diff --git a/command/agent.go b/command/agent.go index 7c2de32886..220679beb3 100644 --- a/command/agent.go +++ b/command/agent.go @@ -489,11 +489,12 @@ func (c *AgentCommand) Run(args []string) int { // Create the lease cache proxier and set its underlying proxier to // the API proxier. leaseCache, err = cache.NewLeaseCache(&cache.LeaseCacheConfig{ - Client: proxyClient, - BaseContext: ctx, - Proxier: apiProxy, - Logger: cacheLogger.Named("leasecache"), - UserAgentToUse: useragent.ProxyAPIProxyString(), + Client: proxyClient, + BaseContext: ctx, + Proxier: apiProxy, + Logger: cacheLogger.Named("leasecache"), + CacheDynamicSecrets: true, + UserAgentToUse: useragent.ProxyAPIProxyString(), }) if err != nil { c.UI.Error(fmt.Sprintf("Error creating lease cache: %v", err)) diff --git a/command/agent/cache_end_to_end_test.go b/command/agent/cache_end_to_end_test.go index bd0a8555ed..555d3f2879 100644 --- a/command/agent/cache_end_to_end_test.go +++ b/command/agent/cache_end_to_end_test.go @@ -6,7 +6,6 @@ package agent import ( "context" "fmt" - "io/ioutil" "net" "net/http" "os" @@ -123,7 +122,7 @@ func TestCache_UsingAutoAuthToken(t *testing.T) { } roleID1 := resp.Data["role_id"].(string) - rolef, err := ioutil.TempFile("", "auth.role-id.test.") + rolef, err := os.CreateTemp("", "auth.role-id.test.") if err != nil { t.Fatal(err) } @@ -132,7 +131,7 @@ func TestCache_UsingAutoAuthToken(t *testing.T) { defer os.Remove(role) t.Logf("input role_id_file_path: %s", role) - secretf, err := ioutil.TempFile("", "auth.secret-id.test.") + secretf, err := os.CreateTemp("", "auth.secret-id.test.") if err != nil { t.Fatal(err) } @@ -143,7 +142,7 @@ func TestCache_UsingAutoAuthToken(t *testing.T) { // We close these right away because we're just basically testing // permissions and finding a usable file name - ouf, err := ioutil.TempFile("", "auth.tokensink.test.") + ouf, err := os.CreateTemp("", "auth.tokensink.test.") if err != nil { t.Fatal(err) } @@ -176,11 +175,12 @@ func TestCache_UsingAutoAuthToken(t *testing.T) { // 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"), - UserAgentToUse: "test", + Client: client, + BaseContext: ctx, + Proxier: apiProxy, + Logger: cacheLogger.Named("leasecache"), + CacheDynamicSecrets: true, + UserAgentToUse: "test", }) if err != nil { t.Fatal(err) @@ -269,13 +269,13 @@ func TestCache_UsingAutoAuthToken(t *testing.T) { t.Fatal("expected notexist err") } - if err := ioutil.WriteFile(role, []byte(roleID1), 0o600); err != nil { + if err := os.WriteFile(role, []byte(roleID1), 0o600); err != nil { t.Fatal(err) } else { logger.Trace("wrote test role 1", "path", role) } - if err := ioutil.WriteFile(secret, []byte(secretID1), 0o600); err != nil { + if err := os.WriteFile(secret, []byte(secretID1), 0o600); err != nil { t.Fatal(err) } else { logger.Trace("wrote test secret 1", "path", secret) @@ -287,7 +287,7 @@ func TestCache_UsingAutoAuthToken(t *testing.T) { if time.Now().After(timeout) { t.Fatal("did not find a written token after timeout") } - val, err := ioutil.ReadFile(out) + val, err := os.ReadFile(out) if err == nil { os.Remove(out) if len(val) == 0 { diff --git a/command/agentproxyshared/cache/api_proxy_test.go b/command/agentproxyshared/cache/api_proxy_test.go index 48252f68d0..9e7035918c 100644 --- a/command/agentproxyshared/cache/api_proxy_test.go +++ b/command/agentproxyshared/cache/api_proxy_test.go @@ -272,11 +272,12 @@ func setupClusterAndAgentCommon(ctx context.Context, t *testing.T, coreConfig *v // Create the lease cache proxier and set its underlying proxier to // the API proxier. leaseCache, err = NewLeaseCache(&LeaseCacheConfig{ - Client: clienToUse, - BaseContext: ctx, - Proxier: apiProxy, - Logger: cacheLogger.Named("leasecache"), - UserAgentToUse: "test", + Client: clienToUse, + BaseContext: ctx, + Proxier: apiProxy, + Logger: cacheLogger.Named("leasecache"), + CacheDynamicSecrets: true, + UserAgentToUse: "test", }) if err != nil { t.Fatal(err) diff --git a/command/agentproxyshared/cache/lease_cache.go b/command/agentproxyshared/cache/lease_cache.go index 8182e074d5..1b6dcc1e11 100644 --- a/command/agentproxyshared/cache/lease_cache.go +++ b/command/agentproxyshared/cache/lease_cache.go @@ -106,6 +106,10 @@ type LeaseCache struct { // cache static secrets, as well as dynamic secrets. cacheStaticSecrets bool + // cacheDynamicSecrets is used to determine if the cache should + // cache dynamic secrets + cacheDynamicSecrets bool + // capabilityManager is used when static secrets are enabled to // manage the capabilities of cached tokens. capabilityManager *StaticSecretCapabilityManager @@ -114,13 +118,14 @@ type LeaseCache struct { // LeaseCacheConfig is the configuration for initializing a new // LeaseCache. type LeaseCacheConfig struct { - Client *api.Client - BaseContext context.Context - Proxier Proxier - Logger hclog.Logger - UserAgentToUse string - Storage *cacheboltdb.BoltStorage - CacheStaticSecrets bool + Client *api.Client + BaseContext context.Context + Proxier Proxier + Logger hclog.Logger + UserAgentToUse string + Storage *cacheboltdb.BoltStorage + CacheStaticSecrets bool + CacheDynamicSecrets bool } type inflightRequest struct { @@ -167,17 +172,18 @@ func NewLeaseCache(conf *LeaseCacheConfig) (*LeaseCache, error) { baseCtxInfo := cachememdb.NewContextInfo(conf.BaseContext) return &LeaseCache{ - client: conf.Client, - proxier: conf.Proxier, - logger: conf.Logger, - userAgentToUse: conf.UserAgentToUse, - db: db, - baseCtxInfo: baseCtxInfo, - l: &sync.RWMutex{}, - idLocks: locksutil.CreateLocks(), - inflightCache: gocache.New(gocache.NoExpiration, gocache.NoExpiration), - ps: conf.Storage, - cacheStaticSecrets: conf.CacheStaticSecrets, + client: conf.Client, + proxier: conf.Proxier, + logger: conf.Logger, + userAgentToUse: conf.UserAgentToUse, + db: db, + baseCtxInfo: baseCtxInfo, + l: &sync.RWMutex{}, + idLocks: locksutil.CreateLocks(), + inflightCache: gocache.New(gocache.NoExpiration, gocache.NoExpiration), + ps: conf.Storage, + cacheStaticSecrets: conf.CacheStaticSecrets, + cacheDynamicSecrets: conf.CacheDynamicSecrets, }, nil } @@ -486,6 +492,11 @@ func (c *LeaseCache) Send(ctx context.Context, req *SendRequest) (*SendResponse, index.ID = dynamicSecretCacheId } + // Short-circuit if we've been configured to not cache dynamic secrets + if !c.cacheDynamicSecrets { + return resp, nil + } + // Short-circuit if the secret is not renewable tokenRenewable, err := secret.TokenIsRenewable() if err != nil { diff --git a/command/agentproxyshared/cache/lease_cache_test.go b/command/agentproxyshared/cache/lease_cache_test.go index 45972b6918..d3dd599aad 100644 --- a/command/agentproxyshared/cache/lease_cache_test.go +++ b/command/agentproxyshared/cache/lease_cache_test.go @@ -43,12 +43,13 @@ func testNewLeaseCache(t *testing.T, responses []*SendResponse) *LeaseCache { t.Fatal(err) } lc, err := NewLeaseCache(&LeaseCacheConfig{ - Client: client, - BaseContext: context.Background(), - Proxier: NewMockProxier(responses), - Logger: logging.NewVaultLogger(hclog.Trace).Named("cache.leasecache"), - CacheStaticSecrets: true, - UserAgentToUse: "test", + Client: client, + BaseContext: context.Background(), + Proxier: NewMockProxier(responses), + Logger: logging.NewVaultLogger(hclog.Trace).Named("cache.leasecache"), + CacheStaticSecrets: true, + CacheDynamicSecrets: true, + UserAgentToUse: "test", }) if err != nil { t.Fatal(err) @@ -65,12 +66,13 @@ func testNewLeaseCacheWithDelay(t *testing.T, cacheable bool, delay int) *LeaseC } lc, err := NewLeaseCache(&LeaseCacheConfig{ - Client: client, - BaseContext: context.Background(), - Proxier: &mockDelayProxier{cacheable, delay}, - Logger: logging.NewVaultLogger(hclog.Trace).Named("cache.leasecache"), - CacheStaticSecrets: true, - UserAgentToUse: "test", + Client: client, + BaseContext: context.Background(), + Proxier: &mockDelayProxier{cacheable, delay}, + Logger: logging.NewVaultLogger(hclog.Trace).Named("cache.leasecache"), + CacheStaticSecrets: true, + CacheDynamicSecrets: true, + UserAgentToUse: "test", }) if err != nil { t.Fatal(err) @@ -86,13 +88,14 @@ func testNewLeaseCacheWithPersistence(t *testing.T, responses []*SendResponse, s require.NoError(t, err) lc, err := NewLeaseCache(&LeaseCacheConfig{ - Client: client, - BaseContext: context.Background(), - Proxier: NewMockProxier(responses), - Logger: logging.NewVaultLogger(hclog.Trace).Named("cache.leasecache"), - Storage: storage, - CacheStaticSecrets: true, - UserAgentToUse: "test", + Client: client, + BaseContext: context.Background(), + Proxier: NewMockProxier(responses), + Logger: logging.NewVaultLogger(hclog.Trace).Named("cache.leasecache"), + Storage: storage, + CacheStaticSecrets: true, + CacheDynamicSecrets: true, + UserAgentToUse: "test", }) require.NoError(t, err) diff --git a/command/agentproxyshared/helpers_test.go b/command/agentproxyshared/helpers_test.go index 9838497111..e5f1d6007c 100644 --- a/command/agentproxyshared/helpers_test.go +++ b/command/agentproxyshared/helpers_test.go @@ -22,11 +22,12 @@ func testNewLeaseCache(t *testing.T, responses []*cache.SendResponse) *cache.Lea t.Fatal(err) } lc, err := cache.NewLeaseCache(&cache.LeaseCacheConfig{ - Client: client, - BaseContext: context.Background(), - Proxier: cache.NewMockProxier(responses), - Logger: logging.NewVaultLogger(hclog.Trace).Named("cache.leasecache"), - UserAgentToUse: "test", + Client: client, + BaseContext: context.Background(), + Proxier: cache.NewMockProxier(responses), + Logger: logging.NewVaultLogger(hclog.Trace).Named("cache.leasecache"), + CacheDynamicSecrets: true, + UserAgentToUse: "test", }) if err != nil { t.Fatal(err) diff --git a/command/proxy.go b/command/proxy.go index 890eee6cfc..55dc555fb2 100644 --- a/command/proxy.go +++ b/command/proxy.go @@ -447,13 +447,17 @@ func (c *ProxyCommand) Run(args []string) int { Proxier: apiProxy, Logger: cacheLogger.Named("leasecache"), CacheStaticSecrets: config.Cache.CacheStaticSecrets, - UserAgentToUse: useragent.AgentProxyString(), + // dynamic secrets are configured as default-on to preserve backwards compatibility + CacheDynamicSecrets: !config.Cache.DisableCachingDynamicSecrets, + UserAgentToUse: useragent.AgentProxyString(), }) if err != nil { c.UI.Error(fmt.Sprintf("Error creating lease cache: %v", err)) return 1 } + cacheLogger.Info("cache configured", "cache_static_secrets", config.Cache.CacheStaticSecrets, "disable_caching_dynamic_secrets", config.Cache.DisableCachingDynamicSecrets) + // Configure persistent storage and add to LeaseCache if config.Cache.Persist != nil { deferFunc, oldToken, err := agentproxyshared.AddPersistentStorageToLeaseCache(ctx, leaseCache, config.Cache.Persist, cacheLogger) diff --git a/command/proxy/config/config.go b/command/proxy/config/config.go index 2103fb11ee..a67e00bb36 100644 --- a/command/proxy/config/config.go +++ b/command/proxy/config/config.go @@ -104,6 +104,7 @@ type Cache struct { Persist *agentproxyshared.PersistConfig `hcl:"persist"` InProcDialer transportDialer `hcl:"-"` CacheStaticSecrets bool `hcl:"cache_static_secrets"` + DisableCachingDynamicSecrets bool `hcl:"disable_caching_dynamic_secrets"` StaticSecretTokenCapabilityRefreshIntervalRaw interface{} `hcl:"static_secret_token_capability_refresh_interval"` StaticSecretTokenCapabilityRefreshInterval time.Duration `hcl:"-"` } @@ -260,6 +261,10 @@ func (c *Config) ValidateConfig() error { return fmt.Errorf("cache.cache_static_secrets=true requires an auto-auth block configured, to use the token to connect with Vault's event system") } + if c.Cache != nil && !c.Cache.CacheStaticSecrets && c.Cache.DisableCachingDynamicSecrets { + return fmt.Errorf("to enable the cache, the cache must be configured to either cache static secrets or dynamic secrets") + } + if c.AutoAuth == nil && c.Cache == nil && len(c.Listeners) == 0 { return fmt.Errorf("no auto_auth, cache, or listener block found in config") } diff --git a/command/proxy/config/config_test.go b/command/proxy/config/config_test.go index 114436eb6f..e0afc50de5 100644 --- a/command/proxy/config/config_test.go +++ b/command/proxy/config/config_test.go @@ -119,6 +119,19 @@ func TestLoadConfigFile_ProxyCache(t *testing.T) { } } +// TestLoadConfigFile_NoCachingEnabled tests that you cannot enable a cache +// without either of the options to enable caching secrets +func TestLoadConfigFile_NoCachingEnabled(t *testing.T) { + cfg, err := LoadConfigFile("./test-fixtures/config-cache-but-no-secrets.hcl") + if err != nil { + t.Fatal(err) + } + + if err := cfg.ValidateConfig(); err == nil { + t.Fatalf("expected error, as you cannot configure a cache without caching secrets") + } +} + // TestLoadConfigFile_StaticSecretCachingWithoutAutoAuth tests that loading // a config file with static secret caching enabled but no auto auth will fail. func TestLoadConfigFile_StaticSecretCachingWithoutAutoAuth(t *testing.T) { diff --git a/command/proxy/config/test-fixtures/config-cache-but-no-secrets.hcl b/command/proxy/config/test-fixtures/config-cache-but-no-secrets.hcl new file mode 100644 index 0000000000..edd8e6a2a5 --- /dev/null +++ b/command/proxy/config/test-fixtures/config-cache-but-no-secrets.hcl @@ -0,0 +1,19 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: BUSL-1.1 + +pid_file = "./pidfile" + +cache { + cache_static_secrets = false + disable_caching_dynamic_secrets = true +} + +listener "tcp" { + address = "127.0.0.1:8300" + tls_disable = true +} + +vault { + address = "http://127.0.0.1:1111" + tls_skip_verify = "true" +} diff --git a/command/proxy_test.go b/command/proxy_test.go index e1daac1ad9..ef856e9127 100644 --- a/command/proxy_test.go +++ b/command/proxy_test.go @@ -686,6 +686,130 @@ vault { wg.Wait() } +// TestProxy_Cache_DisableDynamicSecretCaching tests that the cache will not cache a dynamic secret +// if disabled in the options. +func TestProxy_Cache_DisableDynamicSecretCaching(t *testing.T) { + logger := logging.NewVaultLogger(hclog.Trace) + cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{ + HandlerFunc: vaulthttp.Handler, + }) + cluster.Start() + defer cluster.Cleanup() + + serverClient := cluster.Cores[0].Client + + tokenFileName := makeTempFile(t, "token-file", serverClient.Token()) + defer os.Remove(tokenFileName) + // We need auto-auth for static secret caching. + // For ease, we use the token file path with the root token. + autoAuthConfig := fmt.Sprintf(` +auto_auth { + method { + type = "token_file" + config = { + token_file_path = "%s" + } + } +}`, tokenFileName) + + // Unset the environment variable so that proxy picks up the right test + // cluster address + defer os.Setenv(api.EnvVaultAddress, os.Getenv(api.EnvVaultAddress)) + os.Unsetenv(api.EnvVaultAddress) + + cacheConfig := ` +cache { + disable_caching_dynamic_secrets = true + cache_static_secrets = true // We need to cache at least one kind of secret +} +` + listenAddr := generateListenerAddress(t) + listenConfig := fmt.Sprintf(` +listener "tcp" { + address = "%s" + tls_disable = true +} +`, listenAddr) + + config := fmt.Sprintf(` +vault { + address = "%s" + tls_skip_verify = true +} +%s +%s +%s +`, serverClient.Address(), cacheConfig, listenConfig, autoAuthConfig) + configPath := makeTempFile(t, "config.hcl", config) + defer os.Remove(configPath) + + // Start proxy + _, cmd := testProxyCommand(t, logger) + cmd.startedCh = make(chan struct{}) + + wg := &sync.WaitGroup{} + wg.Add(1) + go func() { + cmd.Run([]string{"-config", configPath}) + wg.Done() + }() + + select { + case <-cmd.startedCh: + case <-time.After(5 * time.Second): + t.Errorf("timeout") + } + + proxyClient, err := api.NewClient(api.DefaultConfig()) + if err != nil { + t.Fatal(err) + } + proxyClient.SetToken(serverClient.Token()) + proxyClient.SetMaxRetries(0) + err = proxyClient.SetAddress("http://" + listenAddr) + if err != nil { + t.Fatal(err) + } + + renewable := true + tokenCreateRequest := &api.TokenCreateRequest{ + Policies: []string{"default"}, + TTL: "30m", + Renewable: &renewable, + } + + // This was the simplest test I could find to trigger the caching behaviour, + // i.e. the most concise I could make the test that I can tell + // creating an orphan token returns Auth, is renewable, and isn't a token + // that's managed elsewhere (since it's an orphan) + secret, err := proxyClient.Auth().Token().CreateOrphan(tokenCreateRequest) + if err != nil { + t.Fatal(err) + } + if secret == nil || secret.Auth == nil { + t.Fatalf("secret not as expected: %v", secret) + } + + token := secret.Auth.ClientToken + + secret, err = proxyClient.Auth().Token().CreateOrphan(tokenCreateRequest) + if err != nil { + t.Fatal(err) + } + if secret == nil || secret.Auth == nil { + t.Fatalf("secret not as expected: %v", secret) + } + + token2 := secret.Auth.ClientToken + + if token == token2 { + t.Fatalf("token create response was cached, as the tokens differ") + } + + close(cmd.ShutdownCh) + wg.Wait() +} + // TestProxy_Cache_StaticSecret Tests that the cache successfully caches a static secret // going through the Proxy, func TestProxy_Cache_StaticSecret(t *testing.T) {