diff --git a/Makefile b/Makefile index 0eee1f26..de7c4b44 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