From ccc2e1b391f4dfb042b856ad9c2a975ca13a5100 Mon Sep 17 00:00:00 2001 From: Violet Hynes Date: Mon, 26 Feb 2024 14:41:27 -0500 Subject: [PATCH] VAULT-24386 CE change portion of moving proxy static secret caching to enterprise (#25641) --- .../cache/static_secret_cache_updater_test.go | 22 +- command/proxy_test.go | 1116 ----------------- 2 files changed, 19 insertions(+), 1119 deletions(-) diff --git a/command/agentproxyshared/cache/static_secret_cache_updater_test.go b/command/agentproxyshared/cache/static_secret_cache_updater_test.go index d3a56fe169..8824df1d95 100644 --- a/command/agentproxyshared/cache/static_secret_cache_updater_test.go +++ b/command/agentproxyshared/cache/static_secret_cache_updater_test.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/vault/command/agentproxyshared/cache/cacheboltdb" "github.com/hashicorp/vault/command/agentproxyshared/cache/cachememdb" "github.com/hashicorp/vault/command/agentproxyshared/sink" + "github.com/hashicorp/vault/helper/constants" "github.com/hashicorp/vault/helper/testhelpers/minimal" vaulthttp "github.com/hashicorp/vault/http" "github.com/hashicorp/vault/sdk/helper/logging" @@ -136,6 +137,9 @@ func TestNewStaticSecretCacheUpdater(t *testing.T) { // TestOpenWebSocketConnection tests that the openWebSocketConnection function // works as expected. This uses a TLS enabled (wss) WebSocket connection. func TestOpenWebSocketConnection(t *testing.T) { + if !constants.IsEnterprise { + t.Skip("test can only run on enterprise due to requiring the event notification system") + } t.Parallel() // We need a valid cluster for the connection to succeed. cluster := minimal.NewTestSoloCluster(t, nil) @@ -155,11 +159,11 @@ func TestOpenWebSocketConnection(t *testing.T) { // works as expected with the default KVV1 mount, and then the connection can be used to receive an event. // This acts as more of an event system sanity check than a test of the updater // logic. It's still important coverage, though. -// As of right now, it does not pass since the default kv mount is LeasedPassthroughBackend. -// If that is changed, this test will be unskipped. func TestOpenWebSocketConnectionReceivesEventsDefaultMount(t *testing.T) { + if !constants.IsEnterprise { + t.Skip("test can only run on enterprise due to requiring the event notification system") + } t.Parallel() - t.Skip("This test won't finish, as the default KV mount is LeasedPassthroughBackend in tests, and therefore does not send events") // We need a valid cluster for the connection to succeed. cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{ HandlerFunc: vaulthttp.Handler, @@ -211,6 +215,9 @@ func TestOpenWebSocketConnectionReceivesEventsDefaultMount(t *testing.T) { // This acts as more of an event system sanity check than a test of the updater // logic. It's still important coverage, though. func TestOpenWebSocketConnectionReceivesEventsKVV1(t *testing.T) { + if !constants.IsEnterprise { + t.Skip("test can only run on enterprise due to requiring the event notification system") + } t.Parallel() // We need a valid cluster for the connection to succeed. cluster := vault.NewTestCluster(t, &vault.CoreConfig{ @@ -273,6 +280,9 @@ func TestOpenWebSocketConnectionReceivesEventsKVV1(t *testing.T) { // This acts as more of an event system sanity check than a test of the updater // logic. It's still important coverage, though. func TestOpenWebSocketConnectionReceivesEventsKVV2(t *testing.T) { + if !constants.IsEnterprise { + t.Skip("test can only run on enterprise due to requiring the event notification system") + } t.Parallel() // We need a valid cluster for the connection to succeed. cluster := vault.NewTestCluster(t, &vault.CoreConfig{ @@ -335,6 +345,9 @@ func TestOpenWebSocketConnectionReceivesEventsKVV2(t *testing.T) { // works as expected using vaulthttp.TestServer. This server isn't TLS enabled, so tests // the ws path (as opposed to the wss) path. func TestOpenWebSocketConnectionTestServer(t *testing.T) { + if !constants.IsEnterprise { + t.Skip("test can only run on enterprise due to requiring the event notification system") + } t.Parallel() // We need a valid cluster for the connection to succeed. core := vault.TestCoreWithConfig(t, &vault.CoreConfig{}) @@ -371,6 +384,9 @@ func TestOpenWebSocketConnectionTestServer(t *testing.T) { // ensuring that updateStaticSecret gets called by the event arriving // (as part of streamStaticSecretEvents) instead of testing calling it explicitly. func Test_StreamStaticSecretEvents_UpdatesCacheWithNewSecrets(t *testing.T) { + if !constants.IsEnterprise { + t.Skip("test can only run on enterprise due to requiring the event notification system") + } t.Parallel() cluster := vault.NewTestCluster(t, &vault.CoreConfig{ LogicalBackends: map[string]logical.Factory{ diff --git a/command/proxy_test.go b/command/proxy_test.go index 4e95becc88..cffc93b750 100644 --- a/command/proxy_test.go +++ b/command/proxy_test.go @@ -4,7 +4,6 @@ package command import ( - "context" "crypto/tls" "crypto/x509" "fmt" @@ -686,1121 +685,6 @@ 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) { - 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 - - // 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) - - tokenFileName := makeTempFile(t, "token-file", serverClient.Token()) - defer os.Remove(tokenFileName) - // We need auto-auth so that the event system can run. - // 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) - - cacheConfig := ` -cache { - cache_static_secrets = true -} -` - 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 -log_level = "trace" -`, serverClient.Address(), cacheConfig, listenConfig, autoAuthConfig) - configPath := makeTempFile(t, "config.hcl", config) - defer os.Remove(configPath) - - // Start proxy - ui, 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") - t.Errorf("stdout: %s", ui.OutputWriter.String()) - t.Errorf("stderr: %s", ui.ErrorWriter.String()) - } - - 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) - } - - secretData := map[string]interface{}{ - "foo": "bar", - } - - // Create kvv1 secret - err = serverClient.KVv1("secret").Put(context.Background(), "my-secret", secretData) - if err != nil { - t.Fatal(err) - } - - // We use raw requests so we can check the headers for cache hit/miss. - // We expect the first to miss, and the second to hit. - req := proxyClient.NewRequest(http.MethodGet, "/v1/secret/my-secret") - resp1, err := proxyClient.RawRequest(req) - if err != nil { - t.Fatal(err) - } - - cacheValue := resp1.Header.Get("X-Cache") - require.Equal(t, "MISS", cacheValue) - - req = proxyClient.NewRequest(http.MethodGet, "/v1/secret/my-secret") - resp2, err := proxyClient.RawRequest(req) - if err != nil { - t.Fatal(err) - } - - cacheValue = resp2.Header.Get("X-Cache") - require.Equal(t, "HIT", cacheValue) - - // Lastly, we check to make sure the actual data we received is - // as we expect. We must use ParseSecret due to the raw requests. - secret1, err := api.ParseSecret(resp1.Body) - if err != nil { - t.Fatal(err) - } - require.Equal(t, secretData, secret1.Data) - - secret2, err := api.ParseSecret(resp2.Body) - if err != nil { - t.Fatal(err) - } - require.Equal(t, secret1.Data, secret2.Data) - - close(cmd.ShutdownCh) - wg.Wait() -} - -// TestProxy_Cache_EventSystemUpdatesCacheKVV1 Tests that the cache successfully caches a static secret -// going through the Proxy, and then the cache gets updated on a POST to the KVV1 secret due to an -// event. -func TestProxy_Cache_EventSystemUpdatesCacheKVV1(t *testing.T) { - logger := logging.NewVaultLogger(hclog.Trace) - cluster := vault.NewTestCluster(t, &vault.CoreConfig{ - LogicalBackends: map[string]logical.Factory{ - "kv": logicalKv.Factory, - }, - }, &vault.TestClusterOptions{ - HandlerFunc: vaulthttp.Handler, - }) - - serverClient := cluster.Cores[0].Client - - // 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) - - tokenFileName := makeTempFile(t, "token-file", serverClient.Token()) - defer os.Remove(tokenFileName) - // We need auto-auth so that the event system can run. - // 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) - - cacheConfig := ` -cache { - cache_static_secrets = true -} -` - 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 -log_level = "trace" -`, serverClient.Address(), cacheConfig, listenConfig, autoAuthConfig) - configPath := makeTempFile(t, "config.hcl", config) - defer os.Remove(configPath) - - // Start proxy - ui, 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") - t.Errorf("stdout: %s", ui.OutputWriter.String()) - t.Errorf("stderr: %s", ui.ErrorWriter.String()) - } - - 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) - } - - secretData := map[string]interface{}{ - "foo": "bar", - } - - secretData2 := map[string]interface{}{ - "bar": "baz", - } - - // Wait for the event system to successfully connect. - // The test would pass without this time.Sleep, due to the call to updater.preEventStreamUpdate - // but this Sleep ensures we test both paths (both updating from an event, and from - // the pre event update). - // As a result, we shouldn't remove this sleep, since it ensures we have greater coverage. - time.Sleep(5 * time.Second) - - // Mount the KVV2 engine - err = serverClient.Sys().Mount("secret-v1", &api.MountInput{ - Type: "kv", - }) - if err != nil { - t.Fatal(err) - } - - // Create kvv1 secret - err = serverClient.KVv1("secret-v1").Put(context.Background(), "my-secret", secretData) - if err != nil { - t.Fatal(err) - } - - // We use raw requests so we can check the headers for cache hit/miss. - req := proxyClient.NewRequest(http.MethodGet, "/v1/secret-v1/my-secret") - resp1, err := proxyClient.RawRequest(req) - if err != nil { - t.Fatal(err) - } - - cacheValue := resp1.Header.Get("X-Cache") - require.Equal(t, "MISS", cacheValue) - - // Update the secret using the proxy client - err = proxyClient.KVv1("secret-v1").Put(context.Background(), "my-secret", secretData2) - if err != nil { - t.Fatal(err) - } - - // Give some time for the event to actually get sent and the cache to be updated. - // This is longer than it needs to be to account for unnatural slowness/avoiding - // flakiness. - time.Sleep(5 * time.Second) - - // We expect this to be a cache hit, with the new value - resp2, err := proxyClient.RawRequest(req) - if err != nil { - t.Fatal(err) - } - - cacheValue = resp2.Header.Get("X-Cache") - require.Equal(t, "HIT", cacheValue) - - // Lastly, we check to make sure the actual data we received is - // as we expect. We must use ParseSecret due to the raw requests. - secret1, err := api.ParseSecret(resp1.Body) - if err != nil { - t.Fatal(err) - } - require.Equal(t, secretData, secret1.Data) - - secret2, err := api.ParseSecret(resp2.Body) - if err != nil { - t.Fatal(err) - } - require.Equal(t, secretData2, secret2.Data) - - close(cmd.ShutdownCh) - wg.Wait() -} - -// TestProxy_Cache_EventSystemUpdatesCacheKVV2 Tests that the cache successfully caches a static secret -// going through the Proxy for a KVV2 secret, and then the cache gets updated on a POST to the secret due to an -// event. -func TestProxy_Cache_EventSystemUpdatesCacheKVV2(t *testing.T) { - logger := logging.NewVaultLogger(hclog.Trace) - cluster := vault.NewTestCluster(t, &vault.CoreConfig{ - LogicalBackends: map[string]logical.Factory{ - "kv": logicalKv.VersionedKVFactory, - }, - }, &vault.TestClusterOptions{ - HandlerFunc: vaulthttp.Handler, - }) - - serverClient := cluster.Cores[0].Client - - // 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) - - tokenFileName := makeTempFile(t, "token-file", serverClient.Token()) - defer os.Remove(tokenFileName) - // We need auto-auth so that the event system can run. - // 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) - - cacheConfig := ` -cache { - cache_static_secrets = true -} -` - 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 -log_level = "trace" -`, serverClient.Address(), cacheConfig, listenConfig, autoAuthConfig) - configPath := makeTempFile(t, "config.hcl", config) - defer os.Remove(configPath) - - // Start proxy - ui, 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") - t.Errorf("stdout: %s", ui.OutputWriter.String()) - t.Errorf("stderr: %s", ui.ErrorWriter.String()) - } - - 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) - } - - secretData := map[string]interface{}{ - "foo": "bar", - } - - secretData2 := map[string]interface{}{ - "bar": "baz", - } - - // Wait for the event system to successfully connect. - // This is longer than it needs to be to account for unnatural slowness/avoiding - // flakiness. - time.Sleep(5 * time.Second) - - // Mount the KVV2 engine - err = serverClient.Sys().Mount("secret-v2", &api.MountInput{ - Type: "kv-v2", - }) - if err != nil { - t.Fatal(err) - } - - // Create kvv2 secret - _, err = serverClient.KVv2("secret-v2").Put(context.Background(), "my-secret", secretData) - if err != nil { - t.Fatal(err) - } - - // We use raw requests so we can check the headers for cache hit/miss. - req := proxyClient.NewRequest(http.MethodGet, "/v1/secret-v2/data/my-secret") - resp1, err := proxyClient.RawRequest(req) - if err != nil { - t.Fatal(err) - } - - cacheValue := resp1.Header.Get("X-Cache") - require.Equal(t, "MISS", cacheValue) - - // Update the secret using the proxy client - _, err = proxyClient.KVv2("secret-v2").Put(context.Background(), "my-secret", secretData2) - if err != nil { - t.Fatal(err) - } - - // Give some time for the event to actually get sent and the cache to be updated. - // This is longer than it needs to be to account for unnatural slowness/avoiding - // flakiness. - time.Sleep(5 * time.Second) - - // We expect this to be a cache hit, with the new value - resp2, err := proxyClient.RawRequest(req) - if err != nil { - t.Fatal(err) - } - - cacheValue = resp2.Header.Get("X-Cache") - require.Equal(t, "HIT", cacheValue) - - // Lastly, we check to make sure the actual data we received is - // as we expect. We must use ParseSecret due to the raw requests. - secret1, err := api.ParseSecret(resp1.Body) - if err != nil { - t.Fatal(err) - } - data, ok := secret1.Data["data"] - require.True(t, ok) - require.Equal(t, secretData, data) - - secret2, err := api.ParseSecret(resp2.Body) - if err != nil { - t.Fatal(err) - } - data2, ok := secret2.Data["data"] - require.True(t, ok) - // We expect that the cached value got updated by the event system. - require.Equal(t, secretData2, data2) - - // Lastly, ensure that a client without a token fails to access the secret. - proxyClient.SetToken("") - req = proxyClient.NewRequest(http.MethodGet, "/v1/secret-v2/data/my-secret") - _, err = proxyClient.RawRequest(req) - require.NotNil(t, err) - - _, err = proxyClient.KVv2("secret-v2").Get(context.Background(), "my-secret") - require.NotNil(t, err) - - close(cmd.ShutdownCh) - wg.Wait() -} - -// TestProxy_Cache_EventSystemUpdatesCacheUseAutoAuthToken Tests that the cache successfully caches a static secret -// going through the Proxy for a KVV2 secret, and that the cache works as expected with the -// use_auto_auth_token=force option. -func TestProxy_Cache_EventSystemUpdatesCacheUseAutoAuthToken(t *testing.T) { - logger := logging.NewVaultLogger(hclog.Trace) - cluster := vault.NewTestCluster(t, &vault.CoreConfig{ - LogicalBackends: map[string]logical.Factory{ - "kv": logicalKv.VersionedKVFactory, - }, - }, &vault.TestClusterOptions{ - HandlerFunc: vaulthttp.Handler, - }) - - serverClient := cluster.Cores[0].Client - - // 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) - - tokenFileName := makeTempFile(t, "token-file", serverClient.Token()) - defer os.Remove(tokenFileName) - // We need auto-auth so that the event system can run. - // 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) - - cacheConfig := ` -cache { - cache_static_secrets = true -} -` - 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 - -api_proxy { - use_auto_auth_token = "force" -} - -log_level = "trace" -`, serverClient.Address(), cacheConfig, listenConfig, autoAuthConfig) - configPath := makeTempFile(t, "config.hcl", config) - defer os.Remove(configPath) - - // Start proxy - ui, 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") - t.Errorf("stdout: %s", ui.OutputWriter.String()) - t.Errorf("stderr: %s", ui.ErrorWriter.String()) - } - - 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) - } - - secretData := map[string]interface{}{ - "foo": "bar", - } - - secretData2 := map[string]interface{}{ - "bar": "baz", - } - - // Wait for the event system to successfully connect. - // This is longer than it needs to be to account for unnatural slowness/avoiding - // flakiness. - time.Sleep(5 * time.Second) - - // Mount the KVV2 engine - err = serverClient.Sys().Mount("secret-v2", &api.MountInput{ - Type: "kv-v2", - }) - if err != nil { - t.Fatal(err) - } - - // Create kvv2 secret - _, err = serverClient.KVv2("secret-v2").Put(context.Background(), "my-secret", secretData) - if err != nil { - t.Fatal(err) - } - - // We use raw requests so we can check the headers for cache hit/miss. - req := proxyClient.NewRequest(http.MethodGet, "/v1/secret-v2/data/my-secret") - resp1, err := proxyClient.RawRequest(req) - if err != nil { - t.Fatal(err) - } - - cacheValue := resp1.Header.Get("X-Cache") - require.Equal(t, "MISS", cacheValue) - - // Update the secret using the proxy client - _, err = proxyClient.KVv2("secret-v2").Put(context.Background(), "my-secret", secretData2) - if err != nil { - t.Fatal(err) - } - - // Give some time for the event to actually get sent and the cache to be updated. - // This is longer than it needs to be to account for unnatural slowness/avoiding - // flakiness. - time.Sleep(5 * time.Second) - - // We expect this to be a cache hit, with the new value - resp2, err := proxyClient.RawRequest(req) - if err != nil { - t.Fatal(err) - } - - cacheValue = resp2.Header.Get("X-Cache") - require.Equal(t, "HIT", cacheValue) - - // Lastly, we check to make sure the actual data we received is - // as we expect. We must use ParseSecret due to the raw requests. - secret1, err := api.ParseSecret(resp1.Body) - require.Nil(t, err) - data, ok := secret1.Data["data"] - require.True(t, ok) - require.Equal(t, secretData, data) - - secret2, err := api.ParseSecret(resp2.Body) - require.Nil(t, err) - data2, ok := secret2.Data["data"] - require.True(t, ok) - // We expect that the cached value got updated by the event system. - require.Equal(t, secretData2, data2) - - // Lastly, ensure that a client without a token succeeds - // at accessing the secret, due to the use_auto_auth_token = "force" - // option. - proxyClient.SetToken("") - req = proxyClient.NewRequest(http.MethodGet, "/v1/secret-v2/data/my-secret") - resp3, err := proxyClient.RawRequest(req) - require.Nil(t, err) - cacheValue = resp3.Header.Get("X-Cache") - require.Equal(t, "HIT", cacheValue) - - secret3, err := api.ParseSecret(resp3.Body) - require.Nil(t, err) - data3, ok := secret3.Data["data"] - require.True(t, ok) - // We expect that the cached value got updated by the event system. - require.Equal(t, secretData2, data3) - - close(cmd.ShutdownCh) - wg.Wait() -} - -// TestProxy_Cache_EventSystemPreEventStreamUpdateWorks Tests that the pre-event stream update works -// (i.e. the method preEventStreamUpdate). This test is similar to TestProxy_Cache_EventSystemUpdatesCacheKVV2, -// but with the key difference of not waiting the five seconds for the event subsystem of Proxy to get running -// before it updates the secret, meaning that the event system should update it from the pre-event stream -// update as opposed to receiving the event. -func TestProxy_Cache_EventSystemPreEventStreamUpdateWorks(t *testing.T) { - logger := logging.NewVaultLogger(hclog.Trace) - cluster := minimal.NewTestSoloCluster(t, &vault.CoreConfig{ - LogicalBackends: map[string]logical.Factory{ - "kv": logicalKv.VersionedKVFactory, - }, - }) - - serverClient := cluster.Cores[0].Client - - // 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) - - tokenFileName := makeTempFile(t, "token-file", serverClient.Token()) - defer os.Remove(tokenFileName) - // We need auto-auth so that the event system can run. - // 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) - - cacheConfig := ` -cache { - cache_static_secrets = true -} -` - 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 -log_level = "trace" -`, serverClient.Address(), cacheConfig, listenConfig, autoAuthConfig) - configPath := makeTempFile(t, "config.hcl", config) - defer os.Remove(configPath) - - // Start proxy - ui, 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") - t.Errorf("stdout: %s", ui.OutputWriter.String()) - t.Errorf("stderr: %s", ui.ErrorWriter.String()) - } - - proxyClient, err := api.NewClient(api.DefaultConfig()) - if err != nil { - t.Fatal(err) - } - proxyClient.SetToken(serverClient.Token()) - proxyClient.SetMaxRetries(0) - err = proxyClient.SetAddress("http://" + listenAddr) - require.NoError(t, err) - - secretData := map[string]interface{}{ - "foo": "bar", - } - - secretData2 := map[string]interface{}{ - "bar": "baz", - } - - // Mount the KVV2 engine - err = serverClient.Sys().Mount("secret-v2", &api.MountInput{ - Type: "kv-v2", - }) - require.NoError(t, err) - - // Create kvv2 secret - _, err = serverClient.KVv2("secret-v2").Put(context.Background(), "my-secret", secretData) - require.NoError(t, err) - - // We use raw requests so we can check the headers for cache hit/miss. - req := proxyClient.NewRequest(http.MethodGet, "/v1/secret-v2/data/my-secret") - resp1, err := proxyClient.RawRequest(req) - require.NoError(t, err) - - cacheValue := resp1.Header.Get("X-Cache") - require.Equal(t, "MISS", cacheValue) - - // Update the secret using the proxy client - _, err = proxyClient.KVv2("secret-v2").Put(context.Background(), "my-secret", secretData2) - require.NoError(t, err) - - // Give some time for the event system to run and update the secret as part of the - // pre-event stream run. Likely, this will be the period in which the event subsystem - // of proxy actually starts up, so it will have missed the event, but this should still - // result in an updated cache. - time.Sleep(5 * time.Second) - - // We expect this to be a cache hit, with the new value - resp2, err := proxyClient.RawRequest(req) - require.NoError(t, err) - - cacheValue = resp2.Header.Get("X-Cache") - require.Equal(t, "HIT", cacheValue) - - // Lastly, we check to make sure the actual data we received is - // as we expect. We must use ParseSecret due to the raw requests. - secret1, err := api.ParseSecret(resp1.Body) - require.NoError(t, err) - data, ok := secret1.Data["data"] - require.True(t, ok) - require.Equal(t, secretData, data) - - secret2, err := api.ParseSecret(resp2.Body) - require.NoError(t, err) - data2, ok := secret2.Data["data"] - require.True(t, ok) - // We expect that the cached value got updated by the event system. - require.Equal(t, secretData2, data2) - - close(cmd.ShutdownCh) - wg.Wait() -} - -// TestProxy_Cache_StaticSecretPermissionsLost Tests that the cache successfully caches a static secret -// going through the Proxy for a KVV2 secret, and then the calling client loses permissions to the secret, -// so it can no longer access the cache. -func TestProxy_Cache_StaticSecretPermissionsLost(t *testing.T) { - logger := logging.NewVaultLogger(hclog.Trace) - cluster := vault.NewTestCluster(t, &vault.CoreConfig{ - LogicalBackends: map[string]logical.Factory{ - "kv": logicalKv.VersionedKVFactory, - }, - }, &vault.TestClusterOptions{ - HandlerFunc: vaulthttp.Handler, - }) - - serverClient := cluster.Cores[0].Client - - // 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) - - tokenFileName := makeTempFile(t, "token-file", serverClient.Token()) - defer os.Remove(tokenFileName) - // We need auto-auth so that the event system can run. - // 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) - - // We make the token capability refresh interval one second, for ease of testing - cacheConfig := ` -cache { - cache_static_secrets = true - static_secret_token_capability_refresh_interval = "1s" -} -` - 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 -log_level = "trace" -`, serverClient.Address(), cacheConfig, listenConfig, autoAuthConfig) - configPath := makeTempFile(t, "config.hcl", config) - defer os.Remove(configPath) - - // Start proxy - ui, 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") - t.Errorf("stdout: %s", ui.OutputWriter.String()) - t.Errorf("stderr: %s", ui.ErrorWriter.String()) - } - - proxyClient, err := api.NewClient(api.DefaultConfig()) - require.Nil(t, err) - proxyClient.SetMaxRetries(0) - err = proxyClient.SetAddress("http://" + listenAddr) - require.Nil(t, err) - - secretData := map[string]interface{}{ - "foo": "bar", - } - - // Mount the KVV2 engine - err = serverClient.Sys().Mount("secret-v2", &api.MountInput{ - Type: "kv-v2", - }) - require.Nil(t, err) - - err = serverClient.Sys().PutPolicy("kv-policy", ` - path "secret-v2/*" { - capabilities = ["update", "read"] - }`) - require.Nil(t, err) - - // Setup a token that we can later revoke: - renewable := true - // Set the token's policies to 'default' and nothing else - tokenCreateRequest := &api.TokenCreateRequest{ - Policies: []string{"default", "kv-policy"}, - TTL: "2s", - Renewable: &renewable, - } - - secret, err := serverClient.Auth().Token().CreateOrphan(tokenCreateRequest) - require.Nil(t, err) - token := secret.Auth.ClientToken - proxyClient.SetToken(token) - - // Create kvv2 secret - _, err = serverClient.KVv2("secret-v2").Put(context.Background(), "my-secret", secretData) - require.Nil(t, err) - - // We use raw requests so we can check the headers for cache hit/miss. - req := proxyClient.NewRequest(http.MethodGet, "/v1/secret-v2/data/my-secret") - resp1, err := proxyClient.RawRequest(req) - require.Nil(t, err) - - cacheValue := resp1.Header.Get("X-Cache") - require.Equal(t, "MISS", cacheValue) - - // We expect this to be a cache hit, with the new value - resp2, err := proxyClient.RawRequest(req) - require.Nil(t, err) - - cacheValue = resp2.Header.Get("X-Cache") - require.Equal(t, "HIT", cacheValue) - - // Lastly, we check to make sure the actual data we received is - // as we expect. We must use ParseSecret due to the raw requests. - secret1, err := api.ParseSecret(resp1.Body) - if err != nil { - t.Fatal(err) - } - data, ok := secret1.Data["data"] - require.True(t, ok) - require.Equal(t, secretData, data) - - secret2, err := api.ParseSecret(resp2.Body) - if err != nil { - t.Fatal(err) - } - data2, ok := secret2.Data["data"] - require.True(t, ok) - // We expect that the cached value got updated by the event system. - require.Equal(t, secretData, data2) - - // Wait for the token to expire, and for the permissions to be revoked - // The TTL on the token was 2s, and the capability refresh is every 1s, - // so this should give us more than enough time! - time.Sleep(5 * time.Second) - kvSecret, err := proxyClient.KVv2("secret-v2").Get(context.Background(), "my-secret") - if err == nil { - t.Fatalf("expected error, but none found, secret:%v, err:%v", kvSecret, err) - } - // Make sure it's a permission denied error - if !strings.Contains(err.Error(), "permission denied") { - t.Fatalf("expected error on GET to secret after token revocation, secret:%v, err:%v", kvSecret, err) - } - - close(cmd.ShutdownCh) - wg.Wait() -} - // TestProxy_ApiProxy_Retry Tests the retry functionalities of Vault Proxy's API Proxy func TestProxy_ApiProxy_Retry(t *testing.T) { //----------------------------------------------------