From e608c624b548c480a47803914daafd30be31596d Mon Sep 17 00:00:00 2001 From: Ben Ash <32777270+benashz@users.noreply.github.com> Date: Fri, 13 Jan 2023 15:17:47 -0500 Subject: [PATCH] Ensure a consistent TLS configuration (#173) (#178) * Ensure a consistent TLS configuration for k8s API requests Previously, it was possible for the http.Client's Transport to be missing the necessary root CAs to ensure that all TLS connections between the auth engine and the Kubernetes API were validated against a configured set of CA certificates. This fix ensures that the http.Client's Transport is always consistent with the configured CA cert chain, by introducing a periodic TLS configuration checker that is started as part of the backend's initialization. Other fixes: - only update the client's transport when the CA certificate pool has changed. Co-authored-by: Tom Proctor --- Makefile | 10 +- backend.go | 228 +++++++++++++++++-- backend_test.go | 528 ++++++++++++++++++++++++++++++++++++++++++++ common_test.go | 105 +++++++++ path_config.go | 36 +-- path_config_test.go | 81 ------- path_login.go | 8 +- path_role_test.go | 8 +- 8 files changed, 867 insertions(+), 137 deletions(-) create mode 100644 backend_test.go create mode 100644 common_test.go diff --git a/Makefile b/Makefile index 92e1fe02..c3957a9b 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,9 @@ +TESTARGS ?= '-test.v' # kind cluster name -KIND_CLUSTER_NAME?=vault-plugin-auth-kubernetes +KIND_CLUSTER_NAME ?= vault-plugin-auth-kubernetes # kind k8s version -KIND_K8S_VERSION?=v1.25.0 +KIND_K8S_VERSION ?= v1.25.0 .PHONY: default default: dev @@ -13,11 +14,11 @@ dev: .PHONY: test test: fmtcheck - CGO_ENABLED=0 go test ./... $(TESTARGS) -timeout=20m + CGO_ENABLED=0 go test $(TESTARGS) -timeout=20m ./... .PHONY: integration-test integration-test: - INTEGRATION_TESTS=true CGO_ENABLED=0 go test github.com/hashicorp/vault-plugin-auth-kubernetes/integrationtest/... $(TESTARGS) -count=1 -timeout=20m + INTEGRATION_TESTS=true CGO_ENABLED=0 go test $(TESTARGS) -count=1 -timeout=20m github.com/hashicorp/vault-plugin-auth-kubernetes/integrationtest/... .PHONY: fmtcheck fmtcheck: @@ -58,6 +59,7 @@ setup-integration-test: teardown-integration-test vault-image --set server.dev.enabled=true \ --set server.image.tag=dev \ --set server.image.pullPolicy=Never \ + --set server.logLevel=trace \ --set injector.enabled=false \ --set server.extraArgs="-dev-plugin-dir=/vault/plugin_directory" kubectl patch --namespace=test statefulset vault --patch-file integrationtest/vault/hostPortPatch.yaml diff --git a/backend.go b/backend.go index 2c090c6b..da8284c2 100644 --- a/backend.go +++ b/backend.go @@ -6,6 +6,7 @@ import ( "crypto/tls" "crypto/x509" "encoding/json" + "errors" "fmt" "net/http" "strings" @@ -26,6 +27,7 @@ const ( aliasNameSourceSAUid = "serviceaccount_uid" aliasNameSourceSAName = "serviceaccount_name" aliasNameSourceDefault = aliasNameSourceSAUid + minTLSVersion = tls.VersionTLS12 ) var ( @@ -44,6 +46,17 @@ var ( // caReloadPeriod is the time period how often the in-memory copy of local // CA cert can be used, before reading it again from disk. caReloadPeriod = 1 * time.Hour + + // defaultHorizon provides the default duration to be used + // in the tlsConfigUpdater's time.Ticker, setup in runTLSConfigUpdater() + defaultHorizon = time.Second * 30 + + // defaultMinHorizon provides the minimum duration that can be specified + // in the tlsConfigUpdater's time.Ticker, setup in runTLSConfigUpdater() + defaultMinHorizon = time.Second * 5 + + errTLSConfigNotSet = errors.New("TLSConfig not set") + errHTTPClientNotSet = errors.New("http.Client not set") ) // kubeAuthBackend implements logical.Backend @@ -53,8 +66,11 @@ type kubeAuthBackend struct { // default HTTP client for connection reuse httpClient *http.Client + // tlsConfig is periodically updated whenever the CA certificate configuration changes. + tlsConfig *tls.Config + // reviewFactory is used to configure the strategy for doing a token review. - // Currently the only options are using the kubernetes API or mocking the + // Currently, the only options are using the kubernetes API or mocking the // review. Mocks should only be used in tests. reviewFactory tokenReviewFactory @@ -71,22 +87,47 @@ type kubeAuthBackend struct { // - disable_local_ca_jwt is false localCACertReader *cachingFileReader + // tlsConfigUpdaterRunning is used to signal the current state of the tlsConfig updater routine. + tlsConfigUpdaterRunning bool + + // tlsConfigUpdateCancelFunc should be called in the backend's Clean(), set in initialize(). + tlsConfigUpdateCancelFunc context.CancelFunc + l sync.RWMutex + + // tlsMu provides the lock for synchronizing updates to the tlsConfig. + tlsMu sync.RWMutex } // Factory returns a new backend as logical.Backend. func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) { b := Backend() + if err := b.Setup(ctx, conf); err != nil { return nil, err } + return b, nil } +var getDefaultHTTPClient = cleanhttp.DefaultPooledClient + +func getDefaultTLSConfig() *tls.Config { + return &tls.Config{ + MinVersion: minTLSVersion, + } +} + func Backend() *kubeAuthBackend { b := &kubeAuthBackend{ localSATokenReader: newCachingFileReader(localJWTPath, jwtReloadPeriod, time.Now), localCACertReader: newCachingFileReader(localCACertPath, caReloadPeriod, time.Now), + // Set default HTTP client + httpClient: getDefaultHTTPClient(), + // Set the default TLSConfig + tlsConfig: getDefaultTLSConfig(), + // Set the review factory to default to calling into the kubernetes API. + reviewFactory: tokenReviewAPIFactory, } b.Backend = &framework.Backend{ @@ -109,46 +150,129 @@ func Backend() *kubeAuthBackend { pathsRole(b), ), InitializeFunc: b.initialize, + Clean: b.cleanup, } - // Set default HTTP client - b.httpClient = cleanhttp.DefaultPooledClient() - - // Set the review factory to default to calling into the kubernetes API. - b.reviewFactory = tokenReviewAPIFactory - return b } // initialize is used to handle the state of config values just after the K8s plugin has been mounted func (b *kubeAuthBackend) initialize(ctx context.Context, req *logical.InitializationRequest) error { - // Try to load the config on initialization - config, err := b.loadConfig(ctx, req.Storage) + updaterCtx, cancel := context.WithCancel(context.Background()) + if err := b.runTLSConfigUpdater(updaterCtx, req.Storage, defaultHorizon); err != nil { + cancel() + return err + } + + b.tlsConfigUpdateCancelFunc = cancel + + config, err := b.config(ctx, req.Storage) if err != nil { return err } - if config == nil { + + if config != nil { + if err := b.updateTLSConfig(config); err != nil { + return err + } + } + + return nil +} + +func (b *kubeAuthBackend) cleanup(_ context.Context) { + b.shutdownTLSConfigUpdater() +} + +// validateHTTPClientInit that the Backend's HTTPClient and TLSConfig has been properly instantiated. +func (b *kubeAuthBackend) validateHTTPClientInit() error { + if b.httpClient == nil { + return errHTTPClientNotSet + } + if b.tlsConfig == nil { + return errTLSConfigNotSet + } + + return nil +} + +// runTLSConfigUpdater sets up a routine that periodically calls b.updateTLSConfig(). This ensures that the +// httpClient's TLS configuration is consistent with the backend's stored configuration. +func (b *kubeAuthBackend) runTLSConfigUpdater(ctx context.Context, s logical.Storage, horizon time.Duration) error { + b.tlsMu.Lock() + defer b.tlsMu.Unlock() + + if b.tlsConfigUpdaterRunning { return nil } - b.l.Lock() - defer b.l.Unlock() - // If we have a CA cert build the TLSConfig - if len(config.CACert) > 0 { - certPool := x509.NewCertPool() - certPool.AppendCertsFromPEM([]byte(config.CACert)) + if horizon < defaultMinHorizon { + return fmt.Errorf("update horizon must be equal to or greater than %s", defaultMinHorizon) + } + + if err := b.validateHTTPClientInit(); err != nil { + return err + } + + updateTLSConfig := func(ctx context.Context, s logical.Storage) error { + config, err := b.config(ctx, s) + if err != nil { + return fmt.Errorf("failed config read, err=%w", err) + } - tlsConfig := &tls.Config{ - MinVersion: tls.VersionTLS12, - RootCAs: certPool, + if config == nil { + b.Logger().Trace("Skipping TLSConfig update, no configuration set") + return nil + } + + if err := b.updateTLSConfig(config); err != nil { + return err } - b.httpClient.Transport.(*http.Transport).TLSClientConfig = tlsConfig + return nil } + var wg sync.WaitGroup + wg.Add(1) + ticker := time.NewTicker(horizon) + go func(ctx context.Context, s logical.Storage) { + defer func() { + b.tlsMu.Lock() + defer b.tlsMu.Unlock() + ticker.Stop() + b.tlsConfigUpdaterRunning = false + b.Logger().Trace("TLSConfig updater shutdown completed") + }() + + b.Logger().Trace("TLSConfig updater starting", "horizon", horizon) + b.tlsConfigUpdaterRunning = true + wg.Done() + for { + select { + case <-ctx.Done(): + b.Logger().Trace("TLSConfig updater shutting down") + return + case <-ticker.C: + if err := updateTLSConfig(ctx, s); err != nil { + b.Logger().Warn("TLSConfig update failed, retrying", + "horizon", defaultHorizon.String(), "err", err) + } + } + } + }(ctx, s) + wg.Wait() + return nil } +func (b *kubeAuthBackend) shutdownTLSConfigUpdater() { + if b.tlsConfigUpdateCancelFunc != nil { + b.Logger().Debug("TLSConfig updater shutdown requested") + b.tlsConfigUpdateCancelFunc() + b.tlsConfigUpdateCancelFunc = nil + } +} + // config takes a storage object and returns a kubeConfig object. // It does not return local token and CA file which are specific to the pod we run in. func (b *kubeAuthBackend) config(ctx context.Context, s logical.Storage) (*kubeConfig, error) { @@ -255,6 +379,70 @@ func (b *kubeAuthBackend) role(ctx context.Context, s logical.Storage, name stri return role, nil } +// getHTTPClient return the backend's HTTP client for connecting to the Kubernetes API. +func (b *kubeAuthBackend) getHTTPClient() (*http.Client, error) { + b.tlsMu.RLock() + defer b.tlsMu.RUnlock() + + if err := b.validateHTTPClientInit(); err != nil { + return nil, err + } + + return b.httpClient, nil +} + +// updateTLSConfig ensures that the httpClient's TLS configuration is consistent +// with the backend's stored configuration. +func (b *kubeAuthBackend) updateTLSConfig(config *kubeConfig) error { + b.tlsMu.Lock() + defer b.tlsMu.Unlock() + + if err := b.validateHTTPClientInit(); err != nil { + return err + } + + // attempt to read the CA certificates from the config directly or from the filesystem. + var caCertBytes []byte + if config.CACert != "" { + caCertBytes = []byte(config.CACert) + } else if !config.DisableLocalCAJwt && b.localCACertReader != nil { + data, err := b.localCACertReader.ReadFile() + if err != nil { + return err + } + caCertBytes = []byte(data) + } + + certPool := x509.NewCertPool() + if len(caCertBytes) > 0 { + if ok := certPool.AppendCertsFromPEM(caCertBytes); !ok { + b.Logger().Warn("Configured CA PEM data contains no valid certificates, TLS verification will fail") + } + } else { + // provide an empty certPool + b.Logger().Warn("No CA certificates configured, TLS verification will fail") + // TODO: think about supporting host root CA certificates via a configuration toggle, + // in which case RootCAs should be set to nil + } + + // only refresh the Root CAs if they have changed since the last full update. + if !b.tlsConfig.RootCAs.Equal(certPool) { + b.Logger().Trace("Root CA certificate pool has changed, updating the client's transport") + transport, ok := b.httpClient.Transport.(*http.Transport) + if !ok { + // should never happen + return fmt.Errorf("type assertion failed for %T", b.httpClient.Transport) + } + + b.tlsConfig.RootCAs = certPool + transport.TLSClientConfig = b.tlsConfig + } else { + b.Logger().Trace("Root CA certificate pool is unchanged, no update required") + } + + return nil +} + func validateAliasNameSource(source string) error { for _, s := range aliasNameSources { if s == source { diff --git a/backend_test.go b/backend_test.go new file mode 100644 index 00000000..f4f0f0de --- /dev/null +++ b/backend_test.go @@ -0,0 +1,528 @@ +package kubeauth + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "net/http" + "os" + "path/filepath" + "reflect" + "testing" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/vault/sdk/framework" + "github.com/hashicorp/vault/sdk/logical" +) + +func Test_kubeAuthBackend_updateTLSConfig(t *testing.T) { + defaultCertPool := getTestCertPool(t, testCACert) + localCertPool := getTestCertPool(t, testLocalCACert) + otherCertPool := getTestCertPool(t, testOtherCACert) + + type testConfig struct { + config *kubeConfig + expectTLSConfig *tls.Config + localCACert string + wantErr bool + expectError error + } + tests := []struct { + name string + httpClient *http.Client + tlsConfig *tls.Config + wantErr bool + configs []testConfig + }{ + { + name: "fail-client-not-set", + httpClient: nil, + configs: []testConfig{ + { + wantErr: true, + expectError: errHTTPClientNotSet, + }, + }, + }, + { + name: "fail-tlsConfig-not-set", + httpClient: getDefaultHTTPClient(), + configs: []testConfig{ + { + wantErr: true, + expectError: errTLSConfigNotSet, + }, + }, + }, + { + name: "ca-certs-from-config-source", + httpClient: getDefaultHTTPClient(), + tlsConfig: getDefaultTLSConfig(), + wantErr: false, + configs: []testConfig{ + { + config: &kubeConfig{ + CACert: testCACert, + DisableLocalCAJwt: false, + }, + expectTLSConfig: &tls.Config{ + MinVersion: minTLSVersion, + RootCAs: defaultCertPool, + }, + }, + { + config: &kubeConfig{ + CACert: testLocalCACert, + DisableLocalCAJwt: false, + }, + expectTLSConfig: &tls.Config{ + MinVersion: minTLSVersion, + RootCAs: localCertPool, + }, + }, + { + config: &kubeConfig{ + CACert: testCACert, + DisableLocalCAJwt: false, + }, + expectTLSConfig: &tls.Config{ + MinVersion: minTLSVersion, + RootCAs: defaultCertPool, + }, + }, + }, + }, + { + name: "ca-certs-from-file-source", + httpClient: getDefaultHTTPClient(), + tlsConfig: getDefaultTLSConfig(), + configs: []testConfig{ + { + config: &kubeConfig{ + DisableLocalCAJwt: false, + }, + expectTLSConfig: &tls.Config{ + MinVersion: minTLSVersion, + RootCAs: defaultCertPool, + }, + localCACert: testCACert, + }, + { + config: &kubeConfig{ + DisableLocalCAJwt: false, + }, + localCACert: testLocalCACert, + expectTLSConfig: &tls.Config{ + MinVersion: minTLSVersion, + RootCAs: localCertPool, + }, + }, + }, + wantErr: false, + }, + { + name: "ca-certs-mixed-source", + httpClient: getDefaultHTTPClient(), + tlsConfig: getDefaultTLSConfig(), + configs: []testConfig{ + { + config: &kubeConfig{ + CACert: testCACert, + DisableLocalCAJwt: false, + }, + expectTLSConfig: &tls.Config{ + MinVersion: minTLSVersion, + RootCAs: defaultCertPool, + }, + }, + { + config: &kubeConfig{ + DisableLocalCAJwt: false, + }, + localCACert: testLocalCACert, + expectTLSConfig: &tls.Config{ + MinVersion: minTLSVersion, + RootCAs: localCertPool, + }, + }, + { + config: &kubeConfig{ + CACert: testOtherCACert, + DisableLocalCAJwt: false, + }, + expectTLSConfig: &tls.Config{ + MinVersion: minTLSVersion, + RootCAs: otherCertPool, + }, + }, + { + config: &kubeConfig{ + DisableLocalCAJwt: false, + }, + expectTLSConfig: &tls.Config{ + MinVersion: minTLSVersion, + RootCAs: defaultCertPool, + }, + localCACert: testCACert, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := &kubeAuthBackend{ + Backend: &framework.Backend{}, + httpClient: tt.httpClient, + tlsConfig: tt.tlsConfig, + } + + if err := b.Setup(context.Background(), + &logical.BackendConfig{ + Logger: hclog.NewNullLogger(), + }); err != nil { + t.Fatalf("failed to setup the backend, err=%v", err) + } + + localFile := filepath.Join(t.TempDir(), "ca.crt") + b.localCACertReader = &cachingFileReader{ + path: localFile, + currentTime: time.Now().UTC, + ttl: 0, + } + for idx, config := range tt.configs { + t.Run(fmt.Sprintf("config-%d", idx), func(t *testing.T) { + if config.localCACert != "" { + if err := os.WriteFile(localFile, []byte(config.localCACert), 0600); err != nil { + t.Fatalf("failed to write local file %q", localFile) + } + t.Cleanup(func() { + if err := os.Remove(localFile); err != nil { + t.Fatal(err) + } + }) + } + + err := b.updateTLSConfig(config.config) + if config.wantErr && err == nil { + t.Fatalf("updateTLSConfig() error = %v, wantErr %v", err, config.wantErr) + } + + if !reflect.DeepEqual(err, config.expectError) { + t.Fatalf("updateTLSConfig() error = %v, expectErr %v", err, config.expectError) + } + + if config.wantErr { + return + } + + assertTLSConfigEquals(t, b.tlsConfig, config.expectTLSConfig) + assertValidTransport(t, b, config.expectTLSConfig) + }) + } + }) + } +} + +func Test_kubeAuthBackend_initialize(t *testing.T) { + defaultCertPool := getTestCertPool(t, testCACert) + + tests := []struct { + name string + httpClient *http.Client + ctx context.Context + req *logical.InitializationRequest + config *kubeConfig + tlsConfig *tls.Config + expectTLSConfig *tls.Config + wantErr bool + expectErr error + }{ + { + name: "fail-client-not-set", + ctx: context.Background(), + httpClient: nil, + tlsConfig: getDefaultTLSConfig(), + req: &logical.InitializationRequest{ + Storage: &logical.InmemStorage{}, + }, + config: &kubeConfig{ + CACert: testCACert, + DisableLocalCAJwt: false, + }, + wantErr: true, + expectErr: errHTTPClientNotSet, + }, + { + name: "no-config", + ctx: context.Background(), + httpClient: getDefaultHTTPClient(), + tlsConfig: getDefaultTLSConfig(), + req: &logical.InitializationRequest{ + Storage: &logical.InmemStorage{}, + }, + wantErr: false, + expectErr: nil, + }, + { + name: "initialized-from-config", + ctx: context.Background(), + httpClient: getDefaultHTTPClient(), + tlsConfig: getDefaultTLSConfig(), + req: &logical.InitializationRequest{ + Storage: &logical.InmemStorage{}, + }, + config: &kubeConfig{ + CACert: testCACert, + DisableLocalCAJwt: false, + }, + expectTLSConfig: &tls.Config{ + MinVersion: minTLSVersion, + RootCAs: defaultCertPool, + }, + wantErr: false, + expectErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := &kubeAuthBackend{ + Backend: &framework.Backend{}, + httpClient: tt.httpClient, + tlsConfig: tt.tlsConfig, + } + + if err := b.Setup(context.Background(), + &logical.BackendConfig{ + Logger: hclog.NewNullLogger(), + StorageView: tt.req.Storage, + }); err != nil { + t.Fatalf("failed to setup the backend, err=%v", err) + } + + if tt.config != nil { + entry, err := logical.StorageEntryJSON(configPath, tt.config) + if err != nil { + t.Fatal(err) + } + + if err := tt.req.Storage.Put(tt.ctx, entry); err != nil { + t.Fatal(err) + } + } + + if b.tlsConfigUpdaterRunning { + t.Fatalf("tlsConfigUpdater started before initialize()") + } + + ctx, _ := context.WithTimeout(tt.ctx, time.Second*30) + err := b.initialize(ctx, tt.req) + if tt.wantErr && err == nil { + t.Errorf("initialize() error = %v, wantErr %v", err, tt.wantErr) + + } + + if !reflect.DeepEqual(err, tt.expectErr) { + t.Fatalf("initialize() error = %v, expectErr %v", err, tt.expectErr) + } + + if tt.wantErr { + return + } + + if tt.config != nil { + assertTLSConfigEquals(t, b.tlsConfig, tt.expectTLSConfig) + assertValidTransport(t, b, tt.expectTLSConfig) + } + + if !b.tlsConfigUpdaterRunning { + t.Fatalf("tlsConfigUpdater not started from initialize()") + } + }) + } +} + +func Test_kubeAuthBackend_runTLSConfigUpdater(t *testing.T) { + defaultCertPool := getTestCertPool(t, testCACert) + otherCertPool := getTestCertPool(t, testOtherCACert) + + type testConfig struct { + config *kubeConfig + expectTLSConfig *tls.Config + } + + tests := []struct { + name string + ctx context.Context + storage logical.Storage + tlsConfig *tls.Config + horizon time.Duration + minHorizon time.Duration + wantErr bool + expectErr error + configs []*testConfig + }{ + { + name: "initialized-from-config", + tlsConfig: getDefaultTLSConfig(), + ctx: context.Background(), + storage: &logical.InmemStorage{}, + horizon: time.Millisecond * 500, + minHorizon: time.Millisecond * 499, + wantErr: false, + expectErr: nil, + configs: []*testConfig{ + { + config: &kubeConfig{ + CACert: testCACert, + DisableLocalCAJwt: false, + }, + expectTLSConfig: &tls.Config{ + MinVersion: minTLSVersion, + RootCAs: defaultCertPool, + }, + }, + { + config: &kubeConfig{ + CACert: testOtherCACert, + DisableLocalCAJwt: false, + }, + expectTLSConfig: &tls.Config{ + MinVersion: minTLSVersion, + RootCAs: otherCertPool, + }, + }, + }, + }, + { + name: "fail-min-horizon", + ctx: context.Background(), + storage: &logical.InmemStorage{}, + horizon: time.Millisecond * 500, + wantErr: true, + expectErr: fmt.Errorf("update horizon must be equal to or greater than %s", defaultMinHorizon), + }, + } + + d := defaultMinHorizon + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.minHorizon > 0 { + defer (func() { + defaultMinHorizon = d + })() + defaultMinHorizon = tt.minHorizon + } + b := &kubeAuthBackend{ + Backend: &framework.Backend{}, + httpClient: getDefaultHTTPClient(), + tlsConfig: tt.tlsConfig, + } + + if err := b.Setup(context.Background(), + &logical.BackendConfig{ + Logger: hclog.NewNullLogger(), + StorageView: tt.storage, + }); err != nil { + t.Fatalf("failed to setup the backend, err=%v", err) + } + + if b.tlsConfigUpdaterRunning { + t.Fatalf("tlsConfigUpdater already started") + } + + configCount := len(tt.configs) + ctx, cancel := context.WithTimeout(tt.ctx, tt.horizon*time.Duration(configCount*2)) + defer cancel() + err := b.runTLSConfigUpdater(ctx, tt.storage, tt.horizon) + if tt.wantErr && err == nil { + t.Errorf("runTLSConfigUpdater() error = %v, wantErr %v", err, tt.wantErr) + + } + + if !reflect.DeepEqual(err, tt.expectErr) { + t.Fatalf("runTLSConfigUpdater() error = %v, expectErr %v", err, tt.expectErr) + } + + if tt.wantErr { + return + } + + if !b.tlsConfigUpdaterRunning { + t.Fatalf("tlsConfigUpdater not started") + } + + if configCount > 0 { + for idx := 0; idx < configCount; idx++ { + t.Run(fmt.Sprintf("config-%d", idx), func(t *testing.T) { + config := tt.configs[idx] + if config.config != nil { + entry, err := logical.StorageEntryJSON(configPath, config.config) + if err != nil { + t.Fatal(err) + } + + if err := tt.storage.Put(tt.ctx, entry); err != nil { + t.Fatal(err) + } + } + + time.Sleep(tt.horizon * 3) + if b.tlsConfig == nil { + t.Fatalf("runTLSConfigUpdater(), expected tlsConfig initialization") + } + assertTLSConfigEquals(t, b.tlsConfig, config.expectTLSConfig) + assertValidTransport(t, b, config.expectTLSConfig) + }) + } + } else { + if b.tlsConfig != nil { + t.Errorf("runTLSConfigUpdater(), unexpected tlsConfig initialization") + } + } + + cancel() + time.Sleep(tt.horizon) + if b.tlsConfigUpdaterRunning { + t.Fatalf("tlsConfigUpdater did not shutdown cleanly") + } + }) + } +} + +func assertTLSConfigEquals(t *testing.T, actual, expected *tls.Config) { + t.Helper() + + if !actual.RootCAs.Equal(expected.RootCAs) { + t.Errorf("updateTLSConfig() actual RootCAs = %v, expected RootCAs %v", + actual.RootCAs, expected.RootCAs) + } + if actual.MinVersion != expected.MinVersion { + t.Errorf("updateTLSConfig() actual MinVersion = %v, expected MinVersion %v", + actual.MinVersion, expected.MinVersion) + } + +} + +func assertValidTransport(t *testing.T, b *kubeAuthBackend, expected *tls.Config) { + t.Helper() + + transport, ok := b.httpClient.Transport.(*http.Transport) + if !ok { + t.Fatalf("type assertion failed for %T", b.httpClient.Transport) + } + + assertTLSConfigEquals(t, transport.TLSClientConfig, expected) +} + +func getTestCertPool(t *testing.T, cert string) *x509.CertPool { + t.Helper() + + pool := x509.NewCertPool() + if ok := pool.AppendCertsFromPEM([]byte(cert)); !ok { + t.Fatalf("test certificate contains no valid certificates") + } + return pool +} diff --git a/common_test.go b/common_test.go new file mode 100644 index 00000000..7adf0195 --- /dev/null +++ b/common_test.go @@ -0,0 +1,105 @@ +package kubeauth + +const ( + testLocalCACert = `-----BEGIN CERTIFICATE----- +MIIDVDCCAjwCCQDFiyFY1M6afTANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJV +UzETMBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEgMB4GA1UE +CgwXVmF1bHQgVGVzdGluZyBBdXRob3JpdHkxFDASBgNVBAMMC2V4YW1wbGUubmV0 +MB4XDTIwMDkxODAxMjkxM1oXDTQ1MDkxODAxMjkxM1owbDELMAkGA1UEBhMCVVMx +EzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxIDAeBgNVBAoM +F1ZhdWx0IFRlc3RpbmcgQXV0aG9yaXR5MRQwEgYDVQQDDAtleGFtcGxlLm5ldDCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALCA9oKv+ESRHX2e/iq1PlGr +zD23/MBS0V+fWQDY0hyEqY98CGwRtF6pEcLEYsreArj5/zznsIevLkNOD+beg43y +WpEJlCPgDhGXI/Oima6ooHVEIMaIKLjK7GrSzAb3rNRGACwrR/u/IKaFl+XJG0qx +g8mOZ3fByaAlIk+shVLUcIedNN1tNR+6/4ZpHg7PDjrZXP4XKrmKPTh4yqfu+BtZ +9IY2oyregqEsGW1/3h1NM+LHGVakTV2d/mwMYHhwoq9Y8BD+PemT5z8TmhH/cIk5 +P8Q8ud5/q6YTIJg9TELKebLAeNtRNnNoHeUoRTjiW1MBwNHtgyTTY+H3W/9Dne0C +AwEAATANBgkqhkiG9w0BAQsFAAOCAQEAXmygFkGIBnXxKlsTDiV8RW2iHLgFdZFJ +hcU8UpxZhhaL5JbQl6byfbHjrX31q7ii8uC8FcbW0AEdnEQAb9Ui6a+if7HwXNmI +DTlYl+lMlk9RtWvExw6AEEbg5nCpGaKexm7wJgzYGP9by9pQ7wX/CS7ofCzCK+Al +uSIqjPkMC201ZXH39n1lxxq6BacdYjv8wo4mMzi8iTSQGVWPdjHZVYOClFgN6hoj +8SkrrSe888a0H+i7EknRxC4sLRaMUK/FAvwtXaSZi2djruAtQzQGQ56m1phC2C/k +k9aL00AQ9Y4KTfiJD7LK8YIZDnFKLOCJhYgKCLCOVwOHb7836SNCxA== +-----END CERTIFICATE-----` + + testLocalJWT = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlZhdWx0IFRlc3QiLCJpYXQiOjExMjM1OH0.GOC8w-MyhorgojB20SPNyH_ECsBjYJH89hjntOxSywA` + + testRSACert = `-----BEGIN CERTIFICATE----- +MIIDcjCCAlqgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p +a3ViZUNBMB4XDTE3MDgzMDE5MDgzNloXDTE4MDgzMDE5MDgzNlowLDEXMBUGA1UE +ChMOc3lzdGVtOm1hc3RlcnMxETAPBgNVBAMTCG1pbmlrdWJlMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxD3eM3+WNc4phxAeQxNOmcybKlNJWowuC12u +v+cGJWxxpDx/OoEIxKI5wmgHxEwFCZL545sjfLqyBcgxQR2xSCib+bYzjBtfA6uV +6d/35nurzz21okcMffc5xKMyZhEwt98WAvYWD71Bihz7iGBq5Sw9md6pqnkNoScR +Hhi3Vl94a6D6shwb6nXA2hlwYLcnoKtpe3Ptq6MW6CpfBA8C11q5eeW4xdvrwKt3 +Vd1TgFeEnnqwzUWGapU2uwwUfbRkLTDvrp6791uq0Vo7mzz00xYhV1PLCeAdpJEK +3Vr74FT7jHIbPlzi/qjRBVFKf9IRXnhbjrCl7S0Ayev1Fao4TQIDAQABo4G1MIGy +MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw +DAYDVR0TAQH/BAIwADBzBgNVHREEbDBqgiRrdWJlcm5ldGVzLmRlZmF1bHQuc3Zj +LmNsdXN0ZXIubG9jYWyCFmt1YmVybmV0ZXMuZGVmYXVsdC5zdmOCEmt1YmVybmV0 +ZXMuZGVmYXVsdIIKa3ViZXJuZXRlc4cEwKhjZIcECgAAATANBgkqhkiG9w0BAQsF +AAOCAQEAIw8rKuryhhl527wf9q/VrWixzZ1jCLvyc/60z9rWpXxKFxT8AyCsHirM +F4fHXW4Brcoh/Dc2ci36cUbuywIyxHjgVUG45D4jPPWskY1++ZSfJfSXAuA8eFew +c+No3WPkmZB6ZOZ6q5iPY+FOgDZC7ddWmGuZrle51gBL347cU7H1BrTm6Lm6kXRs +fHRZJX2+B8lnsXsS3QF2BTU0ymuCxCCQxub/GhPZVz3nNNtro1z7/szLUVP1c1/8 +p7HP3k7caxfp346TZ/HgbV9sJEkHP7Ym7n9E7LSyUTSxXwBRPraH1WQzEgFNPSUV +V0n6FBLiejOTPKapJ2F0tIqAyJHFug== +-----END CERTIFICATE-----` + + testECCert = `-----BEGIN CERTIFICATE----- +MIICZDCCAeugAwIBAgIJALM9NbK8WRuBMAkGByqGSM49BAEwRTELMAkGA1UEBhMC +dXMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdp +dHMgUHR5IEx0ZDAeFw0xNzA5MTExNzQ2NDNaFw0yNzA5MDkxNzQ2NDNaMEUxCzAJ +BgNVBAYTAnVzMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l +dCBXaWRnaXRzIFB0eSBMdGQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATcqsBLxKP+ +UHk7Y6ktGGFvfrIfIXHxeZe3Xwt691CWfdmJFvrGzyzW5/AbJIuO1utdOsqUStAm +W/Scfxop/FGadKqR4nAWLNBI4intgnf0r1rzBCSOmanolHqxQPqQ0UOjgacwgaQw +HQYDVR0OBBYEFHxh1pTd8ApEzg0gKMwwt01aA10TMHUGA1UdIwRuMGyAFHxh1pTd +8ApEzg0gKMwwt01aA10ToUmkRzBFMQswCQYDVQQGEwJ1czETMBEGA1UECBMKU29t +ZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkggkAsz01 +srxZG4EwDAYDVR0TBAUwAwEB/zAJBgcqhkjOPQQBA2gAMGUCMCR+CvAoNBhqSe2M +4qWWD/9XX/0qmf0O442Qowcg5MWH1+mwl1s7ozinvbTPDPaYDwIxAM54qKhuL6xt +GxqJpa7Onn15Hu8zTsdzeYBqUUXA6wtn+Pa7197CgUkfty9yc2eeQw== +-----END CERTIFICATE-----` + + testCACert = ` +-----BEGIN CERTIFICATE----- +MIIC5zCCAc+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p +a3ViZUNBMB4XDTE3MDgxMDIzMTQ1NVoXDTI3MDgwODIzMTQ1NVowFTETMBEGA1UE +AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN8d +w2p/KXRkm+vzOO0eT1vYBWP7fKsnng9/g5nnXAJlt9NxpOSolRcyItm/04R0E1jx +jpgsdzkybc+QU5ZiszOYN833/D5hCNVAABVivpDd2P8wVKXN46cB99e24etUVBqG +5aR0Ku3IBsJjCN9efhF+XRCA2gy/KaXMdKJhHfdtc8hCr7G9+2wO2G58FLmIfEyH +owviOGt0BSnCtMpsA8ZgGQyfqgSd5u466aCv6oj0MyzsMnfS38niM53Rlv4IY6ol +taYbWXtCNndQ2S687qE0qTCxhE95Bm2Nfkqct4R1798sJz83xNv8hALvxr/vPK/J +2XkIm3oo3YKG4n/CHXcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW +MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQCSkrhE1PczqeqXfRaWayJUbXWPwKFbszO0MhGB1zwnPZq39qjY +ySQiGvnjV3fP+N5CTQAwMNe79Xiw31fSoexgceCPJpraWrTOLdCv04SbGDBapMFM +aezBu9jzZm0CNt60jHXWXuHHVPFX6u7ZR8W+RiBvsT8GZ5U6sNs3aN3M9Vym06BL +aSphIw1v+hRlPfnrlJwUnQp158DRgkt/9ncTa/k88KoIoZAbulaiGB4zHxxkbura +GSlgpZzhHSrBDLuXf65GHwwGxSExhgY5AA/n8rumGVvE8IYohS9yg/jOG0xP2WQH +u/ABoYtOyseO+lgElA8R4PB9MtwgN6c/b0xH +-----END CERTIFICATE-----` + + testOtherCACert = `-----BEGIN CERTIFICATE----- +MIIC/jCCAeagAwIBAgIBADANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwprdWJl +cm5ldGVzMB4XDTIyMTEyOTIwNDYwNVoXDTMyMTEyNjIwNDYwNVowFTETMBEGA1UE +AxMKa3ViZXJuZXRlczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOYO +AGi6hysYe4PF3sCyVXAFM72X96F51ncmDg+Ihhvt9Touj7Yo0LLMmy1UPM3YzEdV +z2IV1iksLIBObkct2eBGt2SFjLMhphZdA34mPAzFZhpvNgn1U2uUSqfp5phg00Mg +DdXLE0LFIVqAGkoBysr89l6P2MaTibcKoZwAhpMbATLcn1QcXF/NLzYuO9FPvrUL +mAR/HslDz10LBsMjtgRKXd2dX4yrQlYSB7YmLlu/bLKdjiE1a/+EO4wNcl/JJ+vu +fzPwxWALej/k61mcP+4JxfjgY53AM7vaZ+P9Yb0bGw7r1bozgP7+FGL1f6onTxeG +7+FECpErmrv9IQocgh8CAwEAAaNZMFcwDgYDVR0PAQH/BAQDAgKkMA8GA1UdEwEB +/wQFMAMBAf8wHQYDVR0OBBYEFE0Y+CYtusQDfr8tqSZZ7PEcDJjwMBUGA1UdEQQO +MAyCCmt1YmVybmV0ZXMwDQYJKoZIhvcNAQELBQADggEBAJK57wm9rH0yVmjmY1ES +kE8e+pTnXZkKaqUce/d7cPRn1+0Dtutvxl/j3P4DUjba7lVGYYNKp1xy2xVvg7Dl +mXyJigvBoTGyzJzNDIUJz8Kgse4eCrwl59WP94K83cVRLeUq+3amLwzubUNbezsW +QwcCyACuzTetR5ZXEg7iIS4HDy+ER2yjuY6d0GPLG+FH02WMrlE7mmxNfZOSy/5E +pEDcN/HcXM47TP7XgWW0rfQli3RucuqMV7LHvvpiGIWwfutrK9g7Py91W2JbQCA0 +D14XDzgsruCwlWAP1FMvLMIPhSknpIJd9Xql+0/Ae1yl9f3Uamj3mDtBKg8/U5nJ +0wU= +-----END CERTIFICATE----- +` +) diff --git a/path_config.go b/path_config.go index 2c46b9c0..40af5270 100644 --- a/path_config.go +++ b/path_config.go @@ -5,11 +5,9 @@ import ( "crypto" "crypto/ecdsa" "crypto/rsa" - "crypto/tls" "crypto/x509" "encoding/pem" "errors" - "net/http" "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/logical" @@ -122,6 +120,9 @@ func (b *kubeAuthBackend) pathConfigRead(ctx context.Context, req *logical.Reque // pathConfigWrite handles create and update commands to the config func (b *kubeAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { + b.l.Lock() + defer b.l.Unlock() + host := data.Get("kubernetes_host").(string) if host == "" { return logical.ErrorResponse("no host provided"), nil @@ -157,32 +158,6 @@ func (b *kubeAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Requ DisableLocalCAJwt: disableLocalJWT, } - b.l.Lock() - defer b.l.Unlock() - - // Determine if we load the local CA cert or the CA cert provided - // by the kubernetes_ca_cert path into the backend's HTTP client - certPool := x509.NewCertPool() - tlsConfig := &tls.Config{ - MinVersion: tls.VersionTLS12, - } - if disableLocalJWT || len(caCert) > 0 { - certPool.AppendCertsFromPEM([]byte(config.CACert)) - tlsConfig.RootCAs = certPool - - b.httpClient.Transport.(*http.Transport).TLSClientConfig = tlsConfig - } else { - localCACert, err := b.localCACertReader.ReadFile() - if err != nil { - return nil, err - } - - certPool.AppendCertsFromPEM([]byte(localCACert)) - tlsConfig.RootCAs = certPool - - b.httpClient.Transport.(*http.Transport).TLSClientConfig = tlsConfig - } - var err error for i, pem := range pemList { config.PublicKeys[i], err = parsePublicKeyPEM([]byte(pem)) @@ -191,6 +166,10 @@ func (b *kubeAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Requ } } + if err := b.updateTLSConfig(config); err != nil { + return logical.ErrorResponse(err.Error()), nil + } + entry, err := logical.StorageEntryJSON(configPath, config) if err != nil { return nil, err @@ -199,6 +178,7 @@ func (b *kubeAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Requ if err := req.Storage.Put(ctx, entry); err != nil { return nil, err } + return nil, nil } diff --git a/path_config_test.go b/path_config_test.go index 3ee9a9ad..335c49c1 100644 --- a/path_config_test.go +++ b/path_config_test.go @@ -535,84 +535,3 @@ func TestConfig_LocalJWTRenewal(t *testing.T) { t.Fatalf("got unexpected JWT: expected %#v\n got %#v\n", token2, conf.TokenReviewerJWT) } } - -var testLocalCACert string = `-----BEGIN CERTIFICATE----- -MIIDVDCCAjwCCQDFiyFY1M6afTANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQGEwJV -UzETMBEGA1UECAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEgMB4GA1UE -CgwXVmF1bHQgVGVzdGluZyBBdXRob3JpdHkxFDASBgNVBAMMC2V4YW1wbGUubmV0 -MB4XDTIwMDkxODAxMjkxM1oXDTQ1MDkxODAxMjkxM1owbDELMAkGA1UEBhMCVVMx -EzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxIDAeBgNVBAoM -F1ZhdWx0IFRlc3RpbmcgQXV0aG9yaXR5MRQwEgYDVQQDDAtleGFtcGxlLm5ldDCC -ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALCA9oKv+ESRHX2e/iq1PlGr -zD23/MBS0V+fWQDY0hyEqY98CGwRtF6pEcLEYsreArj5/zznsIevLkNOD+beg43y -WpEJlCPgDhGXI/Oima6ooHVEIMaIKLjK7GrSzAb3rNRGACwrR/u/IKaFl+XJG0qx -g8mOZ3fByaAlIk+shVLUcIedNN1tNR+6/4ZpHg7PDjrZXP4XKrmKPTh4yqfu+BtZ -9IY2oyregqEsGW1/3h1NM+LHGVakTV2d/mwMYHhwoq9Y8BD+PemT5z8TmhH/cIk5 -P8Q8ud5/q6YTIJg9TELKebLAeNtRNnNoHeUoRTjiW1MBwNHtgyTTY+H3W/9Dne0C -AwEAATANBgkqhkiG9w0BAQsFAAOCAQEAXmygFkGIBnXxKlsTDiV8RW2iHLgFdZFJ -hcU8UpxZhhaL5JbQl6byfbHjrX31q7ii8uC8FcbW0AEdnEQAb9Ui6a+if7HwXNmI -DTlYl+lMlk9RtWvExw6AEEbg5nCpGaKexm7wJgzYGP9by9pQ7wX/CS7ofCzCK+Al -uSIqjPkMC201ZXH39n1lxxq6BacdYjv8wo4mMzi8iTSQGVWPdjHZVYOClFgN6hoj -8SkrrSe888a0H+i7EknRxC4sLRaMUK/FAvwtXaSZi2djruAtQzQGQ56m1phC2C/k -k9aL00AQ9Y4KTfiJD7LK8YIZDnFKLOCJhYgKCLCOVwOHb7836SNCxA== ------END CERTIFICATE-----` - -var testLocalJWT string = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlZhdWx0IFRlc3QiLCJpYXQiOjExMjM1OH0.GOC8w-MyhorgojB20SPNyH_ECsBjYJH89hjntOxSywA` - -var testRSACert string = `-----BEGIN CERTIFICATE----- -MIIDcjCCAlqgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p -a3ViZUNBMB4XDTE3MDgzMDE5MDgzNloXDTE4MDgzMDE5MDgzNlowLDEXMBUGA1UE -ChMOc3lzdGVtOm1hc3RlcnMxETAPBgNVBAMTCG1pbmlrdWJlMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxD3eM3+WNc4phxAeQxNOmcybKlNJWowuC12u -v+cGJWxxpDx/OoEIxKI5wmgHxEwFCZL545sjfLqyBcgxQR2xSCib+bYzjBtfA6uV -6d/35nurzz21okcMffc5xKMyZhEwt98WAvYWD71Bihz7iGBq5Sw9md6pqnkNoScR -Hhi3Vl94a6D6shwb6nXA2hlwYLcnoKtpe3Ptq6MW6CpfBA8C11q5eeW4xdvrwKt3 -Vd1TgFeEnnqwzUWGapU2uwwUfbRkLTDvrp6791uq0Vo7mzz00xYhV1PLCeAdpJEK -3Vr74FT7jHIbPlzi/qjRBVFKf9IRXnhbjrCl7S0Ayev1Fao4TQIDAQABo4G1MIGy -MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw -DAYDVR0TAQH/BAIwADBzBgNVHREEbDBqgiRrdWJlcm5ldGVzLmRlZmF1bHQuc3Zj -LmNsdXN0ZXIubG9jYWyCFmt1YmVybmV0ZXMuZGVmYXVsdC5zdmOCEmt1YmVybmV0 -ZXMuZGVmYXVsdIIKa3ViZXJuZXRlc4cEwKhjZIcECgAAATANBgkqhkiG9w0BAQsF -AAOCAQEAIw8rKuryhhl527wf9q/VrWixzZ1jCLvyc/60z9rWpXxKFxT8AyCsHirM -F4fHXW4Brcoh/Dc2ci36cUbuywIyxHjgVUG45D4jPPWskY1++ZSfJfSXAuA8eFew -c+No3WPkmZB6ZOZ6q5iPY+FOgDZC7ddWmGuZrle51gBL347cU7H1BrTm6Lm6kXRs -fHRZJX2+B8lnsXsS3QF2BTU0ymuCxCCQxub/GhPZVz3nNNtro1z7/szLUVP1c1/8 -p7HP3k7caxfp346TZ/HgbV9sJEkHP7Ym7n9E7LSyUTSxXwBRPraH1WQzEgFNPSUV -V0n6FBLiejOTPKapJ2F0tIqAyJHFug== ------END CERTIFICATE-----` - -var testECCert string = `-----BEGIN CERTIFICATE----- -MIICZDCCAeugAwIBAgIJALM9NbK8WRuBMAkGByqGSM49BAEwRTELMAkGA1UEBhMC -dXMxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdp -dHMgUHR5IEx0ZDAeFw0xNzA5MTExNzQ2NDNaFw0yNzA5MDkxNzQ2NDNaMEUxCzAJ -BgNVBAYTAnVzMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5l -dCBXaWRnaXRzIFB0eSBMdGQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATcqsBLxKP+ -UHk7Y6ktGGFvfrIfIXHxeZe3Xwt691CWfdmJFvrGzyzW5/AbJIuO1utdOsqUStAm -W/Scfxop/FGadKqR4nAWLNBI4intgnf0r1rzBCSOmanolHqxQPqQ0UOjgacwgaQw -HQYDVR0OBBYEFHxh1pTd8ApEzg0gKMwwt01aA10TMHUGA1UdIwRuMGyAFHxh1pTd -8ApEzg0gKMwwt01aA10ToUmkRzBFMQswCQYDVQQGEwJ1czETMBEGA1UECBMKU29t -ZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkggkAsz01 -srxZG4EwDAYDVR0TBAUwAwEB/zAJBgcqhkjOPQQBA2gAMGUCMCR+CvAoNBhqSe2M -4qWWD/9XX/0qmf0O442Qowcg5MWH1+mwl1s7ozinvbTPDPaYDwIxAM54qKhuL6xt -GxqJpa7Onn15Hu8zTsdzeYBqUUXA6wtn+Pa7197CgUkfty9yc2eeQw== ------END CERTIFICATE-----` - -var testCACert string = ` ------BEGIN CERTIFICATE----- -MIIC5zCCAc+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p -a3ViZUNBMB4XDTE3MDgxMDIzMTQ1NVoXDTI3MDgwODIzMTQ1NVowFTETMBEGA1UE -AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN8d -w2p/KXRkm+vzOO0eT1vYBWP7fKsnng9/g5nnXAJlt9NxpOSolRcyItm/04R0E1jx -jpgsdzkybc+QU5ZiszOYN833/D5hCNVAABVivpDd2P8wVKXN46cB99e24etUVBqG -5aR0Ku3IBsJjCN9efhF+XRCA2gy/KaXMdKJhHfdtc8hCr7G9+2wO2G58FLmIfEyH -owviOGt0BSnCtMpsA8ZgGQyfqgSd5u466aCv6oj0MyzsMnfS38niM53Rlv4IY6ol -taYbWXtCNndQ2S687qE0qTCxhE95Bm2Nfkqct4R1798sJz83xNv8hALvxr/vPK/J -2XkIm3oo3YKG4n/CHXcCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW -MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 -DQEBCwUAA4IBAQCSkrhE1PczqeqXfRaWayJUbXWPwKFbszO0MhGB1zwnPZq39qjY -ySQiGvnjV3fP+N5CTQAwMNe79Xiw31fSoexgceCPJpraWrTOLdCv04SbGDBapMFM -aezBu9jzZm0CNt60jHXWXuHHVPFX6u7ZR8W+RiBvsT8GZ5U6sNs3aN3M9Vym06BL -aSphIw1v+hRlPfnrlJwUnQp158DRgkt/9ncTa/k88KoIoZAbulaiGB4zHxxkbura -GSlgpZzhHSrBDLuXf65GHwwGxSExhgY5AA/n8rumGVvE8IYohS9yg/jOG0xP2WQH -u/ABoYtOyseO+lgElA8R4PB9MtwgN6c/b0xH ------END CERTIFICATE-----` diff --git a/path_login.go b/path_login.go index ad745a2e..819020cc 100644 --- a/path_login.go +++ b/path_login.go @@ -129,8 +129,14 @@ func (b *kubeAuthBackend) pathLogin(ctx context.Context, req *logical.Request, d return nil, err } + client, err := b.getHTTPClient() + if err != nil { + b.Logger().Error(`Failed to get the HTTP client`, "err", err) + return nil, logical.ErrUnrecoverable + } + // look up the JWT token in the kubernetes API - err = serviceAccount.lookup(ctx, b.httpClient, jwtStr, b.reviewFactory(config)) + err = serviceAccount.lookup(ctx, client, jwtStr, b.reviewFactory(config)) if err != nil { b.Logger().Debug(`login unauthorized`, "err", err) diff --git a/path_role_test.go b/path_role_test.go index 3b63113c..317b34c6 100644 --- a/path_role_test.go +++ b/path_role_test.go @@ -19,6 +19,9 @@ func getBackend(t *testing.T) (logical.Backend, logical.Storage) { defaultLeaseTTLVal := time.Hour * 12 maxLeaseTTLVal := time.Hour * 24 b := Backend() + if err := b.validateHTTPClientInit(); err != nil { + t.Fatalf("unable to create backend: %v", err) + } config := &logical.BackendConfig{ Logger: logging.NewVaultLogger(log.Trace), @@ -29,9 +32,8 @@ func getBackend(t *testing.T) (logical.Backend, logical.Storage) { }, StorageView: &logical.InmemStorage{}, } - err := b.Setup(context.Background(), config) - if err != nil { - t.Fatalf("unable to create backend: %v", err) + if err := b.Setup(context.Background(), config); err != nil { + t.Fatalf("unable to setup backend: %v", err) } return b, config.StorageView