diff --git a/pkg/redpanda/client.go b/pkg/redpanda/client.go index bb886e76b..4d66daeff 100644 --- a/pkg/redpanda/client.go +++ b/pkg/redpanda/client.go @@ -56,10 +56,10 @@ func AdminClient(dot *helmette.Dot, dialer DialContextFunc) (*rpadmin.AdminAPI, var tlsConfig *tls.Config var err error - if redpanda.TLSEnabled(dot) { + if values.Listeners.Admin.TLS.IsEnabled(&values.TLS) { prefix = "https://" - tlsConfig, err = tlsConfigFromDot(dot, values.Listeners.Admin.TLS.Cert) + tlsConfig, err = tlsConfigFromDot(dot, values.Listeners.Admin.TLS) if err != nil { return nil, err } @@ -113,10 +113,10 @@ func SchemaRegistryClient(dot *helmette.Dot, dialer DialContextFunc, opts ...sr. }).DialContext } - if redpanda.TLSEnabled(dot) { + if values.Listeners.SchemaRegistry.TLS.IsEnabled(&values.TLS) { prefix = "https://" - tlsConfig, err := tlsConfigFromDot(dot, values.Listeners.SchemaRegistry.TLS.Cert) + tlsConfig, err := tlsConfigFromDot(dot, values.Listeners.SchemaRegistry.TLS) if err != nil { return nil, err } @@ -156,8 +156,8 @@ func KafkaClient(dot *helmette.Dot, dialer DialContextFunc, opts ...kgo.Opt) (*k opts = append(opts, kgo.SeedBrokers(brokers...)) - if redpanda.TLSEnabled(dot) { - tlsConfig, err := tlsConfigFromDot(dot, values.Listeners.Kafka.TLS.Cert) + if values.Listeners.Kafka.TLS.IsEnabled(&values.TLS) { + tlsConfig, err := tlsConfigFromDot(dot, values.Listeners.Kafka.TLS) if err != nil { return nil, err } @@ -237,14 +237,44 @@ func authFromDot(dot *helmette.Dot) (username string, password string, mechanism return } -func tlsConfigFromDot(dot *helmette.Dot, cert string) (*tls.Config, error) { +func certificatesFor(dot *helmette.Dot, cert string) (certSecret, certKey, clientSecret string) { + values := helmette.Unwrap[redpanda.Values](dot.Values) + name := redpanda.Fullname(dot) + + // default to cert manager issued names and tls.crt which is + // where cert-manager outputs the root CA + certKey = corev1.TLSCertKey + certSecret = fmt.Sprintf("%s-%s-root-certificate", name, cert) + clientSecret = fmt.Sprintf("%s-client", name) + + if certificate, ok := values.TLS.Certs[cert]; ok { + // if this references a non-enabled certificate, just return + // the default cert-manager issued names + if certificate.Enabled != nil && !*certificate.Enabled { + return certSecret, certKey, clientSecret + } + + if certificate.ClientSecretRef != nil { + clientSecret = certificate.ClientSecretRef.Name + } + if certificate.SecretRef != nil { + certSecret = certificate.SecretRef.Name + if certificate.CAEnabled { + certKey = "ca.crt" + } + } + } + return certSecret, certKey, clientSecret +} + +func tlsConfigFromDot(dot *helmette.Dot, listener redpanda.InternalTLS) (*tls.Config, error) { namespace := dot.Release.Namespace serviceName := redpanda.ServiceName(dot) - clientCertName := fmt.Sprintf("%s-client", name) - rootCertName := fmt.Sprintf("%s-%s-root-certificate", name, cert) serverName := fmt.Sprintf("%s.%s.svc", serviceName, namespace) + rootCertName, rootCertKey, clientCertName := certificatesFor(dot, listener.Cert) + serverTLSError := func(err error) error { return fmt.Errorf("error fetching server root CA %s/%s: %w", namespace, rootCertName, err) } @@ -263,7 +293,7 @@ func tlsConfigFromDot(dot *helmette.Dot, cert string) (*tls.Config, error) { return nil, serverTLSError(ErrServerCertificateNotFound) } - serverPublicKey, found := serverCert.Data[corev1.TLSCertKey] + serverPublicKey, found := serverCert.Data[rootCertKey] if !found { return nil, serverTLSError(ErrServerCertificatePublicKeyNotFound) } @@ -278,7 +308,7 @@ func tlsConfigFromDot(dot *helmette.Dot, cert string) (*tls.Config, error) { tlsConfig.RootCAs = pool - if redpanda.ClientAuthRequired(dot) { + if listener.RequireClientAuth { clientCert, found, lookupErr := helmette.SafeLookup[corev1.Secret](dot, namespace, clientCertName) if lookupErr != nil { return nil, clientTLSError(lookupErr) @@ -288,6 +318,7 @@ func tlsConfigFromDot(dot *helmette.Dot, cert string) (*tls.Config, error) { return nil, clientTLSError(ErrServerCertificateNotFound) } + // we always use tls.crt for client certs clientPublicKey, found := clientCert.Data[corev1.TLSCertKey] if !found { return nil, clientTLSError(ErrClientCertificatePublicKeyNotFound) diff --git a/pkg/redpanda/client_test.go b/pkg/redpanda/client_test.go index f131caa41..e2d75daed 100644 --- a/pkg/redpanda/client_test.go +++ b/pkg/redpanda/client_test.go @@ -3,7 +3,13 @@ package redpanda import ( "testing" + "github.com/redpanda-data/helm-charts/charts/redpanda" + "github.com/redpanda-data/helm-charts/pkg/gotohelm/helmette" + "github.com/redpanda-data/helm-charts/pkg/kube" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" ) func TestFirstUser(t *testing.T) { @@ -30,3 +36,96 @@ func TestFirstUser(t *testing.T) { assert.Equal(t, [3]string{user, password, mechanism}, c.Out) } } + +func TestCertificates(t *testing.T) { + cases := map[string]struct { + Cert *redpanda.TLSCert + CertificateName string + ExpectedRootCertName string + ExpectedRootCertKey string + ExpectedClientCertName string + }{ + "default": { + CertificateName: "default", + ExpectedRootCertName: "redpanda-default-root-certificate", + ExpectedRootCertKey: "tls.crt", + ExpectedClientCertName: "redpanda-client", + }, + "default with non-enabled global cert": { + Cert: &redpanda.TLSCert{ + Enabled: ptr.To(false), + SecretRef: &v1.LocalObjectReference{ + Name: "some-cert", + }, + }, + CertificateName: "default", + ExpectedRootCertName: "redpanda-default-root-certificate", + ExpectedRootCertKey: "tls.crt", + ExpectedClientCertName: "redpanda-client", + }, + "certificate with secret ref": { + Cert: &redpanda.TLSCert{ + SecretRef: &v1.LocalObjectReference{ + Name: "some-cert", + }, + }, + CertificateName: "default", + ExpectedRootCertName: "some-cert", + ExpectedRootCertKey: "tls.crt", + ExpectedClientCertName: "redpanda-client", + }, + "certificate with CA": { + Cert: &redpanda.TLSCert{ + CAEnabled: true, + SecretRef: &v1.LocalObjectReference{ + Name: "some-cert", + }, + }, + CertificateName: "default", + ExpectedRootCertName: "some-cert", + ExpectedRootCertKey: "ca.crt", + ExpectedClientCertName: "redpanda-client", + }, + "certificate with client certificate": { + Cert: &redpanda.TLSCert{ + CAEnabled: true, + SecretRef: &v1.LocalObjectReference{ + Name: "some-cert", + }, + ClientSecretRef: &v1.LocalObjectReference{ + Name: "client-cert", + }, + }, + CertificateName: "default", + ExpectedRootCertName: "some-cert", + ExpectedRootCertKey: "ca.crt", + ExpectedClientCertName: "client-cert", + }, + } + + for name, c := range cases { + t.Run(name, func(t *testing.T) { + certMap := redpanda.TLSCertMap{} + + if c.Cert != nil { + certMap[c.CertificateName] = *c.Cert + } + + dot, err := redpanda.Chart.Dot(kube.Config{}, helmette.Release{ + Name: "redpanda", + Namespace: "redpanda", + Service: "Helm", + }, redpanda.Values{ + TLS: redpanda.TLS{ + Certs: certMap, + }, + }) + require.NoError(t, err) + + actualRootCertName, actualRootCertKey, actualClientCertName := certificatesFor(dot, c.CertificateName) + require.Equal(t, c.ExpectedRootCertName, actualRootCertName) + require.Equal(t, c.ExpectedRootCertKey, actualRootCertKey) + require.Equal(t, c.ExpectedClientCertName, actualClientCertName) + }) + } +}