From 920b519ebf7fe0e79c374e9f39ded350067834a7 Mon Sep 17 00:00:00 2001 From: Ryan Richard Date: Fri, 19 Jul 2024 16:18:52 -0700 Subject: [PATCH] error when CA bundle from Secret or ConfigMap is empty Co-authored-by: Joshua Casey --- .../cachecleaner/cachecleaner_test.go | 1 - .../jwtcachefiller/jwtcachefiller.go | 2 +- .../webhookcachefiller/webhookcachefiller.go | 2 +- .../tlsconfigutil/tls_config_util.go | 80 +-- .../tlsconfigutil/tls_config_util_test.go | 466 ++++++++---------- 5 files changed, 238 insertions(+), 313 deletions(-) diff --git a/internal/controller/authenticator/cachecleaner/cachecleaner_test.go b/internal/controller/authenticator/cachecleaner/cachecleaner_test.go index 3602c0b21..214e65f30 100644 --- a/internal/controller/authenticator/cachecleaner/cachecleaner_test.go +++ b/internal/controller/authenticator/cachecleaner/cachecleaner_test.go @@ -155,7 +155,6 @@ func TestController(t *testing.T) { defer cancel() informers.Start(ctx.Done()) - informers.WaitForCacheSync(ctx.Done()) controllerlib.TestRunSynchronously(t, controller) syncCtx := controllerlib.Context{ diff --git a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go index 906e6a894..7e1d6cc48 100644 --- a/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go +++ b/internal/controller/authenticator/jwtcachefiller/jwtcachefiller.go @@ -306,7 +306,7 @@ func (c *jwtCacheFillerController) cacheValueAsJWTAuthenticator(value authncache func (c *jwtCacheFillerController) validateTLSBundle(tlsSpec *authenticationv1alpha1.TLSSpec, conditions []*metav1.Condition) (*x509.CertPool, []*metav1.Condition, []byte, bool) { condition, pemBundle, rootCAs := tlsconfigutil.ValidateTLSConfig( - tlsconfigutil.TlsSpecForConcierge(tlsSpec), + tlsconfigutil.TLSSpecForConcierge(tlsSpec), "spec.tls", c.namespace, c.secretInformer, diff --git a/internal/controller/authenticator/webhookcachefiller/webhookcachefiller.go b/internal/controller/authenticator/webhookcachefiller/webhookcachefiller.go index 40d7c66bc..2074795ff 100644 --- a/internal/controller/authenticator/webhookcachefiller/webhookcachefiller.go +++ b/internal/controller/authenticator/webhookcachefiller/webhookcachefiller.go @@ -341,7 +341,7 @@ func (c *webhookCacheFillerController) validateConnection(certPool *x509.CertPoo func (c *webhookCacheFillerController) validateTLSBundle(tlsSpec *authenticationv1alpha1.TLSSpec, conditions []*metav1.Condition) (*x509.CertPool, []byte, []*metav1.Condition, bool) { condition, pemBytes, rootCAs := tlsconfigutil.ValidateTLSConfig( - tlsconfigutil.TlsSpecForConcierge(tlsSpec), + tlsconfigutil.TLSSpecForConcierge(tlsSpec), "spec.tls", c.namespace, c.secretInformer, diff --git a/internal/controller/tlsconfigutil/tls_config_util.go b/internal/controller/tlsconfigutil/tls_config_util.go index cb996db96..121609517 100644 --- a/internal/controller/tlsconfigutil/tls_config_util.go +++ b/internal/controller/tlsconfigutil/tls_config_util.go @@ -64,8 +64,8 @@ func TLSSpecForSupervisor(source *idpv1alpha1.TLSSpec) *TLSSpec { return dest } -// TlsSpecForConcierge is a helper function to convert the Concierge's TLSSpec to the unified TLSSpec. -func TlsSpecForConcierge(source *authenticationv1alpha1.TLSSpec) *TLSSpec { +// TLSSpecForConcierge is a helper function to convert the Concierge's TLSSpec to the unified TLSSpec. +func TLSSpecForConcierge(source *authenticationv1alpha1.TLSSpec) *TLSSpec { if source == nil { return nil } @@ -82,6 +82,30 @@ func TlsSpecForConcierge(source *authenticationv1alpha1.TLSSpec) *TLSSpec { return dest } +// ValidateTLSConfig reads ca bundle in the tlsSpec, supplied either inline using the CertificateAuthorityDate +// or as a reference to a kubernetes secret or configmap using the CertificateAuthorityDataSource, and returns +// - a condition of type TLSConfigurationValid based on the validity of the ca bundle, +// - a pem encoded ca bundle +// - a X509 cert pool with the ca bundle +// TODO: should this show the resource version of the Secret/ConfigMap to the user on all conditions? +func ValidateTLSConfig( + tlsSpec *TLSSpec, + conditionPrefix string, + namespace string, + secretInformer corev1informers.SecretInformer, + configMapInformer corev1informers.ConfigMapInformer, +) (*metav1.Condition, []byte, *x509.CertPool) { + certPool, bundle, err := getCertPool(tlsSpec, conditionPrefix, namespace, secretInformer, configMapInformer) + if err != nil { + return invalidTLSCondition(err.Error()), nil, nil + } + if bundle == nil { + // An empty or nil CA bundle results in a valid TLS condition which indicates that no CA data was supplied. + return validTLSCondition(fmt.Sprintf("%s is valid: %s", conditionPrefix, noTLSConfigurationMessage)), nil, nil + } + return validTLSCondition(fmt.Sprintf("%s is valid: %s", conditionPrefix, loadedTLSConfigurationMessage)), bundle, certPool +} + // getCertPool reads the unified tlsSpec and returns an X509 cert pool with the CA data that is read either from // the inline tls.certificateAuthorityData or from a kubernetes secret or a config map as specified in the // tls.certificateAuthorityDataSource. @@ -147,30 +171,6 @@ func getCertPool( return ca, bundleBytes, nil } -// ValidateTLSConfig reads ca bundle in the tlsSpec, supplied either inline using the CertificateAuthorityDate -// or as a reference to a kubernetes secret or configmap using the CertificateAuthorityDataSource, and returns -// - a condition of type TLSConfigurationValid based on the validity of the ca bundle, -// - a pem encoded ca bundle -// - a X509 cert pool with the ca bundle -// TODO: should this show the resource version of the Secret/ConfigMap to the user on all conditions? -func ValidateTLSConfig( - tlsSpec *TLSSpec, - conditionPrefix string, - namespace string, - secretInformer corev1informers.SecretInformer, - configMapInformer corev1informers.ConfigMapInformer, -) (*metav1.Condition, []byte, *x509.CertPool) { - certPool, bundle, err := getCertPool(tlsSpec, conditionPrefix, namespace, secretInformer, configMapInformer) - if err != nil { - return invalidTLSCondition(err.Error()), nil, nil - } - if bundle == nil { - // An empty or nil CA bundle results in a valid TLS condition which indicates that no CA data was supplied. - return validTLSCondition(fmt.Sprintf("%s is valid: %s", conditionPrefix, noTLSConfigurationMessage)), nil, nil - } - return validTLSCondition(fmt.Sprintf("%s is valid: %s", conditionPrefix, loadedTLSConfigurationMessage)), bundle, certPool -} - func readCABundleFromSource(source *caBundleSource, namespace string, secretInformer corev1informers.SecretInformer, configMapInformer corev1informers.ConfigMapInformer) (string, error) { switch source.Kind { case "Secret": @@ -189,17 +189,21 @@ func readCABundleFromK8sSecret(namespace string, name string, key string, secret if err != nil { return "", errors.Wrapf(err, "failed to get secret %q", namespacedName) } - // for kubernetes secrets to be used as a certificate authority data source, the secret should be of type + + // For Secrets to be used as a certificate authority data source, the secret should be of type // kubernetes.io/tls or Opaque. It is an error to use a secret that is of any other type. if s.Type != corev1.SecretTypeTLS && s.Type != corev1.SecretTypeOpaque { return "", fmt.Errorf("secret %q of type %q cannot be used as a certificate authority data source", namespacedName, s.Type) } - // ca bundle in the secret is expected to exist in a specific key, if that key does not exist, then it is an error - if val, exists := s.Data[key]; exists { - // TODO: if val is an empty string, it should be an error - return string(val), nil + + val, exists := s.Data[key] + if !exists { + return "", fmt.Errorf("key %q not found in secret %q", key, namespacedName) } - return "", fmt.Errorf("key %q not found in secret %q", key, namespacedName) + if len(val) == 0 { + return "", fmt.Errorf("key %q has empty value in secret %q", key, namespacedName) + } + return string(val), nil } func readCABundleFromK8sConfigMap(namespace string, name string, key string, configMapInformer corev1informers.ConfigMapInformer) (string, error) { @@ -210,12 +214,14 @@ func readCABundleFromK8sConfigMap(namespace string, name string, key string, con return "", errors.Wrapf(err, "failed to get configmap %q", namespacedName) } - // ca bundle in the secret is expected to exist in a specific key, if that key does not exist, then it is an error - if val, exists := c.Data[key]; exists { - // TODO: if val is an empty string, it should be an error - return val, nil + val, exists := c.Data[key] + if !exists { + return "", fmt.Errorf("key %q not found in configmap %q", key, namespacedName) } - return "", fmt.Errorf("key %q not found in configmap %q", key, namespacedName) + if len(val) == 0 { + return "", fmt.Errorf("key %q has empty value in configmap %q", key, namespacedName) + } + return val, nil } func validTLSCondition(message string) *metav1.Condition { diff --git a/internal/controller/tlsconfigutil/tls_config_util_test.go b/internal/controller/tlsconfigutil/tls_config_util_test.go index 8d94a9bc4..25b11f1e8 100644 --- a/internal/controller/tlsconfigutil/tls_config_util_test.go +++ b/internal/controller/tlsconfigutil/tls_config_util_test.go @@ -5,6 +5,7 @@ package tlsconfigutil import ( "context" + "crypto/x509" "encoding/base64" "testing" "time" @@ -27,12 +28,17 @@ func TestValidateTLSConfig(t *testing.T) { testCA, err := certauthority.New("Test CA", 1*time.Hour) require.NoError(t, err) bundle := testCA.Bundle() + certPool := x509.NewCertPool() + require.True(t, certPool.AppendCertsFromPEM(bundle)) base64EncodedBundle := base64.StdEncoding.EncodeToString(bundle) + tests := []struct { name string tlsSpec *TLSSpec namespace string k8sObjects []runtime.Object + expectedBundle []byte + expectedCertPool *x509.CertPool expectedCondition *metav1.Condition }{ { @@ -60,6 +66,8 @@ func TestValidateTLSConfig(t *testing.T) { tlsSpec: &TLSSpec{ CertificateAuthorityData: base64EncodedBundle, }, + expectedBundle: bundle, + expectedCertPool: certPool, expectedCondition: &metav1.Condition{ Type: typeTLSConfigurationValid, Status: metav1.ConditionTrue, @@ -130,6 +138,8 @@ func TestValidateTLSConfig(t *testing.T) { }, }, }, + expectedBundle: bundle, + expectedCertPool: certPool, expectedCondition: &metav1.Condition{ Type: typeTLSConfigurationValid, Status: metav1.ConditionTrue, @@ -159,6 +169,8 @@ func TestValidateTLSConfig(t *testing.T) { }, }, }, + expectedBundle: bundle, + expectedCertPool: certPool, expectedCondition: &metav1.Condition{ Type: typeTLSConfigurationValid, Status: metav1.ConditionTrue, @@ -166,7 +178,6 @@ func TestValidateTLSConfig(t *testing.T) { Message: "tls is valid: loaded TLS configuration", }, }, - { name: "should return invalid condition when a secrets not of type tls or opaque are used as ca data source", tlsSpec: &TLSSpec{ @@ -196,7 +207,120 @@ func TestValidateTLSConfig(t *testing.T) { Message: `tls.certificateAuthorityDataSource is invalid: secret "awesome-namespace/awesome-secret-ba" of type "kubernetes.io/basic-auth" cannot be used as a certificate authority data source`, }, }, - + { + name: "should return invalid condition when a secret does not have the configured key", + tlsSpec: &TLSSpec{ + CertificateAuthorityDataSource: &caBundleSource{ + Kind: "Secret", + Name: "awesome-secret", + Key: "ca-bundle", + }, + }, + namespace: "awesome-namespace", + k8sObjects: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "awesome-secret", + Namespace: "awesome-namespace", + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "wrong-key": bundle, + }, + }, + }, + expectedCondition: &metav1.Condition{ + Type: typeTLSConfigurationValid, + Status: metav1.ConditionFalse, + Reason: ReasonInvalidTLSConfig, + Message: `tls.certificateAuthorityDataSource is invalid: key "ca-bundle" not found in secret "awesome-namespace/awesome-secret"`, + }, + }, + { + name: "should return invalid condition when a secret has the configured key but its value is empty", + tlsSpec: &TLSSpec{ + CertificateAuthorityDataSource: &caBundleSource{ + Kind: "Secret", + Name: "awesome-secret", + Key: "ca-bundle", + }, + }, + namespace: "awesome-namespace", + k8sObjects: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "awesome-secret", + Namespace: "awesome-namespace", + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "ca-bundle": []byte(""), + }, + }, + }, + expectedCondition: &metav1.Condition{ + Type: typeTLSConfigurationValid, + Status: metav1.ConditionFalse, + Reason: ReasonInvalidTLSConfig, + Message: `tls.certificateAuthorityDataSource is invalid: key "ca-bundle" has empty value in secret "awesome-namespace/awesome-secret"`, + }, + }, + { + name: "should return invalid condition when a configmap does not have the configured key", + tlsSpec: &TLSSpec{ + CertificateAuthorityDataSource: &caBundleSource{ + Kind: "ConfigMap", + Name: "awesome-configmap", + Key: "ca-bundle", + }, + }, + namespace: "awesome-namespace", + k8sObjects: []runtime.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "awesome-configmap", + Namespace: "awesome-namespace", + }, + Data: map[string]string{ + "wrong-key": string(bundle), + }, + }, + }, + expectedCondition: &metav1.Condition{ + Type: typeTLSConfigurationValid, + Status: metav1.ConditionFalse, + Reason: ReasonInvalidTLSConfig, + Message: `tls.certificateAuthorityDataSource is invalid: key "ca-bundle" not found in configmap "awesome-namespace/awesome-configmap"`, + }, + }, + { + name: "should return invalid condition when a configmap has the configured key but its value is empty", + tlsSpec: &TLSSpec{ + CertificateAuthorityDataSource: &caBundleSource{ + Kind: "ConfigMap", + Name: "awesome-configmap", + Key: "ca-bundle", + }, + }, + namespace: "awesome-namespace", + k8sObjects: []runtime.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "awesome-configmap", + Namespace: "awesome-namespace", + }, + Data: map[string]string{ + "ca-bundle": "", + }, + }, + }, + expectedCondition: &metav1.Condition{ + Type: typeTLSConfigurationValid, + Status: metav1.ConditionFalse, + Reason: ReasonInvalidTLSConfig, + Message: `tls.certificateAuthorityDataSource is invalid: key "ca-bundle" has empty value in configmap "awesome-namespace/awesome-configmap"`, + }, + }, { name: "should return ca bundle from kubernetes configMap", tlsSpec: &TLSSpec{ @@ -218,6 +342,8 @@ func TestValidateTLSConfig(t *testing.T) { }, }, }, + expectedBundle: bundle, + expectedCertPool: certPool, expectedCondition: &metav1.Condition{ Type: typeTLSConfigurationValid, Status: metav1.ConditionTrue, @@ -234,18 +360,8 @@ func TestValidateTLSConfig(t *testing.T) { Key: "does-not-matter", }, }, - namespace: "awesome-namespace", - k8sObjects: []runtime.Object{ - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "awesome-cm", - Namespace: "awesome-namespace", - }, - Data: map[string]string{ - "ca-bundle": string(bundle), - }, - }, - }, + namespace: "awesome-namespace", + k8sObjects: []runtime.Object{}, expectedCondition: &metav1.Condition{ Type: typeTLSConfigurationValid, Status: metav1.ConditionFalse, @@ -262,18 +378,8 @@ func TestValidateTLSConfig(t *testing.T) { Key: "does-not-matter", }, }, - namespace: "awesome-namespace", - k8sObjects: []runtime.Object{ - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "awesome-cm", - Namespace: "awesome-namespace", - }, - Data: map[string]string{ - "ca-bundle": string(bundle), - }, - }, - }, + namespace: "awesome-namespace", + k8sObjects: []runtime.Object{}, expectedCondition: &metav1.Condition{ Type: typeTLSConfigurationValid, Status: metav1.ConditionFalse, @@ -317,265 +423,40 @@ func TestValidateTLSConfig(t *testing.T) { var secretsInformer corev1informers.SecretInformer var configMapInformer corev1informers.ConfigMapInformer - if len(tt.k8sObjects) > 0 { - fakeClient := fake.NewSimpleClientset(tt.k8sObjects...) - sharedInformers := informers.NewSharedInformerFactory(fakeClient, 0) - configMapInformer = sharedInformers.Core().V1().ConfigMaps() - secretsInformer = sharedInformers.Core().V1().Secrets() + fakeClient := fake.NewSimpleClientset(tt.k8sObjects...) + sharedInformers := informers.NewSharedInformerFactory(fakeClient, 0) + configMapInformer = sharedInformers.Core().V1().ConfigMaps() + secretsInformer = sharedInformers.Core().V1().Secrets() - // calling the .Informer function registers this informer in the sharedinformer. - // doing this will ensure that this informer will be sync'd when Start is called next. - configMapInformer.Informer() - secretsInformer.Informer() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - sharedInformers.Start(ctx.Done()) - sharedInformers.WaitForCacheSync(ctx.Done()) - } - actualCondition, _, _ := ValidateTLSConfig(tt.tlsSpec, "tls", tt.namespace, secretsInformer, configMapInformer) - require.Equal(t, tt.expectedCondition, actualCondition) - }) - } -} - -func TestReadCABundleFromK8sSecret(t *testing.T) { - tests := []struct { - name string - secretNamespace string - secretName string - secretKey string - k8sObjects []runtime.Object - expectedData string - expectError string - }{ - { - name: "should return data from existing tls secret and existing key", - secretNamespace: "awesome-namespace", - secretName: "awesome-secret", - secretKey: "awesome", - k8sObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "awesome-secret", - Namespace: "awesome-namespace", - }, - Type: corev1.SecretTypeTLS, - Data: map[string][]byte{ - "awesome": []byte("pinniped-is-awesome"), - }, - }, - }, - expectedData: "pinniped-is-awesome", - expectError: "", - }, - { - name: "should return data from existing opaque secret and existing key", - secretNamespace: "awesome-namespace", - secretName: "awesome-secret", - secretKey: "awesome", - k8sObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "awesome-secret", - Namespace: "awesome-namespace", - }, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{ - "awesome": []byte("pinniped-is-awesome"), - }, - }, - }, - expectedData: "pinniped-is-awesome", - expectError: "", - }, - { - name: "should return error reading a non-existent secret", - secretNamespace: "awesome-namespace", - secretName: "does-not-exist", - secretKey: "does-not-matter", - k8sObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "awesome-secret", - Namespace: "awesome-namespace", - }, - Data: map[string][]byte{ - "awesome": []byte("pinniped-is-awesome"), - }, - }, - }, - expectedData: "", - expectError: `failed to get secret "awesome-namespace/does-not-exist": secret "does-not-exist" not found`, - }, - { - name: "should return error when secret has wrong type", - secretNamespace: "awesome-namespace", - secretName: "awesome-secret", - secretKey: "awesome", - k8sObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "awesome-secret", - Namespace: "awesome-namespace", - }, - Type: "other-type", - Data: map[string][]byte{ - "awesome": []byte("pinniped-is-awesome"), - }, - }, - }, - - expectError: `secret "awesome-namespace/awesome-secret" of type "other-type" cannot be used as a certificate authority data source`, - }, - { - name: "should return error reading a non-existing key in an existing secret", - secretNamespace: "awesome-namespace", - secretName: "awesome-secret", - secretKey: "something-else", - k8sObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "awesome-secret", - Namespace: "awesome-namespace", - }, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{ - "awesome": []byte("pinniped-is-awesome"), - }, - }, - }, - expectedData: "", - expectError: `key "something-else" not found in secret "awesome-namespace/awesome-secret"`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - sharedInformers := informers.NewSharedInformerFactory(fake.NewSimpleClientset(tt.k8sObjects...), 0) - secretsInformer := sharedInformers.Core().V1().Secrets() - - // calling the .Informer function registers this informer in the sharedinformer. - // doing this will ensure that this informer will be sync'd when Start is called next. + // Calling the Informer() function registers this informer in the sharedinformer. + // Doing this will ensure that this informer will be sync'd when Start() is called. + // This is needed in this test because we are not using the controller library here, + // which would do these same calls for us. + configMapInformer.Informer() secretsInformer.Informer() ctx, cancel := context.WithCancel(context.Background()) defer cancel() sharedInformers.Start(ctx.Done()) + // This is needed in this test because we are not using the controller library here, + // which would do this same call for us. sharedInformers.WaitForCacheSync(ctx.Done()) - // now the objects from kubernetes should be sync'd into the informer cache. - actualData, actualError := readCABundleFromK8sSecret(tt.secretNamespace, tt.secretName, tt.secretKey, secretsInformer) - if tt.expectError != "" { - require.ErrorContains(t, actualError, tt.expectError) - } else { - require.NoError(t, actualError) - } - require.Equal(t, tt.expectedData, actualData) + + actualCondition, actualBundle, actualCertPool := ValidateTLSConfig(tt.tlsSpec, "tls", tt.namespace, secretsInformer, configMapInformer) + + require.Equal(t, tt.expectedCondition, actualCondition) + require.Equal(t, tt.expectedBundle, actualBundle) + require.True(t, tt.expectedCertPool.Equal(actualCertPool), "expectedCertPool did not equal actualCertPool") }) } } -func TestReadCABundleFromK8sConfigMap(t *testing.T) { - tests := []struct { - name string - configMapNamespace string - configMapName string - configMapKey string - k8sObjects []runtime.Object - expectedData string - expectError string - }{ - { - name: "should return expected data from an existing key in an existing configMap", - configMapNamespace: "awesome-namespace", - configMapName: "awesome-configmap", - configMapKey: "awesome", - k8sObjects: []runtime.Object{ - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "awesome-configmap", - Namespace: "awesome-namespace", - }, - Data: map[string]string{ - "awesome": "pinniped-is-awesome", - }, - }, - }, - expectedData: "pinniped-is-awesome", - }, - { - name: "should return error reading a non-existent configMap", - configMapNamespace: "awesome-namespace", - configMapName: "does-not-exist", - configMapKey: "does-not-matter", - k8sObjects: []runtime.Object{ - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "awesome-configmap", - Namespace: "awesome-namespace", - }, - Data: map[string]string{ - "awesome": "pinniped-is-awesome", - }, - }, - }, - expectedData: "", - expectError: `failed to get configmap "awesome-namespace/does-not-exist": configmap "does-not-exist" not found`, - }, - { - name: "should return error reading a non-existing key in an existing configMap", - configMapNamespace: "awesome-namespace", - configMapName: "awesome-configmap", - configMapKey: "does-not-exist", - k8sObjects: []runtime.Object{ - &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "awesome-configmap", - Namespace: "awesome-namespace", - }, - Data: map[string]string{ - "awesome": "pinniped-is-awesome", - }, - }, - }, - expectedData: "", - expectError: `key "does-not-exist" not found in configmap "awesome-namespace/awesome-configmap"`, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - fakeClient := fake.NewSimpleClientset(tt.k8sObjects...) - - sharedInformers := informers.NewSharedInformerFactory(fakeClient, 0) - configMapInformer := sharedInformers.Core().V1().ConfigMaps() - - // calling the .Informer function registers this informer in the sharedinformer. - // doing this will ensure that this informer will be sync'd when Start is called next. - configMapInformer.Informer() - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - sharedInformers.Start(ctx.Done()) - sharedInformers.WaitForCacheSync(ctx.Done()) - actualData, actualError := readCABundleFromK8sConfigMap(tt.configMapNamespace, tt.configMapName, tt.configMapKey, configMapInformer) - if tt.expectError != "" { - require.ErrorContains(t, actualError, tt.expectError) - } else { - require.NoError(t, actualError) - } - require.Equal(t, tt.expectedData, actualData) - }) - } -} - -func TestNewCommonTLSSpecForSupervisor(t *testing.T) { +func TestTLSSpecForSupervisor(t *testing.T) { testCA, err := certauthority.New("Test CA", 1*time.Hour) require.NoError(t, err) bundle := testCA.Bundle() base64EncodedBundle := base64.StdEncoding.EncodeToString(bundle) + tests := []struct { name string supervisorTLSSpec *idpv1alpha1.TLSSpec @@ -614,6 +495,25 @@ func TestNewCommonTLSSpecForSupervisor(t *testing.T) { }, }, }, + { + name: "should return tls spec when source has all fields filled", + supervisorTLSSpec: &idpv1alpha1.TLSSpec{ + CertificateAuthorityData: base64EncodedBundle, + CertificateAuthorityDataSource: &idpv1alpha1.CABundleSource{ + Kind: "Secret", + Name: "awesome-secret", + Key: "ca-bundle", + }, + }, + expected: &TLSSpec{ + CertificateAuthorityData: base64EncodedBundle, + CertificateAuthorityDataSource: &caBundleSource{ + Kind: "Secret", + Name: "awesome-secret", + Key: "ca-bundle", + }, + }, + }, } for _, tt := range tests { @@ -625,18 +525,19 @@ func TestNewCommonTLSSpecForSupervisor(t *testing.T) { } } -func TestNewCommonTlsSpecForConcierge(t *testing.T) { +func TestTLSSpecForConcierge(t *testing.T) { testCA, err := certauthority.New("Test CA", 1*time.Hour) require.NoError(t, err) bundle := testCA.Bundle() base64EncodedBundle := base64.StdEncoding.EncodeToString(bundle) + tests := []struct { name string conciergeTLSSpec *authenticationv1alpha1.TLSSpec expected *TLSSpec }{ { - name: "should return nil spec when supervisorTLSSpec is nil", + name: "should return nil spec when TLSSpec is nil", conciergeTLSSpec: nil, expected: nil, }, @@ -668,12 +569,31 @@ func TestNewCommonTlsSpecForConcierge(t *testing.T) { }, }, }, + { + name: "should return tls spec when source has all fields filled", + conciergeTLSSpec: &authenticationv1alpha1.TLSSpec{ + CertificateAuthorityData: base64EncodedBundle, + CertificateAuthorityDataSource: &authenticationv1alpha1.CABundleSource{ + Kind: "Secret", + Name: "awesome-secret", + Key: "ca-bundle", + }, + }, + expected: &TLSSpec{ + CertificateAuthorityData: base64EncodedBundle, + CertificateAuthorityDataSource: &caBundleSource{ + Kind: "Secret", + Name: "awesome-secret", + Key: "ca-bundle", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - actual := TlsSpecForConcierge(tt.conciergeTLSSpec) + actual := TLSSpecForConcierge(tt.conciergeTLSSpec) require.Equal(t, tt.expected, actual) }) }