mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-01-10 07:58:07 +00:00
error when CA bundle from Secret or ConfigMap is empty
Co-authored-by: Joshua Casey <joshuatcasey@gmail.com>
This commit is contained in:
@@ -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{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user