diff --git a/controllers/vaultconnection_controller.go b/controllers/vaultconnection_controller.go index a0191749..3647da73 100644 --- a/controllers/vaultconnection_controller.go +++ b/controllers/vaultconnection_controller.go @@ -73,6 +73,7 @@ func (r *VaultConnectionReconciler) Reconcile(ctx context.Context, req ctrl.Requ Address: o.Spec.Address, SkipTLSVerify: o.Spec.SkipTLSVerify, TLSServerName: o.Spec.TLSServerName, + Headers: o.Spec.Headers, } var errs error diff --git a/internal/vault/client.go b/internal/vault/client.go index 687d0196..c407f1f8 100644 --- a/internal/vault/client.go +++ b/internal/vault/client.go @@ -606,6 +606,7 @@ func (c *defaultClient) init(ctx context.Context, client ctrlclient.Client, VaultNamespace: authObj.Spec.Namespace, K8sNamespace: connObj.Namespace, CACertSecretRef: connObj.Spec.CACertSecretRef, + Headers: connObj.Spec.Headers, } vc, err := MakeVaultClient(ctx, cfg, client) diff --git a/internal/vault/config.go b/internal/vault/config.go index a6ac0cfe..cd30fc86 100644 --- a/internal/vault/config.go +++ b/internal/vault/config.go @@ -9,7 +9,8 @@ import ( "fmt" "github.com/hashicorp/vault/api" - "k8s.io/api/core/v1" + vconsts "github.com/hashicorp/vault/sdk/helper/consts" + v1 "k8s.io/api/core/v1" ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -35,6 +36,8 @@ type ClientConfig struct { TLSServerName string // VaultNamespace is the namespace in Vault to auth to VaultNamespace string + // Headers are http headers to set on the Vault client + Headers map[string]string } // MakeVaultClient creates a Vault api.Client from a ClientConfig. @@ -93,6 +96,12 @@ func MakeVaultClient(ctx context.Context, cfg *ClientConfig, client ctrlclient.C l.Error(err, "error setting up Vault API client") return nil, err } + if _, exists := cfg.Headers[vconsts.NamespaceHeaderName]; exists { + return nil, fmt.Errorf("setting header %q on VaultConnection is not permitted", vconsts.NamespaceHeaderName) + } + for k, v := range cfg.Headers { + c.AddHeader(k, v) + } if cfg.VaultNamespace != "" { c.SetNamespace(cfg.VaultNamespace) } diff --git a/internal/vault/config_test.go b/internal/vault/config_test.go index acaf041e..62918d41 100644 --- a/internal/vault/config_test.go +++ b/internal/vault/config_test.go @@ -72,6 +72,29 @@ func TestMakeVaultClient(t *testing.T) { CACert: nil, expectedError: nil, }, + "headers": { + vaultConfig: &ClientConfig{ + Headers: map[string]string{ + "X-Proxy-Setting": "yes", + "Y-Proxy-Setting": "no", + }, + VaultNamespace: "vault-test-namespace", + }, + CACert: nil, + expectedError: nil, + }, + "headers can't override namespace": { + vaultConfig: &ClientConfig{ + Headers: map[string]string{ + "X-Proxy-Setting": "yes", + "Y-Proxy-Setting": "no", + vconsts.NamespaceHeaderName: "nope", + }, + VaultNamespace: "vault-test-namespace", + }, + CACert: nil, + expectedError: fmt.Errorf(`setting header "X-Vault-Namespace" on VaultConnection is not permitted`), + }, } for name, tc := range tests { @@ -97,7 +120,7 @@ func TestMakeVaultClient(t *testing.T) { assert.Nil(t, vaultClient) } else { assert.NoError(t, err) - assert.NotNil(t, vaultClient) + require.NotNil(t, vaultClient) vaultClient.SetCloneHeaders(true) vaultConfig := vaultClient.CloneConfig() @@ -116,7 +139,25 @@ func TestMakeVaultClient(t *testing.T) { require.NoError(t, err) assert.True(t, tlsConfig.RootCAs.Equal(expectedCertPool), "The CA cert in the client doesn't match the expected cert") } + + expectedHeaders := makeVaultHttpHeaders(t, tc.vaultConfig.VaultNamespace, tc.vaultConfig.Headers) + assert.Equal(t, expectedHeaders, vaultClient.Headers(), "The headers in the client don't match the expected headers") } }) } } + +func makeVaultHttpHeaders(t *testing.T, namespace string, headers map[string]string) http.Header { + t.Helper() + + h := make(http.Header) + for k, v := range headers { + h.Set(k, v) + } + h.Set("X-Vault-Request", "true") + if namespace != "" { + h.Set(vconsts.NamespaceHeaderName, namespace) + } + + return h +}