Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
project: charts/redpanda
kind: Changed
body: Client certificates are now named `$FULLNAME-$CERT-client-cert`.
time: 2025-09-18T15:27:41.700988-04:00
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
project: charts/redpanda
kind: Fixed
body: mTLS client certificates are now generated per certificate, as required, instead of using a single and potentially invalid certificate.
time: 2025-09-18T15:26:23.232523-04:00
4 changes: 4 additions & 0 deletions .changes/unreleased/operator-Changed-20250918-152741.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
project: operator
kind: Changed
body: Client certificates are now named `$FULLNAME-$CERT-client-cert`.
time: 2025-09-18T15:27:41.700988-04:00
4 changes: 4 additions & 0 deletions .changes/unreleased/operator-Fixed-20250918-152623.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
project: operator
kind: Fixed
body: mTLS client certificates are now generated per certificate, as required, instead of using a single and potentially invalid certificate.
time: 2025-09-18T15:26:23.232523-04:00
14 changes: 10 additions & 4 deletions charts/redpanda/cert_issuers.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,17 @@ func certIssuersAndCAs(state *RenderState) ([]*certmanagerv1.Issuer, []*certmana
var issuers []*certmanagerv1.Issuer
var certs []*certmanagerv1.Certificate

if !TLSEnabled(state) {
return issuers, certs
inUseCerts := map[string]bool{}
for _, name := range state.Values.Listeners.InUseServerCerts(&state.Values.TLS) {
inUseCerts[name] = true
}
for _, name := range state.Values.Listeners.InUseClientCerts(&state.Values.TLS) {
inUseCerts[name] = true
}

for name := range helmette.SortedMap(inUseCerts) {
data := state.Values.TLS.Certs.MustGet(name)

for name, data := range helmette.SortedMap(state.Values.TLS.Certs) {
// If this certificate is disabled (.Enabled), provided directly by the
// end user (.SecretRef), or has an issuer provided (.IssuerRef), we
// don't need to bootstrap an issuer.
Expand Down Expand Up @@ -128,7 +134,7 @@ func certIssuersAndCAs(state *RenderState) ([]*certmanagerv1.Issuer, []*certmana
Spec: certmanagerv1.IssuerSpec{
IssuerConfig: certmanagerv1.IssuerConfig{
CA: &certmanagerv1.CAIssuer{
SecretName: fmt.Sprintf(`%s-%s-root-certificate`, Fullname(state), name),
SecretName: data.RootSecretName(state, name),
},
},
},
Expand Down
96 changes: 50 additions & 46 deletions charts/redpanda/certs.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ import (
)

func ClientCerts(state *RenderState) []*certmanagerv1.Certificate {
if !TLSEnabled(state) {
return []*certmanagerv1.Certificate{}
}

fullname := Fullname(state)
service := ServiceName(state)
ns := state.Release.Namespace
Expand All @@ -35,8 +31,11 @@ func ClientCerts(state *RenderState) []*certmanagerv1.Certificate {
domain := strings.TrimSuffix(state.Values.ClusterDomain, ".")

var certs []*certmanagerv1.Certificate
for name, data := range helmette.SortedMap(state.Values.TLS.Certs) {
if !helmette.Empty(data.SecretRef) || !ptr.Deref(data.Enabled, true) {
for _, name := range state.Values.Listeners.InUseServerCerts(&state.Values.TLS) {
data := state.Values.TLS.Certs.MustGet(name)

// Don't generate server Certificates if a secret is provided.
if !helmette.Empty(data.SecretRef) {
continue
}

Expand Down Expand Up @@ -83,7 +82,7 @@ func ClientCerts(state *RenderState) []*certmanagerv1.Certificate {
Duration: helmette.MustDuration(duration),
IsCA: false,
IssuerRef: issuerRef,
SecretName: fmt.Sprintf("%s-%s-cert", fullname, name),
SecretName: data.ServerSecretName(state, name),
PrivateKey: &certmanagerv1.CertificatePrivateKey{
Algorithm: "ECDSA",
Size: 256,
Expand All @@ -92,49 +91,54 @@ func ClientCerts(state *RenderState) []*certmanagerv1.Certificate {
})
}

name := state.Values.Listeners.Kafka.TLS.Cert
for _, name := range state.Values.Listeners.InUseClientCerts(&state.Values.TLS) {
data := state.Values.TLS.Certs.MustGet(name)

data, ok := state.Values.TLS.Certs[name]
if !ok {
panic(fmt.Sprintf("Certificate %q referenced but not defined", name))
}
if data.SecretRef != nil && data.ClientSecretRef == nil {
panic(fmt.Sprintf(".clientSecretRef MUST be set if .secretRef is set and require_client_auth is true: Cert %q", name))
}

if !helmette.Empty(data.SecretRef) || !ClientAuthRequired(state) {
return certs
}
// Don't generate a client Certificate if a client secret is provided.
if data.ClientSecretRef != nil {
continue
}

issuerRef := cmmetav1.ObjectReference{
Group: "cert-manager.io",
Kind: "Issuer",
Name: fmt.Sprintf("%s-%s-root-issuer", fullname, name),
}
issuerRef := cmmetav1.ObjectReference{
Group: "cert-manager.io",
Kind: "Issuer",
Name: fmt.Sprintf("%s-%s-root-issuer", fullname, name),
}

if data.IssuerRef != nil {
issuerRef = *data.IssuerRef
issuerRef.Group = "cert-manager.io"
}
if data.IssuerRef != nil {
issuerRef = *data.IssuerRef
issuerRef.Group = "cert-manager.io"
}

duration := helmette.Default("43800h", data.Duration)

duration := helmette.Default("43800h", data.Duration)

return append(certs, &certmanagerv1.Certificate{
TypeMeta: metav1.TypeMeta{
APIVersion: "cert-manager.io/v1",
Kind: "Certificate",
},
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-client", fullname),
Labels: FullLabels(state),
},
Spec: certmanagerv1.CertificateSpec{
CommonName: fmt.Sprintf("%s-client", fullname),
Duration: helmette.MustDuration(duration),
IsCA: false,
SecretName: fmt.Sprintf("%s-client", fullname),
PrivateKey: &certmanagerv1.CertificatePrivateKey{
Algorithm: "ECDSA",
Size: 256,
certs = append(certs, &certmanagerv1.Certificate{
TypeMeta: metav1.TypeMeta{
APIVersion: "cert-manager.io/v1",
Kind: "Certificate",
},
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-%s-client", fullname, name),
Namespace: state.Release.Namespace,
Labels: FullLabels(state),
},
Spec: certmanagerv1.CertificateSpec{
CommonName: fmt.Sprintf("%s--%s-client", fullname, name),
Duration: helmette.MustDuration(duration),
IsCA: false,
SecretName: data.ClientSecretName(state, name),
PrivateKey: &certmanagerv1.CertificatePrivateKey{
Algorithm: "ECDSA",
Size: 256,
},
IssuerRef: issuerRef,
},
IssuerRef: issuerRef,
},
})
})
}

return certs
}
19 changes: 14 additions & 5 deletions charts/redpanda/chart/templates/_cert-issuers.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,27 @@
{{- $_is_returning := false -}}
{{- $issuers := (coalesce nil) -}}
{{- $certs := (coalesce nil) -}}
{{- if (not (get (fromJson (include "redpanda.TLSEnabled" (dict "a" (list $state)))) "r")) -}}
{{- $_is_returning = true -}}
{{- (dict "r" (list $issuers $certs)) | toJson -}}
{{- $inUseCerts := (dict) -}}
{{- range $_, $name := (get (fromJson (include "redpanda.Listeners.InUseServerCerts" (dict "a" (list $state.Values.listeners $state.Values.tls)))) "r") -}}
{{- $_ := (set $inUseCerts $name true) -}}
{{- end -}}
{{- if $_is_returning -}}
{{- break -}}
{{- end -}}
{{- range $_, $name := (get (fromJson (include "redpanda.Listeners.InUseClientCerts" (dict "a" (list $state.Values.listeners $state.Values.tls)))) "r") -}}
{{- $_ := (set $inUseCerts $name true) -}}
{{- end -}}
{{- if $_is_returning -}}
{{- break -}}
{{- end -}}
{{- range $name, $data := $state.Values.tls.certs -}}
{{- range $name, $_ := $inUseCerts -}}
{{- $data := (get (fromJson (include "redpanda.TLSCertMap.MustGet" (dict "a" (list (deepCopy $state.Values.tls.certs) $name)))) "r") -}}
{{- if (or (or (not (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $data.enabled true)))) "r")) (ne (toJson $data.secretRef) "null")) (ne (toJson $data.issuerRef) "null")) -}}
{{- continue -}}
{{- end -}}
{{- $issuers = (concat (default (list) $issuers) (list (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil)) "spec" (dict) "status" (dict)) (mustMergeOverwrite (dict) (dict "apiVersion" "cert-manager.io/v1" "kind" "Issuer")) (dict "metadata" (mustMergeOverwrite (dict "creationTimestamp" (coalesce nil)) (dict "name" (printf `%s-%s-selfsigned-issuer` (get (fromJson (include "redpanda.Fullname" (dict "a" (list $state)))) "r") $name) "namespace" $state.Release.Namespace "labels" (get (fromJson (include "redpanda.FullLabels" (dict "a" (list $state)))) "r"))) "spec" (mustMergeOverwrite (dict) (mustMergeOverwrite (dict) (dict "selfSigned" (mustMergeOverwrite (dict) (dict)))) (dict)))))) -}}
{{- $certs = (concat (default (list) $certs) (list (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil)) "spec" (dict "secretName" "" "issuerRef" (dict "name" "")) "status" (dict)) (mustMergeOverwrite (dict) (dict "apiVersion" "cert-manager.io/v1" "kind" "Certificate")) (dict "metadata" (mustMergeOverwrite (dict "creationTimestamp" (coalesce nil)) (dict "name" (printf `%s-%s-root-certificate` (get (fromJson (include "redpanda.Fullname" (dict "a" (list $state)))) "r") $name) "namespace" $state.Release.Namespace "labels" (get (fromJson (include "redpanda.FullLabels" (dict "a" (list $state)))) "r"))) "spec" (mustMergeOverwrite (dict "secretName" "" "issuerRef" (dict "name" "")) (dict "duration" (get (fromJson (include "_shims.time_Duration_String" (dict "a" (list (get (fromJson (include "_shims.time_ParseDuration" (dict "a" (list (default "43800h" $data.duration))))) "r"))))) "r") "isCA" true "commonName" (printf `%s-%s-root-certificate` (get (fromJson (include "redpanda.Fullname" (dict "a" (list $state)))) "r") $name) "secretName" (printf `%s-%s-root-certificate` (get (fromJson (include "redpanda.Fullname" (dict "a" (list $state)))) "r") $name) "privateKey" (mustMergeOverwrite (dict) (dict "algorithm" "ECDSA" "size" (256 | int))) "issuerRef" (mustMergeOverwrite (dict "name" "") (dict "name" (printf `%s-%s-selfsigned-issuer` (get (fromJson (include "redpanda.Fullname" (dict "a" (list $state)))) "r") $name) "kind" "Issuer" "group" "cert-manager.io")))))))) -}}
{{- $issuers = (concat (default (list) $issuers) (list (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil)) "spec" (dict) "status" (dict)) (mustMergeOverwrite (dict) (dict "apiVersion" "cert-manager.io/v1" "kind" "Issuer")) (dict "metadata" (mustMergeOverwrite (dict "creationTimestamp" (coalesce nil)) (dict "name" (printf `%s-%s-root-issuer` (get (fromJson (include "redpanda.Fullname" (dict "a" (list $state)))) "r") $name) "namespace" $state.Release.Namespace "labels" (get (fromJson (include "redpanda.FullLabels" (dict "a" (list $state)))) "r"))) "spec" (mustMergeOverwrite (dict) (mustMergeOverwrite (dict) (dict "ca" (mustMergeOverwrite (dict "secretName" "") (dict "secretName" (printf `%s-%s-root-certificate` (get (fromJson (include "redpanda.Fullname" (dict "a" (list $state)))) "r") $name))))) (dict)))))) -}}
{{- $issuers = (concat (default (list) $issuers) (list (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil)) "spec" (dict) "status" (dict)) (mustMergeOverwrite (dict) (dict "apiVersion" "cert-manager.io/v1" "kind" "Issuer")) (dict "metadata" (mustMergeOverwrite (dict "creationTimestamp" (coalesce nil)) (dict "name" (printf `%s-%s-root-issuer` (get (fromJson (include "redpanda.Fullname" (dict "a" (list $state)))) "r") $name) "namespace" $state.Release.Namespace "labels" (get (fromJson (include "redpanda.FullLabels" (dict "a" (list $state)))) "r"))) "spec" (mustMergeOverwrite (dict) (mustMergeOverwrite (dict) (dict "ca" (mustMergeOverwrite (dict "secretName" "") (dict "secretName" (get (fromJson (include "redpanda.TLSCert.RootSecretName" (dict "a" (list $data $state $name)))) "r"))))) (dict)))))) -}}
{{- end -}}
{{- if $_is_returning -}}
{{- break -}}
Expand Down
35 changes: 16 additions & 19 deletions charts/redpanda/chart/templates/_certs.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,14 @@
{{- $state := (index .a 0) -}}
{{- range $_ := (list 1) -}}
{{- $_is_returning := false -}}
{{- if (not (get (fromJson (include "redpanda.TLSEnabled" (dict "a" (list $state)))) "r")) -}}
{{- $_is_returning = true -}}
{{- (dict "r" (list)) | toJson -}}
{{- break -}}
{{- end -}}
{{- $fullname := (get (fromJson (include "redpanda.Fullname" (dict "a" (list $state)))) "r") -}}
{{- $service := (get (fromJson (include "redpanda.ServiceName" (dict "a" (list $state)))) "r") -}}
{{- $ns := $state.Release.Namespace -}}
{{- $domain := (trimSuffix "." $state.Values.clusterDomain) -}}
{{- $certs := (coalesce nil) -}}
{{- range $name, $data := $state.Values.tls.certs -}}
{{- if (or (not (empty $data.secretRef)) (not (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $data.enabled true)))) "r"))) -}}
{{- range $_, $name := (get (fromJson (include "redpanda.Listeners.InUseServerCerts" (dict "a" (list $state.Values.listeners $state.Values.tls)))) "r") -}}
{{- $data := (get (fromJson (include "redpanda.TLSCertMap.MustGet" (dict "a" (list (deepCopy $state.Values.tls.certs) $name)))) "r") -}}
{{- if (not (empty $data.secretRef)) -}}
{{- continue -}}
{{- end -}}
{{- $names := (coalesce nil) -}}
Expand All @@ -40,31 +36,32 @@
{{- end -}}
{{- $duration := (default "43800h" $data.duration) -}}
{{- $issuerRef := (get (fromJson (include "_shims.ptr_Deref" (dict "a" (list $data.issuerRef (mustMergeOverwrite (dict "name" "") (dict "kind" "Issuer" "group" "cert-manager.io" "name" (printf "%s-%s-root-issuer" $fullname $name))))))) "r") -}}
{{- $certs = (concat (default (list) $certs) (list (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil)) "spec" (dict "secretName" "" "issuerRef" (dict "name" "")) "status" (dict)) (mustMergeOverwrite (dict) (dict "apiVersion" "cert-manager.io/v1" "kind" "Certificate")) (dict "metadata" (mustMergeOverwrite (dict "creationTimestamp" (coalesce nil)) (dict "name" (printf "%s-%s-cert" $fullname $name) "labels" (get (fromJson (include "redpanda.FullLabels" (dict "a" (list $state)))) "r") "namespace" $state.Release.Namespace)) "spec" (mustMergeOverwrite (dict "secretName" "" "issuerRef" (dict "name" "")) (dict "dnsNames" $names "duration" (get (fromJson (include "_shims.time_Duration_String" (dict "a" (list (get (fromJson (include "_shims.time_ParseDuration" (dict "a" (list $duration)))) "r"))))) "r") "isCA" false "issuerRef" $issuerRef "secretName" (printf "%s-%s-cert" $fullname $name) "privateKey" (mustMergeOverwrite (dict) (dict "algorithm" "ECDSA" "size" (256 | int))))))))) -}}
{{- $certs = (concat (default (list) $certs) (list (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil)) "spec" (dict "secretName" "" "issuerRef" (dict "name" "")) "status" (dict)) (mustMergeOverwrite (dict) (dict "apiVersion" "cert-manager.io/v1" "kind" "Certificate")) (dict "metadata" (mustMergeOverwrite (dict "creationTimestamp" (coalesce nil)) (dict "name" (printf "%s-%s-cert" $fullname $name) "labels" (get (fromJson (include "redpanda.FullLabels" (dict "a" (list $state)))) "r") "namespace" $state.Release.Namespace)) "spec" (mustMergeOverwrite (dict "secretName" "" "issuerRef" (dict "name" "")) (dict "dnsNames" $names "duration" (get (fromJson (include "_shims.time_Duration_String" (dict "a" (list (get (fromJson (include "_shims.time_ParseDuration" (dict "a" (list $duration)))) "r"))))) "r") "isCA" false "issuerRef" $issuerRef "secretName" (get (fromJson (include "redpanda.TLSCert.ServerSecretName" (dict "a" (list $data $state $name)))) "r") "privateKey" (mustMergeOverwrite (dict) (dict "algorithm" "ECDSA" "size" (256 | int))))))))) -}}
{{- end -}}
{{- if $_is_returning -}}
{{- break -}}
{{- end -}}
{{- $name := $state.Values.listeners.kafka.tls.cert -}}
{{- $_97_data_ok := (get (fromJson (include "_shims.dicttest" (dict "a" (list $state.Values.tls.certs $name (dict "enabled" (coalesce nil) "caEnabled" false "applyInternalDNSNames" (coalesce nil) "duration" "" "issuerRef" (coalesce nil) "secretRef" (coalesce nil) "clientSecretRef" (coalesce nil)))))) "r") -}}
{{- $data := (index $_97_data_ok 0) -}}
{{- $ok := (index $_97_data_ok 1) -}}
{{- if (not $ok) -}}
{{- $_ := (fail (printf "Certificate %q referenced but not defined" $name)) -}}
{{- range $_, $name := (get (fromJson (include "redpanda.Listeners.InUseClientCerts" (dict "a" (list $state.Values.listeners $state.Values.tls)))) "r") -}}
{{- $data := (get (fromJson (include "redpanda.TLSCertMap.MustGet" (dict "a" (list (deepCopy $state.Values.tls.certs) $name)))) "r") -}}
{{- if (and (ne (toJson $data.secretRef) "null") (eq (toJson $data.clientSecretRef) "null")) -}}
{{- $_ := (fail (printf ".clientSecretRef MUST be set if .secretRef is set and require_client_auth is true: Cert %q" $name)) -}}
{{- end -}}
{{- if (or (not (empty $data.secretRef)) (not (get (fromJson (include "redpanda.ClientAuthRequired" (dict "a" (list $state)))) "r"))) -}}
{{- $_is_returning = true -}}
{{- (dict "r" $certs) | toJson -}}
{{- break -}}
{{- if (ne (toJson $data.clientSecretRef) "null") -}}
{{- continue -}}
{{- end -}}
{{- $issuerRef := (mustMergeOverwrite (dict "name" "") (dict "group" "cert-manager.io" "kind" "Issuer" "name" (printf "%s-%s-root-issuer" $fullname $name))) -}}
{{- if (ne (toJson $data.issuerRef) "null") -}}
{{- $issuerRef = $data.issuerRef -}}
{{- $_ := (set $issuerRef "group" "cert-manager.io") -}}
{{- end -}}
{{- $duration := (default "43800h" $data.duration) -}}
{{- $certs = (concat (default (list) $certs) (list (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil)) "spec" (dict "secretName" "" "issuerRef" (dict "name" "")) "status" (dict)) (mustMergeOverwrite (dict) (dict "apiVersion" "cert-manager.io/v1" "kind" "Certificate")) (dict "metadata" (mustMergeOverwrite (dict "creationTimestamp" (coalesce nil)) (dict "name" (printf "%s-%s-client" $fullname $name) "namespace" $state.Release.Namespace "labels" (get (fromJson (include "redpanda.FullLabels" (dict "a" (list $state)))) "r"))) "spec" (mustMergeOverwrite (dict "secretName" "" "issuerRef" (dict "name" "")) (dict "commonName" (printf "%s--%s-client" $fullname $name) "duration" (get (fromJson (include "_shims.time_Duration_String" (dict "a" (list (get (fromJson (include "_shims.time_ParseDuration" (dict "a" (list $duration)))) "r"))))) "r") "isCA" false "secretName" (get (fromJson (include "redpanda.TLSCert.ClientSecretName" (dict "a" (list $data $state $name)))) "r") "privateKey" (mustMergeOverwrite (dict) (dict "algorithm" "ECDSA" "size" (256 | int))) "issuerRef" $issuerRef)))))) -}}
{{- end -}}
{{- if $_is_returning -}}
{{- break -}}
{{- end -}}
{{- $_is_returning = true -}}
{{- (dict "r" (concat (default (list) $certs) (list (mustMergeOverwrite (dict "metadata" (dict "creationTimestamp" (coalesce nil)) "spec" (dict "secretName" "" "issuerRef" (dict "name" "")) "status" (dict)) (mustMergeOverwrite (dict) (dict "apiVersion" "cert-manager.io/v1" "kind" "Certificate")) (dict "metadata" (mustMergeOverwrite (dict "creationTimestamp" (coalesce nil)) (dict "name" (printf "%s-client" $fullname) "labels" (get (fromJson (include "redpanda.FullLabels" (dict "a" (list $state)))) "r"))) "spec" (mustMergeOverwrite (dict "secretName" "" "issuerRef" (dict "name" "")) (dict "commonName" (printf "%s-client" $fullname) "duration" (get (fromJson (include "_shims.time_Duration_String" (dict "a" (list (get (fromJson (include "_shims.time_ParseDuration" (dict "a" (list $duration)))) "r"))))) "r") "isCA" false "secretName" (printf "%s-client" $fullname) "privateKey" (mustMergeOverwrite (dict) (dict "algorithm" "ECDSA" "size" (256 | int))) "issuerRef" $issuerRef))))))) | toJson -}}
{{- (dict "r" $certs) | toJson -}}
{{- break -}}
{{- end -}}
{{- end -}}
Expand Down
Loading