From af490f1c662ec0e81637175b57833d4e4d451737 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 18 Mar 2025 16:00:42 +0100 Subject: [PATCH 01/12] feat: Allow the specification of additional trust roots --- CHANGELOG.md | 2 + rust/operator-binary/src/backend/dynamic.rs | 2 + rust/operator-binary/src/backend/tls/ca.rs | 78 +++++++++++++++++++-- rust/operator-binary/src/backend/tls/mod.rs | 4 +- rust/operator-binary/src/crd.rs | 26 +++++++ 5 files changed, 105 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 278a397b..888c47e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file. - Made RSA key length configurable for certificates issued by cert-manager ([#528]). - Kerberos principal backends now also provision principals for IP address, not just DNS hostnames ([#552]). - OLM deployment helper ([#546]). +- Allow the specification of additional trust roots in autoTls SecretClasses ([#573]). ### Changed @@ -38,6 +39,7 @@ All notable changes to this project will be documented in this file. [#566]: https://github.com/stackabletech/secret-operator/pull/566 [#569]: https://github.com/stackabletech/secret-operator/pull/569 [#571]: https://github.com/stackabletech/secret-operator/pull/571 +[#573]: https://github.com/stackabletech/secret-operator/pull/573 ## [24.11.1] - 2025-01-10 diff --git a/rust/operator-binary/src/backend/dynamic.rs b/rust/operator-binary/src/backend/dynamic.rs index dd1e6b68..865f66ed 100644 --- a/rust/operator-binary/src/backend/dynamic.rs +++ b/rust/operator-binary/src/backend/dynamic.rs @@ -118,11 +118,13 @@ pub async fn from_class( } crd::SecretClassBackend::AutoTls(crd::AutoTlsBackend { ca, + additional_trust_roots, max_certificate_lifetime, }) => from( super::TlsGenerate::get_or_create_k8s_certificate( client, &ca, + &additional_trust_roots, max_certificate_lifetime, ) .await?, diff --git a/rust/operator-binary/src/backend/tls/ca.rs b/rust/operator-binary/src/backend/tls/ca.rs index 442d0925..e8a20df4 100644 --- a/rust/operator-binary/src/backend/tls/ca.rs +++ b/rust/operator-binary/src/backend/tls/ca.rs @@ -1,6 +1,6 @@ //! Dynamically provisions and picks Certificate Authorities. -use std::{collections::BTreeMap, fmt::Display}; +use std::{collections::BTreeMap, ffi::OsStr, fmt::Display, path::Path}; use openssl::{ asn1::{Asn1Integer, Asn1Time}, @@ -34,7 +34,7 @@ use tracing::{info, info_span, warn}; use crate::{ backend::SecretBackendError, - crd::CertificateKeyGeneration, + crd::{AdditionalTrustRoot, CertificateKeyGeneration}, utils::{asn1time_to_offsetdatetime, Asn1TimeParseError, Unloggable}, }; @@ -55,8 +55,8 @@ pub enum Error { #[snafu(display("failed to generate certificate key"))] GenerateKey { source: openssl::error::ErrorStack }, - #[snafu(display("failed to load CA {secret}"))] - FindCa { + #[snafu(display("failed to load {secret}"))] + FindSecret { source: kube::Error, secret: ObjectRef, }, @@ -77,6 +77,12 @@ pub enum Error { secret: ObjectRef, }, + #[snafu(display("unsupported certificate format in key {key:?} of {secret}; supported extensions: .cer, .cert, .crt, .pem"))] + UnsupportedCertificateFormat { + key: String, + secret: ObjectRef, + }, + #[snafu(display("failed to parse CA lifetime from key {key:?} of {secret}"))] ParseLifetime { source: Asn1TimeParseError, @@ -106,9 +112,10 @@ impl SecretBackendError for Error { match self { Error::GenerateKey { .. } => tonic::Code::Internal, Error::MissingCertificate { .. } => tonic::Code::FailedPrecondition, - Error::FindCa { .. } => tonic::Code::Unavailable, + Error::FindSecret { .. } => tonic::Code::Unavailable, Error::CaNotFoundAndGenDisabled { .. } => tonic::Code::FailedPrecondition, Error::LoadCertificate { .. } => tonic::Code::FailedPrecondition, + Error::UnsupportedCertificateFormat { .. } => tonic::Code::InvalidArgument, Error::ParseLifetime { .. } => tonic::Code::FailedPrecondition, Error::BuildCertificate { .. } => tonic::Code::FailedPrecondition, Error::SerializeCertificate { .. } => tonic::Code::FailedPrecondition, @@ -293,12 +300,14 @@ impl CertificateAuthority { #[derive(Debug)] pub struct Manager { certificate_authorities: Vec, + additional_trusted_certificates: Vec, } impl Manager { pub async fn load_or_create( client: &stackable_operator::client::Client, secret_ref: &SecretReference, + additional_trust_roots: &[AdditionalTrustRoot], config: &Config, ) -> Result { // Use entry API rather than apply so that we crash and retry on conflicts (to avoid creating spurious certs that we throw away immediately) @@ -306,7 +315,7 @@ impl Manager { let ca_secret = secrets_api .entry(&secret_ref.name) .await - .with_context(|_| FindCaSnafu { secret: secret_ref })?; + .with_context(|_| FindSecretSnafu { secret: secret_ref })?; let mut update_ca_secret = false; let mut certificate_authorities = match &ca_secret { Entry::Occupied(ca_secret) => { @@ -441,11 +450,67 @@ impl Manager { return SaveRequestedButForbiddenSnafu.fail(); } } + + let mut additional_trusted_certificates = vec![]; + for AdditionalTrustRoot { secret } in additional_trust_roots { + additional_trusted_certificates + .extend(Self::read_certificates_from_secret(client, secret).await?); + } + Ok(Self { certificate_authorities, + additional_trusted_certificates, }) } + /// Read certificates from the given Secret + /// + /// The keys are assumed to be filenames and their extensions denote the expected format of the + /// certificate. + async fn read_certificates_from_secret( + client: &stackable_operator::client::Client, + secret_ref: &SecretReference, + ) -> Result> { + let mut certificates = vec![]; + + let secrets_api = &client.get_api::(&secret_ref.namespace); + let secret = secrets_api + .get(&secret_ref.name) + .await + .with_context(|_| FindSecretSnafu { secret: secret_ref })?; + + let secret_data = secret.data.unwrap_or_default(); + for (key, ByteString(value)) in &secret_data { + let extension = Path::new(key).extension().and_then(OsStr::to_str); + let certs = match extension { + Some("pem") => X509::stack_from_pem(value), + Some("cer") | Some("cert") | Some("crt") => X509::from_der(value) + .map(|cert| vec![cert]) + .or(X509::stack_from_pem(value)), + _ => { + return UnsupportedCertificateFormatSnafu { + key, + secret: secret_ref, + } + .fail(); + } + } + .context(LoadCertificateSnafu { + key, + secret: secret_ref, + })?; + info!( + "Add the certificate(s) {certs:?} from the key [{key}] of [{secret_ref}] to the additional trust roots.", + certs = certs, + secret_ref = secret_ref, + key = key, + ); + certificates.extend(certs); + } + + Ok(certificates) + } + /// Get an appropriate [`CertificateAuthority`] for signing a given certificate. pub fn find_certificate_authority_for_signing( &self, @@ -467,5 +532,6 @@ impl Manager { self.certificate_authorities .iter() .map(|ca| &ca.certificate) + .chain(&self.additional_trusted_certificates) } } diff --git a/rust/operator-binary/src/backend/tls/mod.rs b/rust/operator-binary/src/backend/tls/mod.rs index a223659e..79fb1da8 100644 --- a/rust/operator-binary/src/backend/tls/mod.rs +++ b/rust/operator-binary/src/backend/tls/mod.rs @@ -33,7 +33,7 @@ use super::{ ScopeAddressesError, SecretBackend, SecretBackendError, SecretContents, }; use crate::{ - crd::{self, CertificateKeyGeneration}, + crd::{self, AdditionalTrustRoot, CertificateKeyGeneration}, format::{well_known, SecretData, WellKnownSecretData}, utils::iterator_try_concat_bytes, }; @@ -149,12 +149,14 @@ impl TlsGenerate { ca_certificate_lifetime, key_generation, }: &crd::AutoTlsCa, + additional_trust_roots: &[AdditionalTrustRoot], max_cert_lifetime: Duration, ) -> Result { Ok(Self { ca_manager: ca::Manager::load_or_create( client, ca_secret, + additional_trust_roots, &ca::Config { manage_ca: *auto_generate_ca, ca_certificate_lifetime: *ca_certificate_lifetime, diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index bb388746..41ba6c96 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -89,6 +89,10 @@ pub struct AutoTlsBackend { /// Configures the certificate authority used to issue Pod certificates. pub ca: AutoTlsCa, + /// Additional trust roots which are added to the provided `ca.crt` file. + #[serde(default)] + pub additional_trust_roots: Vec, + /// Maximum lifetime the created certificates are allowed to have. /// In case consumers request a longer lifetime than allowed by this setting, /// the lifetime will be the minimum of both, so this setting takes precedence. @@ -137,6 +141,17 @@ impl AutoTlsCa { } } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct AdditionalTrustRoot { + /// Reference (name and namespace) to a Kubernetes Secret object where additional certificates + /// are stored. + /// The extensions of the keys denote its contents: A key suffixed with `.pem` contains a stack + /// of base64 encoded DER certificates, a key suffixed with `.cer`, `.cert`, or `.crt` contains + /// either a binary DER certificate or a stack of base64 encoded DER certificates. + pub secret: SecretReference, +} + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "camelCase")] pub enum CertificateKeyGeneration { @@ -417,6 +432,7 @@ mod test { length: CertificateKeyGeneration::RSA_KEY_LENGTH_3072 } }, + additional_trust_roots: vec![], max_certificate_lifetime: DEFAULT_MAX_CERT_LIFETIME, }) } @@ -436,6 +452,10 @@ mod test { namespace: default autoGenerate: true caCertificateLifetime: 100d + additionalTrustRoots: + - secret: + name: tls-root-ca + namespace: default maxCertificateLifetime: 31d "#; let deserializer = serde_yaml::Deserializer::from_str(input); @@ -454,6 +474,12 @@ mod test { ca_certificate_lifetime: Duration::from_days_unchecked(100), key_generation: CertificateKeyGeneration::default() }, + additional_trust_roots: vec![AdditionalTrustRoot { + secret: SecretReference { + name: "tls-root-ca".to_string(), + namespace: "default".to_string(), + } + }], max_certificate_lifetime: Duration::from_days_unchecked(31), }) } From 74a966c4c3b1ec9c34ee75514aa61a39c61cd9c0 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 18 Mar 2025 16:08:06 +0100 Subject: [PATCH 02/12] chore: Regenerate charts --- deploy/helm/secret-operator/crds/crds.yaml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/deploy/helm/secret-operator/crds/crds.yaml b/deploy/helm/secret-operator/crds/crds.yaml index e9957745..9af33d30 100644 --- a/deploy/helm/secret-operator/crds/crds.yaml +++ b/deploy/helm/secret-operator/crds/crds.yaml @@ -42,6 +42,28 @@ spec: A new certificate and key pair will be generated and signed for each Pod, keys or certificates are never reused. properties: + additionalTrustRoots: + default: [] + description: Additional trust roots which are added to the provided `ca.crt` file. + items: + properties: + secret: + description: 'Reference (name and namespace) to a Kubernetes Secret object where additional certificates are stored. The extensions of the keys denote its contents: A key suffixed with `.pem` contains a stack of base64 encoded DER certificates, a key suffixed with `.cer`, `.cert`, or `.crt` contains either a binary DER certificate or a stack of base64 encoded DER certificates.' + properties: + name: + description: Name of the Secret being referred to. + type: string + namespace: + description: Namespace of the Secret being referred to. + type: string + required: + - name + - namespace + type: object + required: + - secret + type: object + type: array ca: description: Configures the certificate authority used to issue Pod certificates. properties: From 36f9176168b309e94ee975b98bfbe33e604bdeb8 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Tue, 18 Mar 2025 18:26:40 +0100 Subject: [PATCH 03/12] test: Add integration test for additionalTrustRoots --- rust/operator-binary/src/backend/tls/ca.rs | 3 +- tests/templates/kuttl/tls/02-rbac.yaml.j2 | 6 ++ .../kuttl/tls/03-create-trust-roots.yaml | 61 +++++++++++++++++++ tests/templates/kuttl/tls/consumer.yaml | 18 ++++++ tests/templates/kuttl/tls/secretclass.yaml.j2 | 7 +++ 5 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 tests/templates/kuttl/tls/03-create-trust-roots.yaml diff --git a/rust/operator-binary/src/backend/tls/ca.rs b/rust/operator-binary/src/backend/tls/ca.rs index e8a20df4..5bb62a95 100644 --- a/rust/operator-binary/src/backend/tls/ca.rs +++ b/rust/operator-binary/src/backend/tls/ca.rs @@ -77,7 +77,7 @@ pub enum Error { secret: ObjectRef, }, - #[snafu(display("unsupported certificate format in key {key:?} of {secret}; supported extensions: .cer, .cert, .crt, .pem"))] + #[snafu(display("unsupported certificate format in key {key:?} of {secret}; supported extensions: .cer, .cert, .crt, .der, .pem"))] UnsupportedCertificateFormat { key: String, secret: ObjectRef, @@ -484,6 +484,7 @@ impl Manager { let extension = Path::new(key).extension().and_then(OsStr::to_str); let certs = match extension { Some("pem") => X509::stack_from_pem(value), + Some("der") => X509::from_der(value).map(|cert| vec![cert]), Some("cer") | Some("cert") | Some("crt") => X509::from_der(value) .map(|cert| vec![cert]) .or(X509::stack_from_pem(value)), diff --git a/tests/templates/kuttl/tls/02-rbac.yaml.j2 b/tests/templates/kuttl/tls/02-rbac.yaml.j2 index 9cbf0351..a3eaeb58 100644 --- a/tests/templates/kuttl/tls/02-rbac.yaml.j2 +++ b/tests/templates/kuttl/tls/02-rbac.yaml.j2 @@ -4,6 +4,12 @@ apiVersion: rbac.authorization.k8s.io/v1 metadata: name: use-integration-tests-scc rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - create {% if test_scenario['values']['openshift'] == "true" %} - apiGroups: ["security.openshift.io"] resources: ["securitycontextconstraints"] diff --git a/tests/templates/kuttl/tls/03-create-trust-roots.yaml b/tests/templates/kuttl/tls/03-create-trust-roots.yaml new file mode 100644 index 00000000..9022b503 --- /dev/null +++ b/tests/templates/kuttl/tls/03-create-trust-roots.yaml @@ -0,0 +1,61 @@ +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: create-trust-roots +spec: + template: + spec: + containers: + - name: create-trust-roots + image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev + command: + - bash + args: + - -c + - | + set -euo pipefail + + function create_cert { + outform=$1 + cn=$2 + + openssl req \ + -x509 \ + -nodes \ + -outform "$outform" \ + -subj "/CN=$cn" + } + + # .pem file with one PEM certificate + create_cert PEM "cert 1" > cert1.pem + + # .pem file with multiple PEM certificates + create_cert PEM "cert 2a" > cert2.pem + create_cert PEM "cert 2b" >> cert2.pem + + # .der file with one DER certificate + create_cert DER "cert 3" > cert3.der + + # .crt file with one DER certificate + create_cert DER "cert 4" > cert4.crt + + # .crt file with multiple PEM certificates + create_cert PEM "cert 5a" > cert5.crt + create_cert PEM "cert 5b" >> cert5.crt + + kubectl create secret generic trust-roots-1 \ + --from-file=cert1.pem \ + --from-file=cert2.pem \ + --from-file=cert3.der + + kubectl create secret generic trust-roots-2 \ + --from-file=cert4.crt \ + --from-file=cert5.crt + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + restartPolicy: Never + terminationGracePeriodSeconds: 0 + serviceAccount: integration-tests-sa diff --git a/tests/templates/kuttl/tls/consumer.yaml b/tests/templates/kuttl/tls/consumer.yaml index dcd78cef..082851d5 100644 --- a/tests/templates/kuttl/tls/consumer.yaml +++ b/tests/templates/kuttl/tls/consumer.yaml @@ -40,6 +40,24 @@ spec: cat /stackable/tls-3d/tls.crt | openssl x509 -noout -text | grep "DNS:my-tls-service.$NAMESPACE.svc.cluster.local" cat /stackable/tls-42h/tls.crt | openssl x509 -noout -text | grep "DNS:my-tls-service.$NAMESPACE.svc.cluster.local" + + + function assert_trusted_roots_contain { + subject=$1 + + while openssl x509 -subject -noout; do :; done \ + < /stackable/tls-3d/ca.crt \ + | grep --line-regexp "subject=CN *= *$subject" + } + + assert_trusted_roots_contain "secret-operator self-signed" + assert_trusted_roots_contain "cert 1" + assert_trusted_roots_contain "cert 2a" + assert_trusted_roots_contain "cert 2b" + assert_trusted_roots_contain "cert 3" + assert_trusted_roots_contain "cert 4" + assert_trusted_roots_contain "cert 5a" + assert_trusted_roots_contain "cert 5b" volumeMounts: - mountPath: /stackable/tls-3d name: tls-3d diff --git a/tests/templates/kuttl/tls/secretclass.yaml.j2 b/tests/templates/kuttl/tls/secretclass.yaml.j2 index d7f2ad4f..9083877c 100644 --- a/tests/templates/kuttl/tls/secretclass.yaml.j2 +++ b/tests/templates/kuttl/tls/secretclass.yaml.j2 @@ -15,6 +15,13 @@ spec: keyGeneration: rsa: length: {{ test_scenario['values']['rsa-key-length'] }} + additionalTrustRoots: + - secret: + name: trust-roots-1 + namespace: $NAMESPACE + - secret: + name: trust-roots-2 + namespace: $NAMESPACE --- apiVersion: secrets.stackable.tech/v1alpha1 kind: SecretClass From 1e828c0d2d87de81d48e153587f2ec14ded87dbf Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Wed, 19 Mar 2025 15:56:24 +0100 Subject: [PATCH 04/12] docs: Extend CRD documentation --- deploy/helm/secret-operator/crds/crds.yaml | 2 +- rust/operator-binary/src/crd.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/deploy/helm/secret-operator/crds/crds.yaml b/deploy/helm/secret-operator/crds/crds.yaml index 9af33d30..15b0373e 100644 --- a/deploy/helm/secret-operator/crds/crds.yaml +++ b/deploy/helm/secret-operator/crds/crds.yaml @@ -48,7 +48,7 @@ spec: items: properties: secret: - description: 'Reference (name and namespace) to a Kubernetes Secret object where additional certificates are stored. The extensions of the keys denote its contents: A key suffixed with `.pem` contains a stack of base64 encoded DER certificates, a key suffixed with `.cer`, `.cert`, or `.crt` contains either a binary DER certificate or a stack of base64 encoded DER certificates.' + description: 'Reference (name and namespace) to a Kubernetes Secret object where additional certificates are stored. The extensions of the keys denote its contents: A key suffixed with `.pem` contains a stack of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER certificate, and a key suffixed with `.cer`, `.cert`, or `.crt` contains either a binary DER certificate or a stack of base64 encoded DER certificates.' properties: name: description: Name of the Secret being referred to. diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index 41ba6c96..543ceb52 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -147,8 +147,9 @@ pub struct AdditionalTrustRoot { /// Reference (name and namespace) to a Kubernetes Secret object where additional certificates /// are stored. /// The extensions of the keys denote its contents: A key suffixed with `.pem` contains a stack - /// of base64 encoded DER certificates, a key suffixed with `.cer`, `.cert`, or `.crt` contains - /// either a binary DER certificate or a stack of base64 encoded DER certificates. + /// of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER + /// certificate, and a key suffixed with `.cer`, `.cert`, or `.crt` contains either a binary DER + /// certificate or a stack of base64 encoded DER certificates. pub secret: SecretReference, } From bb680f260bc0cd82d38cdf9c21174794d716c99b Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Thu, 20 Mar 2025 11:46:43 +0100 Subject: [PATCH 05/12] docs: Document the property additionalTrustRoots --- docs/modules/secret-operator/pages/secretclass.adoc | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/modules/secret-operator/pages/secretclass.adoc b/docs/modules/secret-operator/pages/secretclass.adoc index 0b20bbb7..a7d6b71d 100644 --- a/docs/modules/secret-operator/pages/secretclass.adoc +++ b/docs/modules/secret-operator/pages/secretclass.adoc @@ -113,6 +113,10 @@ spec: keyGeneration: rsa: length: 4096 + additionalTrustRoots: + - secret: + name: trust-roots + namespace: default maxCertificateLifetime: 15d # optional ---- @@ -125,6 +129,13 @@ spec: `autoTls.ca.keyGeneration`:: Configures how keys should be generated. `autoTls.ca.keyGeneration.rsa`:: Declares that keys should be generated using the RSA algorithm. `autoTls.ca.keyGeneration.rsa.length`:: The amount of bits used for generating the RSA key pair. Currently, `2048`, `3072` and `4096` are supported. Defaults to `2048` bits. +`additionalTrustRoots`:: Configures additional trust roots which are added to the CA files or truststores in the provisioned volume mounts. +`additionalTrustRoots.secret`:: + Reference (`name` and `namespace`) to a K8s `Secret` object where the trusted certificates are stored. + The extension of a key defines its content: + * `.der` denotes a a binary DER certificates. + * `.pem` denotes a stack of base64 encoded DER certificates. + * `.cer`, `.cert` and `.crt` denote either a binary DER certificate or a stack of base64 encoded DER certificates. `autoTls.maxCertificateLifetime`:: Maximum lifetime the created certificates are allowed to have. In case consumers request a longer lifetime than allowed by this setting, the lifetime will be the minimum of both. [#backend-certmanager] From c27f30dcc53b35bb14008c0bd7ff9d8247b13d37 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Fri, 21 Mar 2025 08:05:19 +0100 Subject: [PATCH 06/12] chore: Improve log message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Natalie Klestrup Röijezon --- rust/operator-binary/src/backend/tls/ca.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rust/operator-binary/src/backend/tls/ca.rs b/rust/operator-binary/src/backend/tls/ca.rs index 5bb62a95..83961603 100644 --- a/rust/operator-binary/src/backend/tls/ca.rs +++ b/rust/operator-binary/src/backend/tls/ca.rs @@ -501,10 +501,10 @@ impl Manager { secret: secret_ref, })?; info!( - "Add the certificate(s) {certs:?} from the key [{key}] of [{secret_ref}] to the additional trust roots.", - certs = certs, - secret_ref = secret_ref, - key = key, + %certs, + %secret_ref, + %key, + "adding certificates from additional trust root", ); certificates.extend(certs); } From 0ac2436e2ff7a0d811c93714c081d0d76ff7b912 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Fri, 21 Mar 2025 08:06:47 +0100 Subject: [PATCH 07/12] chore: Improve code style MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Natalie Klestrup Röijezon --- rust/operator-binary/src/backend/tls/ca.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/operator-binary/src/backend/tls/ca.rs b/rust/operator-binary/src/backend/tls/ca.rs index 83961603..597e9324 100644 --- a/rust/operator-binary/src/backend/tls/ca.rs +++ b/rust/operator-binary/src/backend/tls/ca.rs @@ -485,7 +485,7 @@ impl Manager { let certs = match extension { Some("pem") => X509::stack_from_pem(value), Some("der") => X509::from_der(value).map(|cert| vec![cert]), - Some("cer") | Some("cert") | Some("crt") => X509::from_der(value) + Some("cer" | "cert" | "crt") => X509::from_der(value) .map(|cert| vec![cert]) .or(X509::stack_from_pem(value)), _ => { From 5f57436711cb515810ff75c12bc3253d96583685 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Fri, 21 Mar 2025 08:16:25 +0100 Subject: [PATCH 08/12] chore: Fix compile error --- rust/operator-binary/src/backend/tls/ca.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/operator-binary/src/backend/tls/ca.rs b/rust/operator-binary/src/backend/tls/ca.rs index 597e9324..5e10b4d3 100644 --- a/rust/operator-binary/src/backend/tls/ca.rs +++ b/rust/operator-binary/src/backend/tls/ca.rs @@ -501,7 +501,7 @@ impl Manager { secret: secret_ref, })?; info!( - %certs, + ?certs, %secret_ref, %key, "adding certificates from additional trust root", From 168c6bb2dd30c957b44819a0c5af245f5726a535 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Fri, 21 Mar 2025 15:07:32 +0100 Subject: [PATCH 09/12] feat: Support ConfigMaps in the additionalTrustRoots property --- deploy/helm/secret-operator/crds/crds.yaml | 22 ++- .../helm/secret-operator/templates/roles.yaml | 1 + .../secret-operator/pages/secretclass.adoc | 3 +- rust/crd-utils/src/lib.rs | 50 ++++++- rust/operator-binary/src/backend/tls/ca.rs | 131 +++++++++++++----- rust/operator-binary/src/crd.rs | 47 +++++-- tests/templates/kuttl/tls/02-rbac.yaml.j2 | 1 + .../kuttl/tls/03-create-trust-roots.yaml | 46 +++--- tests/templates/kuttl/tls/consumer.yaml | 13 +- tests/templates/kuttl/tls/secretclass.yaml.j2 | 6 +- 10 files changed, 237 insertions(+), 83 deletions(-) diff --git a/deploy/helm/secret-operator/crds/crds.yaml b/deploy/helm/secret-operator/crds/crds.yaml index 15b0373e..5909c60b 100644 --- a/deploy/helm/secret-operator/crds/crds.yaml +++ b/deploy/helm/secret-operator/crds/crds.yaml @@ -46,9 +46,27 @@ spec: default: [] description: Additional trust roots which are added to the provided `ca.crt` file. items: + oneOf: + - required: + - configMap + - required: + - secret properties: + configMap: + description: 'Reference (name and namespace) to a Kubernetes ConfigMap object where additional certificates are stored. The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER certificate.' + properties: + name: + description: Name of the ConfigMap being referred to. + type: string + namespace: + description: Namespace of the ConfigMap being referred to. + type: string + required: + - name + - namespace + type: object secret: - description: 'Reference (name and namespace) to a Kubernetes Secret object where additional certificates are stored. The extensions of the keys denote its contents: A key suffixed with `.pem` contains a stack of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER certificate, and a key suffixed with `.cer`, `.cert`, or `.crt` contains either a binary DER certificate or a stack of base64 encoded DER certificates.' + description: 'Reference (name and namespace) to a Kubernetes Secret object where additional certificates are stored. The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER certificate.' properties: name: description: Name of the Secret being referred to. @@ -60,8 +78,6 @@ spec: - name - namespace type: object - required: - - secret type: object type: array ca: diff --git a/deploy/helm/secret-operator/templates/roles.yaml b/deploy/helm/secret-operator/templates/roles.yaml index 2ebb1b02..b6e36c36 100644 --- a/deploy/helm/secret-operator/templates/roles.yaml +++ b/deploy/helm/secret-operator/templates/roles.yaml @@ -69,6 +69,7 @@ rules: - apiGroups: - "" resources: + - configmaps - nodes - persistentvolumeclaims verbs: diff --git a/docs/modules/secret-operator/pages/secretclass.adoc b/docs/modules/secret-operator/pages/secretclass.adoc index a7d6b71d..2e241b28 100644 --- a/docs/modules/secret-operator/pages/secretclass.adoc +++ b/docs/modules/secret-operator/pages/secretclass.adoc @@ -134,8 +134,7 @@ spec: Reference (`name` and `namespace`) to a K8s `Secret` object where the trusted certificates are stored. The extension of a key defines its content: * `.der` denotes a a binary DER certificates. - * `.pem` denotes a stack of base64 encoded DER certificates. - * `.cer`, `.cert` and `.crt` denote either a binary DER certificate or a stack of base64 encoded DER certificates. + * `.crt` denotes a stack of base64 encoded DER certificates. `autoTls.maxCertificateLifetime`:: Maximum lifetime the created certificates are allowed to have. In case consumers request a longer lifetime than allowed by this setting, the lifetime will be the minimum of both. [#backend-certmanager] diff --git a/rust/crd-utils/src/lib.rs b/rust/crd-utils/src/lib.rs index cc6b962b..90cffabb 100644 --- a/rust/crd-utils/src/lib.rs +++ b/rust/crd-utils/src/lib.rs @@ -4,11 +4,47 @@ use std::fmt::Display; use serde::{Deserialize, Serialize}; use stackable_operator::{ - k8s_openapi::api::core::v1::Secret, - kube::runtime::reflector::ObjectRef, + k8s_openapi::api::core::v1::{ConfigMap, Secret}, + kube::{api::DynamicObject, runtime::reflector::ObjectRef}, schemars::{self, JsonSchema}, }; +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct ConfigMapReference { + /// Namespace of the ConfigMap being referred to. + pub namespace: String, + /// Name of the ConfigMap being referred to. + pub name: String, +} + +// Use ObjectRef for logging/errors +impl Display for ConfigMapReference { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + ObjectRef::::from(self).fmt(f) + } +} +impl From for ObjectRef { + fn from(val: ConfigMapReference) -> Self { + ObjectRef::::from(&val) + } +} +impl From<&ConfigMapReference> for ObjectRef { + fn from(val: &ConfigMapReference) -> Self { + ObjectRef::::new(&val.name).within(&val.namespace) + } +} +impl From for ObjectRef { + fn from(val: ConfigMapReference) -> Self { + ObjectRef::::from(&val).erase() + } +} +impl From<&ConfigMapReference> for ObjectRef { + fn from(val: &ConfigMapReference) -> Self { + ObjectRef::::from(val).erase() + } +} + // Redefine SecretReference instead of reusing k8s-openapi's, in order to make name/namespace mandatory. #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "camelCase")] @@ -35,3 +71,13 @@ impl From<&SecretReference> for ObjectRef { ObjectRef::::new(&val.name).within(&val.namespace) } } +impl From for ObjectRef { + fn from(val: SecretReference) -> Self { + ObjectRef::::from(&val).erase() + } +} +impl From<&SecretReference> for ObjectRef { + fn from(val: &SecretReference) -> Self { + ObjectRef::::from(val).erase() + } +} diff --git a/rust/operator-binary/src/backend/tls/ca.rs b/rust/operator-binary/src/backend/tls/ca.rs index 5e10b4d3..f4f9ad25 100644 --- a/rust/operator-binary/src/backend/tls/ca.rs +++ b/rust/operator-binary/src/backend/tls/ca.rs @@ -17,18 +17,21 @@ use openssl::{ }; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ - k8s_openapi::{api::core::v1::Secret, ByteString}, + k8s_openapi::{ + api::core::v1::{ConfigMap, Secret}, + ByteString, + }, kube::{ self, api::{ entry::{self, Entry}, - PostParams, + DynamicObject, PostParams, }, runtime::reflector::ObjectRef, }, time::Duration, }; -use stackable_secret_operator_crd_utils::SecretReference; +use stackable_secret_operator_crd_utils::{ConfigMapReference, SecretReference}; use time::OffsetDateTime; use tracing::{info, info_span, warn}; @@ -55,6 +58,12 @@ pub enum Error { #[snafu(display("failed to generate certificate key"))] GenerateKey { source: openssl::error::ErrorStack }, + #[snafu(display("failed to load {config_map}"))] + FindConfigMap { + source: kube::Error, + config_map: ObjectRef, + }, + #[snafu(display("failed to load {secret}"))] FindSecret { source: kube::Error, @@ -70,17 +79,17 @@ pub enum Error { secret: ObjectRef, }, - #[snafu(display("failed to load certificate from key {key:?} of {secret}"))] + #[snafu(display("failed to load certificate from key {key:?} of {object}"))] LoadCertificate { source: openssl::error::ErrorStack, key: String, - secret: ObjectRef, + object: ObjectRef, }, - #[snafu(display("unsupported certificate format in key {key:?} of {secret}; supported extensions: .cer, .cert, .crt, .der, .pem"))] + #[snafu(display("unsupported certificate format in key {key:?} of {object}; supported extensions: .crt, .der"))] UnsupportedCertificateFormat { key: String, - secret: ObjectRef, + object: ObjectRef, }, #[snafu(display("failed to parse CA lifetime from key {key:?} of {secret}"))] @@ -112,6 +121,7 @@ impl SecretBackendError for Error { match self { Error::GenerateKey { .. } => tonic::Code::Internal, Error::MissingCertificate { .. } => tonic::Code::FailedPrecondition, + Error::FindConfigMap { .. } => tonic::Code::Unavailable, Error::FindSecret { .. } => tonic::Code::Unavailable, Error::CaNotFoundAndGenDisabled { .. } => tonic::Code::FailedPrecondition, Error::LoadCertificate { .. } => tonic::Code::FailedPrecondition, @@ -268,7 +278,7 @@ impl CertificateAuthority { ) .with_context(|_| LoadCertificateSnafu { key: key_certificate, - secret: secret_ref, + object: secret_ref, })?; let private_key = PKey::private_key_from_pem( &secret_data @@ -281,7 +291,7 @@ impl CertificateAuthority { ) .with_context(|_| LoadCertificateSnafu { key: key_private_key, - secret: secret_ref, + object: secret_ref, })?; Ok(CertificateAuthority { not_after: asn1time_to_offsetdatetime(certificate.not_after()).with_context(|_| { @@ -452,9 +462,16 @@ impl Manager { } let mut additional_trusted_certificates = vec![]; - for AdditionalTrustRoot { secret } in additional_trust_roots { - additional_trusted_certificates - .extend(Self::read_certificates_from_secret(client, secret).await?); + for entry in additional_trust_roots { + let certs = match entry { + AdditionalTrustRoot::ConfigMap { config_map } => { + Self::read_certificates_from_config_map(client, config_map).await? + } + AdditionalTrustRoot::Secret { secret } => { + Self::read_certificates_from_secret(client, secret).await? + } + }; + additional_trusted_certificates.extend(certs); } Ok(Self { @@ -463,6 +480,48 @@ impl Manager { }) } + /// Read certificates from the given ConfigMap + /// + /// The keys are assumed to be filenames and their extensions denote the expected format of the + /// certificate. + async fn read_certificates_from_config_map( + client: &stackable_operator::client::Client, + config_map_ref: &ConfigMapReference, + ) -> Result> { + let mut certificates = vec![]; + + let config_map_api = &client.get_api::(&config_map_ref.namespace); + let config_map = config_map_api + .get(&config_map_ref.name) + .await + .with_context(|_| FindConfigMapSnafu { + config_map: config_map_ref, + })?; + + let config_map_data = config_map.data.unwrap_or_default(); + let config_map_binary_data = config_map.binary_data.unwrap_or_default(); + let data = config_map_data + .iter() + .map(|(key, value)| (key, value.as_bytes())) + .chain( + config_map_binary_data + .iter() + .map(|(key, ByteString(value))| (key, value.as_ref())), + ); + for (key, value) in data { + let certs = Self::deserialize_certificate(key, value, config_map_ref.into())?; + info!( + ?certs, + %config_map_ref, + %key, + "adding certificates from additional trust root", + ); + certificates.extend(certs); + } + + Ok(certificates) + } + /// Read certificates from the given Secret /// /// The keys are assumed to be filenames and their extensions denote the expected format of the @@ -481,25 +540,7 @@ impl Manager { let secret_data = secret.data.unwrap_or_default(); for (key, ByteString(value)) in &secret_data { - let extension = Path::new(key).extension().and_then(OsStr::to_str); - let certs = match extension { - Some("pem") => X509::stack_from_pem(value), - Some("der") => X509::from_der(value).map(|cert| vec![cert]), - Some("cer" | "cert" | "crt") => X509::from_der(value) - .map(|cert| vec![cert]) - .or(X509::stack_from_pem(value)), - _ => { - return UnsupportedCertificateFormatSnafu { - key, - secret: secret_ref, - } - .fail(); - } - } - .context(LoadCertificateSnafu { - key, - secret: secret_ref, - })?; + let certs = Self::deserialize_certificate(key, value, secret_ref.into())?; info!( ?certs, %secret_ref, @@ -512,6 +553,34 @@ impl Manager { Ok(certificates) } + /// Deserialize a certificate from the given value. The format is determined by the extension + /// of the key. + fn deserialize_certificate( + key: &str, + value: &[u8], + object_ref: ObjectRef, + ) -> Result> { + let extension = Path::new(key).extension().and_then(OsStr::to_str); + + let certs = match extension { + Some("crt") => X509::stack_from_pem(value), + Some("der") => X509::from_der(value).map(|cert| vec![cert]), + _ => { + return UnsupportedCertificateFormatSnafu { + key, + object: object_ref, + } + .fail(); + } + } + .context(LoadCertificateSnafu { + key, + object: object_ref, + })?; + + Ok(certs) + } + /// Get an appropriate [`CertificateAuthority`] for signing a given certificate. pub fn find_certificate_authority_for_signing( &self, diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index 543ceb52..8ab70a09 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -8,7 +8,7 @@ use stackable_operator::{ schemars::{self, schema::Schema, JsonSchema}, time::Duration, }; -use stackable_secret_operator_crd_utils::SecretReference; +use stackable_secret_operator_crd_utils::{ConfigMapReference, SecretReference}; use crate::backend; @@ -143,14 +143,26 @@ impl AutoTlsCa { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "camelCase")] -pub struct AdditionalTrustRoot { +pub enum AdditionalTrustRoot { + /// Reference (name and namespace) to a Kubernetes ConfigMap object where additional + /// certificates are stored. + /// The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack + /// of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER + /// certificate. + ConfigMap { + #[serde(flatten)] + config_map: ConfigMapReference, + }, + /// Reference (name and namespace) to a Kubernetes Secret object where additional certificates /// are stored. - /// The extensions of the keys denote its contents: A key suffixed with `.pem` contains a stack + /// The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack /// of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER - /// certificate, and a key suffixed with `.cer`, `.cert`, or `.crt` contains either a binary DER - /// certificate or a stack of base64 encoded DER certificates. - pub secret: SecretReference, + /// certificate. + Secret { + #[serde(flatten)] + secret: SecretReference, + }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -454,8 +466,11 @@ mod test { autoGenerate: true caCertificateLifetime: 100d additionalTrustRoots: + - configMap: + name: tls-root-ca-config-map + namespace: default - secret: - name: tls-root-ca + name: tls-root-ca-secret namespace: default maxCertificateLifetime: 31d "#; @@ -475,12 +490,20 @@ mod test { ca_certificate_lifetime: Duration::from_days_unchecked(100), key_generation: CertificateKeyGeneration::default() }, - additional_trust_roots: vec![AdditionalTrustRoot { - secret: SecretReference { - name: "tls-root-ca".to_string(), - namespace: "default".to_string(), + additional_trust_roots: vec![ + AdditionalTrustRoot::ConfigMap { + config_map: ConfigMapReference { + name: "tls-root-ca-config-map".to_string(), + namespace: "default".to_string(), + } + }, + AdditionalTrustRoot::Secret { + secret: SecretReference { + name: "tls-root-ca-secret".to_string(), + namespace: "default".to_string(), + } } - }], + ], max_certificate_lifetime: Duration::from_days_unchecked(31), }) } diff --git a/tests/templates/kuttl/tls/02-rbac.yaml.j2 b/tests/templates/kuttl/tls/02-rbac.yaml.j2 index a3eaeb58..403819a6 100644 --- a/tests/templates/kuttl/tls/02-rbac.yaml.j2 +++ b/tests/templates/kuttl/tls/02-rbac.yaml.j2 @@ -7,6 +7,7 @@ rules: - apiGroups: - "" resources: + - configmaps - secrets verbs: - create diff --git a/tests/templates/kuttl/tls/03-create-trust-roots.yaml b/tests/templates/kuttl/tls/03-create-trust-roots.yaml index 9022b503..cc5e2ae3 100644 --- a/tests/templates/kuttl/tls/03-create-trust-roots.yaml +++ b/tests/templates/kuttl/tls/03-create-trust-roots.yaml @@ -27,31 +27,29 @@ spec: -subj "/CN=$cn" } - # .pem file with one PEM certificate - create_cert PEM "cert 1" > cert1.pem - - # .pem file with multiple PEM certificates - create_cert PEM "cert 2a" > cert2.pem - create_cert PEM "cert 2b" >> cert2.pem - - # .der file with one DER certificate - create_cert DER "cert 3" > cert3.der - - # .crt file with one DER certificate - create_cert DER "cert 4" > cert4.crt - - # .crt file with multiple PEM certificates - create_cert PEM "cert 5a" > cert5.crt - create_cert PEM "cert 5b" >> cert5.crt - - kubectl create secret generic trust-roots-1 \ - --from-file=cert1.pem \ - --from-file=cert2.pem \ - --from-file=cert3.der - - kubectl create secret generic trust-roots-2 \ + # .crt files with one PEM certificate + create_cert PEM "cert 1" > cert1.crt + create_cert PEM "cert 2" > cert2.crt + + # .crt files with multiple PEM certificates + create_cert PEM "cert 3a" > cert3.crt + create_cert PEM "cert 3b" >> cert3.crt + create_cert PEM "cert 4a" > cert4.crt + create_cert PEM "cert 4b" >> cert4.crt + + # .der files with one DER certificate + create_cert DER "cert 5" > cert5.der + create_cert DER "cert 6" > cert6.der + + kubectl create configmap trust-roots \ + --from-file=cert1.crt \ + --from-file=cert3.crt \ + --from-file=cert5.der + + kubectl create secret generic trust-roots \ + --from-file=cert2.crt \ --from-file=cert4.crt \ - --from-file=cert5.crt + --from-file=cert6.der securityContext: runAsUser: 1000 runAsGroup: 1000 diff --git a/tests/templates/kuttl/tls/consumer.yaml b/tests/templates/kuttl/tls/consumer.yaml index 082851d5..d00d4863 100644 --- a/tests/templates/kuttl/tls/consumer.yaml +++ b/tests/templates/kuttl/tls/consumer.yaml @@ -52,12 +52,13 @@ spec: assert_trusted_roots_contain "secret-operator self-signed" assert_trusted_roots_contain "cert 1" - assert_trusted_roots_contain "cert 2a" - assert_trusted_roots_contain "cert 2b" - assert_trusted_roots_contain "cert 3" - assert_trusted_roots_contain "cert 4" - assert_trusted_roots_contain "cert 5a" - assert_trusted_roots_contain "cert 5b" + assert_trusted_roots_contain "cert 2" + assert_trusted_roots_contain "cert 3a" + assert_trusted_roots_contain "cert 3b" + assert_trusted_roots_contain "cert 4a" + assert_trusted_roots_contain "cert 4b" + assert_trusted_roots_contain "cert 5" + assert_trusted_roots_contain "cert 6" volumeMounts: - mountPath: /stackable/tls-3d name: tls-3d diff --git a/tests/templates/kuttl/tls/secretclass.yaml.j2 b/tests/templates/kuttl/tls/secretclass.yaml.j2 index 9083877c..b84e2bb9 100644 --- a/tests/templates/kuttl/tls/secretclass.yaml.j2 +++ b/tests/templates/kuttl/tls/secretclass.yaml.j2 @@ -16,11 +16,11 @@ spec: rsa: length: {{ test_scenario['values']['rsa-key-length'] }} additionalTrustRoots: - - secret: - name: trust-roots-1 + - configMap: + name: trust-roots namespace: $NAMESPACE - secret: - name: trust-roots-2 + name: trust-roots namespace: $NAMESPACE --- apiVersion: secrets.stackable.tech/v1alpha1 From a94f08b8f86b46cc137e8266fdfd009ef89d7334 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Fri, 21 Mar 2025 15:33:17 +0100 Subject: [PATCH 10/12] chore: Improve code style --- rust/operator-binary/src/backend/tls/ca.rs | 4 +-- rust/operator-binary/src/crd.rs | 30 ++++++++-------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/rust/operator-binary/src/backend/tls/ca.rs b/rust/operator-binary/src/backend/tls/ca.rs index f4f9ad25..6af1c3dd 100644 --- a/rust/operator-binary/src/backend/tls/ca.rs +++ b/rust/operator-binary/src/backend/tls/ca.rs @@ -464,10 +464,10 @@ impl Manager { let mut additional_trusted_certificates = vec![]; for entry in additional_trust_roots { let certs = match entry { - AdditionalTrustRoot::ConfigMap { config_map } => { + AdditionalTrustRoot::ConfigMap(config_map) => { Self::read_certificates_from_config_map(client, config_map).await? } - AdditionalTrustRoot::Secret { secret } => { + AdditionalTrustRoot::Secret(secret) => { Self::read_certificates_from_secret(client, secret).await? } }; diff --git a/rust/operator-binary/src/crd.rs b/rust/operator-binary/src/crd.rs index 8ab70a09..2395ee29 100644 --- a/rust/operator-binary/src/crd.rs +++ b/rust/operator-binary/src/crd.rs @@ -149,20 +149,14 @@ pub enum AdditionalTrustRoot { /// The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack /// of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER /// certificate. - ConfigMap { - #[serde(flatten)] - config_map: ConfigMapReference, - }, + ConfigMap(ConfigMapReference), /// Reference (name and namespace) to a Kubernetes Secret object where additional certificates /// are stored. /// The extensions of the keys denote its contents: A key suffixed with `.crt` contains a stack /// of base64 encoded DER certificates, a key suffixed with `.der` contains a binary DER /// certificate. - Secret { - #[serde(flatten)] - secret: SecretReference, - }, + Secret(SecretReference), } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -491,18 +485,14 @@ mod test { key_generation: CertificateKeyGeneration::default() }, additional_trust_roots: vec![ - AdditionalTrustRoot::ConfigMap { - config_map: ConfigMapReference { - name: "tls-root-ca-config-map".to_string(), - namespace: "default".to_string(), - } - }, - AdditionalTrustRoot::Secret { - secret: SecretReference { - name: "tls-root-ca-secret".to_string(), - namespace: "default".to_string(), - } - } + AdditionalTrustRoot::ConfigMap(ConfigMapReference { + name: "tls-root-ca-config-map".to_string(), + namespace: "default".to_string(), + }), + AdditionalTrustRoot::Secret(SecretReference { + name: "tls-root-ca-secret".to_string(), + namespace: "default".to_string(), + }) ], max_certificate_lifetime: Duration::from_days_unchecked(31), }) From 7214718a08d342d1d00bb208ef628badeae4391c Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Fri, 21 Mar 2025 15:34:29 +0100 Subject: [PATCH 11/12] chore: Improve code style --- rust/operator-binary/src/backend/tls/ca.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rust/operator-binary/src/backend/tls/ca.rs b/rust/operator-binary/src/backend/tls/ca.rs index 6af1c3dd..5104479b 100644 --- a/rust/operator-binary/src/backend/tls/ca.rs +++ b/rust/operator-binary/src/backend/tls/ca.rs @@ -562,7 +562,7 @@ impl Manager { ) -> Result> { let extension = Path::new(key).extension().and_then(OsStr::to_str); - let certs = match extension { + match extension { Some("crt") => X509::stack_from_pem(value), Some("der") => X509::from_der(value).map(|cert| vec![cert]), _ => { @@ -576,9 +576,7 @@ impl Manager { .context(LoadCertificateSnafu { key, object: object_ref, - })?; - - Ok(certs) + }) } /// Get an appropriate [`CertificateAuthority`] for signing a given certificate. From 834f3b2a0a265baf1e9e49b2e43d9379d08b2142 Mon Sep 17 00:00:00 2001 From: Siegfried Weber Date: Fri, 21 Mar 2025 15:36:39 +0100 Subject: [PATCH 12/12] docs: Improve the description of additionalTrustRoots --- docs/modules/secret-operator/pages/secretclass.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/secret-operator/pages/secretclass.adoc b/docs/modules/secret-operator/pages/secretclass.adoc index 2e241b28..172d4841 100644 --- a/docs/modules/secret-operator/pages/secretclass.adoc +++ b/docs/modules/secret-operator/pages/secretclass.adoc @@ -133,8 +133,8 @@ spec: `additionalTrustRoots.secret`:: Reference (`name` and `namespace`) to a K8s `Secret` object where the trusted certificates are stored. The extension of a key defines its content: + * `.crt` denotes a stack of PEM (base64-encoded DER) certificates. * `.der` denotes a a binary DER certificates. - * `.crt` denotes a stack of base64 encoded DER certificates. `autoTls.maxCertificateLifetime`:: Maximum lifetime the created certificates are allowed to have. In case consumers request a longer lifetime than allowed by this setting, the lifetime will be the minimum of both. [#backend-certmanager]