Skip to content

Commit

Permalink
redpanda: allow specifying truststore_file
Browse files Browse the repository at this point in the history
Prior to this commit the `truststore_file` of a given listener could only be
set by using the confusingly named `caEnabled` field on certificates.
Furthermore `caEnabled` was limited to a the `ca.crt` key of the certificate's
secret and was set on a per certificate basis.

This commit allows explicit control of `truststore_file` on a per listener
basis by adding a `truststore` field to listeners' `tls` field. Similar to
Kubernetes' `corev1.EnvVarSource`, `truststore` accepts either a
`configMapKeyRef` or a `secretKeyRef`.

Fixes #1339
  • Loading branch information
chrisseto committed Jun 24, 2024
1 parent b5cf86f commit f539d8f
Show file tree
Hide file tree
Showing 10 changed files with 940 additions and 141 deletions.
170 changes: 170 additions & 0 deletions charts/redpanda/chart_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
jsoniter "github.com/json-iterator/go"
"github.com/redpanda-data/helm-charts/charts/redpanda"
"github.com/redpanda-data/helm-charts/pkg/helm"
"github.com/redpanda-data/helm-charts/pkg/kube"
Expand Down Expand Up @@ -41,6 +42,7 @@ func TestTemplate(t *testing.T) {
cases := CIGoldenTestCases(t)
cases = append(cases, VersionGoldenTestsCases(t)...)
cases = append(cases, DisableCertmanagerIntegration(t)...)
cases = append(cases, CertTrustStoreCases(t)...)

for _, tc := range cases {
tc := tc
Expand Down Expand Up @@ -294,6 +296,174 @@ tls:
}
}

func CertTrustStoreCases(t *testing.T) []TemplateTestCase {
// truststores is a map of listener type to map of listener name to truststore_file. ({"admin": {"internal": "ca.crt"}}).
assertTrustStores := func(t *testing.T, manifests []byte, truststores map[string]map[string]string) {
cm, _, err := getConfigMaps(manifests)
require.NoError(t, err)

redpandaYAML, err := yaml.YAMLToJSON([]byte(cm.Data["redpanda.yaml"]))
require.NoError(t, err)

tlsConfigs := map[string]jsoniter.Any{
"kafka": jsoniter.Get(redpandaYAML, "redpanda", "kafka_api_tls"),
"admin": jsoniter.Get(redpandaYAML, "redpanda", "admin_api_tls"),
"http": jsoniter.Get(redpandaYAML, "pandaproxy", "pandaproxy_api_tls"),
"schema_registry": jsoniter.Get(redpandaYAML, "schema_registry", "schema_registry_api_tls"),
}

actual := map[string]map[string]string{}
for name, cfg := range tlsConfigs {
m := map[string]string{}
for i := 0; i < cfg.Size(); i++ {
name := cfg.Get(i, "name").ToString()
truststore := cfg.Get(i, "truststore_file").ToString()
m[name] = truststore
}
actual[name] = m
}

require.Equal(t, truststores, actual)
}

return []TemplateTestCase{
{
Name: "ca-enabled",
Values: valuesFromYAML(t, `
tls:
certs:
default:
caEnabled: true
external:
caEnabled: true
`),
Assert: func(t *testing.T, manifests []byte, err error) {
require.NoError(t, err)
assertTrustStores(t, manifests, map[string]map[string]string{
"admin": {
"default": "/etc/tls/certs/external/ca.crt",
"internal": "/etc/tls/certs/default/ca.crt",
},
"http": {
"default": "/etc/tls/certs/external/ca.crt",
"internal": "/etc/tls/certs/default/ca.crt",
},
"kafka": {
"default": "/etc/tls/certs/external/ca.crt",
"internal": "/etc/tls/certs/default/ca.crt",
},
"schema_registry": {
"default": "/etc/tls/certs/external/ca.crt",
"internal": "/etc/tls/certs/default/ca.crt",
},
})
},
},
{
Name: "internal-truststore",
Values: valuesFromYAML(t, `
listeners:
admin:
external:
my-admin:
port: 1234
tls:
cert: default
trustStore:
configMapKeyRef:
key: my-admin.crt
name: admin-cm
tls:
trustStore:
configMapKeyRef:
key: other.crt
name: admin-cm
http:
external:
my-http:
port: 1234
tls:
cert: default
trustStore:
configMapKeyRef:
key: my-http.crt
name: http-cm
tls:
trustStore:
configMapKeyRef:
key: ca.crt
name: http-cm
kafka:
external:
my-kafka:
port: 1234
tls:
cert: default
trustStore:
secretKeyRef:
key: my-kafka.crt
name: kafka-secret
tls:
trustStore:
configMapKeyRef:
key: ca.crt
name: my-ca-bundle
rpc: {}
schemaRegistry:
external:
my-sr:
port: 1234
tls:
cert: default
trustStore:
secretKeyRef:
key: my-sr.crt
name: sr-secret
tls:
trustStore:
secretKeyRef:
key: ca.crt
name: sr-secret
tls:
certs:
default:
caEnabled: true
external:
caEnabled: true
`),
Assert: func(t *testing.T, manifests []byte, err error) {
// Need to update this to be a map of listener to trust store.
// Should also include a mixture of external and internal uses.
// Going to skimp on the testing as Rafal's work to add tests
// will cover what else needs to be tested nicely.
require.NoError(t, err)
assertTrustStores(t, manifests, map[string]map[string]string{
"admin": {
"default": "/etc/tls/certs/external/ca.crt",
"internal": "/etc/truststores/configmaps/admin-cm-other.crt",
"my-admin": "/etc/truststores/configmaps/admin-cm-my-admin.crt",
},
"http": {
"default": "/etc/tls/certs/external/ca.crt",
"internal": "/etc/truststores/configmaps/http-cm-ca.crt",
"my-http": "/etc/truststores/configmaps/http-cm-my-http.crt",
},
"kafka": {
"default": "/etc/tls/certs/external/ca.crt",
"internal": "/etc/truststores/configmaps/my-ca-bundle-ca.crt",
"my-kafka": "/etc/truststores/secrets/kafka-secret-my-kafka.crt",
},
"schema_registry": {
"default": "/etc/tls/certs/external/ca.crt",
"internal": "/etc/truststores/secrets/sr-secret-ca.crt",
"my-sr": "/etc/truststores/secrets/sr-secret-my-sr.crt",
},
})
},
},
}
}

func valuesFromYAML(t *testing.T, values string) redpanda.PartialValues {
// Trim newlines to help with later comparison and avoid any weirdness with
// loading as it's likely to be written with `` strings.
Expand Down
18 changes: 8 additions & 10 deletions charts/redpanda/configmap.tpl.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,11 +323,11 @@ func brokersTLSConfiguration(dot *helmette.Dot) map[string]any {
}

result := map[string]any{}
certName := values.Listeners.Kafka.TLS.Cert

if cert, ok := values.TLS.Certs[certName]; ok && cert.CAEnabled {
result["truststore_file"] = fmt.Sprintf("/etc/tls/certs/%s/ca.crt", values.Listeners.Kafka.TLS.Cert)
if truststore := values.Listeners.Kafka.TLS.TrustStoreFilePath(&values.TLS); truststore != defaultTruststorePath {
result["truststore_file"] = truststore
}

if values.Listeners.Kafka.TLS.RequireClientAuth {
result["cert_file"] = fmt.Sprintf("/etc/tls/certs/%s-client/tls.crt", Fullname(dot))
result["key_file"] = fmt.Sprintf("/etc/tls/certs/%s-client/tls.key", Fullname(dot))
Expand All @@ -345,10 +345,8 @@ func adminTLSConfiguration(dot *helmette.Dot) map[string]any {
return result
}

certName := values.Listeners.Admin.TLS.Cert

if cert, ok := values.TLS.Certs[certName]; ok && cert.CAEnabled {
result["truststore_file"] = fmt.Sprintf("/etc/tls/certs/%s/ca.crt", values.Listeners.Admin.TLS.Cert)
if truststore := values.Listeners.Admin.TLS.TrustStoreFilePath(&values.TLS); truststore != defaultTruststorePath {
result["truststore_file"] = truststore
}

if values.Listeners.Admin.TLS.RequireClientAuth {
Expand Down Expand Up @@ -380,7 +378,7 @@ func kafkaClient(dot *helmette.Dot) map[string]any {
"cert_file": fmt.Sprintf("/etc/tls/certs/%s/tls.crt", kafkaTLS.Cert),
"key_file": fmt.Sprintf("/etc/tls/certs/%s/tls.key", kafkaTLS.Cert),
"require_client_auth": kafkaTLS.RequireClientAuth,
"truststore_file": values.TLS.Certs.getTrustStoreFilePath(kafkaTLS.Cert),
"truststore_file": kafkaTLS.TrustStoreFilePath(&values.TLS),
}
}

Expand Down Expand Up @@ -468,7 +466,7 @@ func rpcListenersTLS(dot *helmette.Dot) map[string]any {
"cert_file": fmt.Sprintf("/etc/tls/certs/%s/tls.crt", certName),
"key_file": fmt.Sprintf("/etc/tls/certs/%s/tls.key", certName),
"require_client_auth": r.TLS.RequireClientAuth,
"truststore_file": values.TLS.Certs.getTrustStoreFilePath(certName),
"truststore_file": r.TLS.TrustStoreFilePath(&values.TLS),
}
}

Expand All @@ -493,7 +491,7 @@ func createInternalListenerTLSCfg(tls *TLS, internal InternalTLS) map[string]any
"cert_file": fmt.Sprintf("/etc/tls/certs/%s/tls.crt", internal.Cert),
"key_file": fmt.Sprintf("/etc/tls/certs/%s/tls.key", internal.Cert),
"require_client_auth": internal.RequireClientAuth,
"truststore_file": tls.Certs.getTrustStoreFilePath(internal.Cert),
"truststore_file": internal.TrustStoreFilePath(tls),
}
}

Expand Down
20 changes: 18 additions & 2 deletions charts/redpanda/statefulset.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ const (
// technically change, this is the name that is used to locate the
// [corev1.Container] that will be smp'd into the redpanda container.
RedpandaContainerName = "redpanda"
// TrustStoreMountPath is the absolute path at which the
// [corev1.VolumeProjection] of truststores will be mounted to the redpanda
// container. (Without a trailing slash)
TrustStoreMountPath = "/etc/truststores"
)

// StatefulSetRedpandaEnv returns the environment variables for the Redpanda
Expand Down Expand Up @@ -153,9 +157,9 @@ func StatefulSetPodAnnotations(dot *helmette.Dot, configMapChecksum string) map[

// StatefulSetVolumes returns the [corev1.Volume]s for the Redpanda StatefulSet.
func StatefulSetVolumes(dot *helmette.Dot) []corev1.Volume {
volumes := CommonVolumes(dot)

fullname := Fullname(dot)
volumes := CommonVolumes(dot)
values := helmette.Unwrap[Values](dot.Values)

// NOTE extraVolumes, datadir, and tiered-storage-dir are NOT in this
// function. TODO: Migrate them into this function.
Expand Down Expand Up @@ -212,13 +216,18 @@ func StatefulSetVolumes(dot *helmette.Dot) []corev1.Volume {
},
}...)

if vol := values.Listeners.TrustStoreVolume(&values.TLS); vol != nil {
volumes = append(volumes, *vol)
}

return volumes
}

// StatefulSetRedpandaMounts returns the VolumeMounts for the Redpanda
// Container of the Redpanda StatefulSet.
func StatefulSetVolumeMounts(dot *helmette.Dot) []corev1.VolumeMount {
mounts := CommonMounts(dot)
values := helmette.Unwrap[Values](dot.Values)

// NOTE extraVolumeMounts and tiered-storage-dir are still handled in helm.
// TODO: Migrate them into this function.
Expand All @@ -229,5 +238,12 @@ func StatefulSetVolumeMounts(dot *helmette.Dot) []corev1.VolumeMount {
{Name: "datadir", MountPath: "/var/lib/redpanda/data"},
}...)

if len(values.Listeners.TrustStores(&values.TLS)) > 0 {
mounts = append(
mounts,
corev1.VolumeMount{Name: "truststores", MountPath: TrustStoreMountPath, ReadOnly: true},
)
}

return mounts
}
Loading

0 comments on commit f539d8f

Please sign in to comment.