From 73d65ce1cde2ed17f77a555aa333241c0ed3eca9 Mon Sep 17 00:00:00 2001 From: Jacob Henner Date: Mon, 17 Jul 2023 15:38:47 -0400 Subject: [PATCH 1/4] Specify headers by environment var --- api/client.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/api/client.go b/api/client.go index 4be03826bbd9..6732c739cc69 100644 --- a/api/client.go +++ b/api/client.go @@ -10,6 +10,7 @@ import ( "crypto/tls" "encoding/base64" "encoding/hex" + "encoding/json" "fmt" "net" "net/http" @@ -41,6 +42,7 @@ const ( EnvVaultClientCert = "VAULT_CLIENT_CERT" EnvVaultClientKey = "VAULT_CLIENT_KEY" EnvVaultClientTimeout = "VAULT_CLIENT_TIMEOUT" + EnvVaultHeaders = "VAULT_HEADERS" EnvVaultSRVLookup = "VAULT_SRV_LOOKUP" EnvVaultSkipVerify = "VAULT_SKIP_VERIFY" EnvVaultNamespace = "VAULT_NAMESPACE" @@ -661,6 +663,25 @@ func NewClient(c *Config) (*Client, error) { client.setNamespace(namespace) } + if envHeaders := os.Getenv(EnvVaultHeaders); envHeaders != "" { + var result map[string]any + err := json.Unmarshal([]byte(envHeaders), &result) + if err != nil { + return nil, fmt.Errorf("could not unmarshal environment-supplied headers") + } + var forbiddenHeaders []string + for key, value := range result { + if strings.HasPrefix(key, "X-Vault-") { + forbiddenHeaders = append(forbiddenHeaders, key) + continue + } + client.AddHeader(key, value.(string)) + } + if len(forbiddenHeaders) > 0 { + return nil, fmt.Errorf("failed to setup Headers[%s]: Header starting by 'X-Vault-' are for internal usage only", strings.Join(forbiddenHeaders, ", ")) + } + } + return client, nil } From b2fb2a4657bcada03a37af0c2dad792707b41802 Mon Sep 17 00:00:00 2001 From: Jacob Henner Date: Thu, 20 Jul 2023 18:09:12 -0400 Subject: [PATCH 2/4] Add changelog entry --- changelog/21993.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/21993.txt diff --git a/changelog/21993.txt b/changelog/21993.txt new file mode 100644 index 000000000000..3bd34e20caf8 --- /dev/null +++ b/changelog/21993.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: Allow vault CLI HTTP headers to be specified by environment variable +``` \ No newline at end of file From b08608bd67ff03b9acfb39f23b25d88b4528df07 Mon Sep 17 00:00:00 2001 From: Jacob Henner Date: Tue, 18 Jun 2024 23:13:47 -0400 Subject: [PATCH 3/4] Add tests, docs --- api/client.go | 7 +++- api/client_test.go | 56 +++++++++++++++++++++++++ changelog/21993.txt | 2 +- website/content/docs/commands/index.mdx | 6 +++ 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/api/client.go b/api/client.go index fe806299da03..0090321caa7f 100644 --- a/api/client.go +++ b/api/client.go @@ -679,7 +679,12 @@ func NewClient(c *Config) (*Client, error) { forbiddenHeaders = append(forbiddenHeaders, key) continue } - client.AddHeader(key, value.(string)) + + value, ok := value.(string) + if !ok { + return nil, fmt.Errorf("environment-supplied headers include non-string values") + } + client.AddHeader(key, value) } if len(forbiddenHeaders) > 0 { return nil, fmt.Errorf("failed to setup Headers[%s]: Header starting by 'X-Vault-' are for internal usage only", strings.Join(forbiddenHeaders, ", ")) diff --git a/api/client_test.go b/api/client_test.go index 3d75aabd6bda..dbd0b43e3726 100644 --- a/api/client_test.go +++ b/api/client_test.go @@ -374,6 +374,62 @@ func TestDefaulRetryPolicy(t *testing.T) { } } +func TestClientEnvHeaders(t *testing.T) { + oldHeaders := os.Getenv(EnvVaultHeaders) + + defer func() { + os.Setenv(EnvVaultHeaders, oldHeaders) + }() + + cases := []struct { + Input string + Valid bool + }{ + { + "{}", + true, + }, + { + "{\"foo\": \"bar\"}", + true, + }, + { + "{\"foo\": 1}", // Values must be strings + false, + }, + { + "{\"X-Vault-Foo\": \"bar\"}", // X-Vault-* not allowed + false, + }, + } + + for _, tc := range cases { + os.Setenv(EnvVaultHeaders, tc.Input) + config := DefaultConfig() + config.ReadEnvironment() + _, err := NewClient(config) + if err != nil { + if tc.Valid { + t.Fatalf("unexpected error reading headers from environment: %v", err) + } + } else { + if !tc.Valid { + t.Fatal("no error reading headers from environment when error was expected") + } + } + } + + os.Setenv(EnvVaultHeaders, "{\"foo\": \"bar\"}") + config := DefaultConfig() + config.ReadEnvironment() + cli, _ := NewClient(config) + + if !reflect.DeepEqual(cli.Headers().Values("foo"), []string{"bar"}) { + t.Error("Environment-supplied headers not set in CLI client") + } + +} + func TestClientEnvSettings(t *testing.T) { cwd, _ := os.Getwd() diff --git a/changelog/21993.txt b/changelog/21993.txt index 3bd34e20caf8..856cfc9662e0 100644 --- a/changelog/21993.txt +++ b/changelog/21993.txt @@ -1,3 +1,3 @@ ```release-note:improvement -cli: Allow vault CLI HTTP headers to be specified by environment variable +cli: Allow vault CLI HTTP headers to be specified using the JSON-encoded VAULT_HEADERS environment variable ``` \ No newline at end of file diff --git a/website/content/docs/commands/index.mdx b/website/content/docs/commands/index.mdx index 7632dd452cb6..15109f9c8f26 100644 --- a/website/content/docs/commands/index.mdx +++ b/website/content/docs/commands/index.mdx @@ -439,6 +439,12 @@ Prevents the Vault client from following redirects. By default, the Vault client ~> **Note:** Disabling redirect following behavior could cause issues with commands such as 'vault operator raft snapshot' as this command redirects the request to the cluster's primary node. +### `VAULT_HEADERS` + +JSON-encoded headers to include in Vault HTTP requests performed by the CLI. For example: `{"FOO": "BAR"}`. + +Like the `-header` CLI parameter, headers starting with `X-Vault-` are forbidden. + ## Flags There are different CLI flags that are available depending on subcommands. Some From 688ac0c4a68f7619889150734933b70a0c6a4d5f Mon Sep 17 00:00:00 2001 From: Jacob Henner Date: Wed, 19 Jun 2024 13:32:10 -0400 Subject: [PATCH 4/4] Formatting --- api/client_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/client_test.go b/api/client_test.go index dbd0b43e3726..1ed4dfd3d359 100644 --- a/api/client_test.go +++ b/api/client_test.go @@ -427,7 +427,6 @@ func TestClientEnvHeaders(t *testing.T) { if !reflect.DeepEqual(cli.Headers().Values("foo"), []string{"bar"}) { t.Error("Environment-supplied headers not set in CLI client") } - } func TestClientEnvSettings(t *testing.T) {