Specify headers by environment variable (#21993)

* Specify headers by environment var

* Add changelog entry

* Add tests, docs

* Formatting

---------

Co-authored-by: Violet Hynes <violet.hynes@hashicorp.com>
This commit is contained in:
Jacob Henner
2024-06-19 16:51:24 -04:00
committed by GitHub
parent 3959722892
commit 46a41a549b
4 changed files with 90 additions and 0 deletions

View File

@@ -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"
@@ -665,6 +667,30 @@ 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
}
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, ", "))
}
}
return client, nil
}

View File

@@ -374,6 +374,61 @@ 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()

3
changelog/21993.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
cli: Allow vault CLI HTTP headers to be specified using the JSON-encoded VAULT_HEADERS environment variable
```

View File

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