VAULT-19239 Add capability to disable dynamic secret caching for Vault Proxy (#23801)

* VAULT-19239 create disable static secret caching config

* VAULT-19239 missed file

* VAULT-19239 didn't finish a log line

* VAULT-19239 adjust test to use new option

* Fix typo

Co-authored-by: Kuba Wieczorek <kuba.wieczorek@hashicorp.com>

---------

Co-authored-by: Kuba Wieczorek <kuba.wieczorek@hashicorp.com>
This commit is contained in:
Violet Hynes 2023-11-15 09:11:10 -05:00 committed by GitHub
parent 22a2e74fcc
commit 3e054cbd4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 247 additions and 65 deletions

View File

@ -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))

View File

@ -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 {

View File

@ -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)

View File

@ -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 {

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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")
}

View File

@ -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) {

View File

@ -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"
}

View File

@ -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) {