From dfdc08fdf7510598198543dc1ffd6759c2b10996 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 20 Feb 2026 10:22:01 +0100 Subject: [PATCH 1/8] feat!: Add mandatory provison_parts argument to SecretOperatorVolumeSourceBuilder::new --- .../src/builder/pod/volume.rs | 32 ++++++++++++++++++- .../src/commons/secret_class.rs | 21 +++++++++--- .../src/commons/tls_verification.rs | 9 ++++-- .../crd/authentication/ldap/v1alpha1_impl.rs | 24 +++++++++++--- .../src/crd/s3/connection/mod.rs | 14 +++++--- .../src/crd/s3/connection/v1alpha1_impl.rs | 9 ++++-- .../src/kvp/annotation/mod.rs | 11 ++++++- 7 files changed, 100 insertions(+), 20 deletions(-) diff --git a/crates/stackable-operator/src/builder/pod/volume.rs b/crates/stackable-operator/src/builder/pod/volume.rs index 9b7a1bd98..1ea5824af 100644 --- a/crates/stackable-operator/src/builder/pod/volume.rs +++ b/crates/stackable-operator/src/builder/pod/volume.rs @@ -281,10 +281,21 @@ pub struct SecretOperatorVolumeSourceBuilder { kerberos_service_names: Vec, tls_pkcs12_password: Option, auto_tls_cert_lifetime: Option, + provision_parts: SecretOperatorVolumeProvisionParts, } impl SecretOperatorVolumeSourceBuilder { - pub fn new(secret_class: impl Into) -> Self { + /// Creates a builder for a secret-operator volume that uses the specified SecretClass to + /// request the specified [`SecretOperatorVolumeProvisionParts`]. + /// + /// This function forces the caller to make an explicit choice if the public parts are + /// sufficient or if private (e.g. a certificate for the Pod) parts are needed as well. + /// This is done to avoid accidentally requesting too much parts. For details see + /// [this issue](https://github.com/stackabletech/issues/issues/547). + pub fn new( + secret_class: impl Into, + provision_parts: SecretOperatorVolumeProvisionParts, + ) -> Self { Self { secret_class: secret_class.into(), scopes: Vec::new(), @@ -292,6 +303,7 @@ impl SecretOperatorVolumeSourceBuilder { kerberos_service_names: Vec::new(), tls_pkcs12_password: None, auto_tls_cert_lifetime: None, + provision_parts, } } @@ -342,6 +354,10 @@ impl SecretOperatorVolumeSourceBuilder { annotations .insert(Annotation::secret_class(&self.secret_class).context(ParseAnnotationSnafu)?); + annotations.insert( + Annotation::secret_provision_parts(&self.provision_parts) + .context(ParseAnnotationSnafu)?, + ); if !self.scopes.is_empty() { annotations @@ -417,6 +433,20 @@ pub enum SecretOperatorVolumeScope { ListenerVolume { name: String }, } +/// What parts secret-operator should provision into the requested volume. +#[derive(Clone, Debug, PartialEq, Eq, strum::AsRefStr)] +#[strum(serialize_all = "kebab-case")] +pub enum SecretOperatorVolumeProvisionParts { + /// Only provision public parts, such as the CA certificate (either as PEM or truststore) or + /// `krb5.conf`. + Public, + + /// Provision all parts, which includes all [`Public`](SecretOperatorVolumeProvisionParts::Public) + /// ones as well as additional private parts, such as a TLS cert + private key, a keystore or a + /// keytab. + Full, +} + /// Reference to a listener class or listener name #[derive(Clone, Debug, Eq, PartialEq)] pub enum ListenerReference { diff --git a/crates/stackable-operator/src/commons/secret_class.rs b/crates/stackable-operator/src/commons/secret_class.rs index 2ed8ca7de..ba757e020 100644 --- a/crates/stackable-operator/src/commons/secret_class.rs +++ b/crates/stackable-operator/src/commons/secret_class.rs @@ -4,7 +4,8 @@ use serde::{Deserialize, Serialize}; use snafu::{ResultExt, Snafu}; use crate::builder::pod::volume::{ - SecretOperatorVolumeSourceBuilder, SecretOperatorVolumeSourceBuilderError, VolumeBuilder, + SecretOperatorVolumeProvisionParts, SecretOperatorVolumeSourceBuilder, + SecretOperatorVolumeSourceBuilderError, VolumeBuilder, }; #[derive(Debug, PartialEq, Snafu)] @@ -38,9 +39,10 @@ impl SecretClassVolume { pub fn to_ephemeral_volume_source( &self, + provision_parts: SecretOperatorVolumeProvisionParts, ) -> Result { let mut secret_operator_volume_builder = - SecretOperatorVolumeSourceBuilder::new(&self.secret_class); + SecretOperatorVolumeSourceBuilder::new(&self.secret_class, provision_parts); if let Some(scope) = &self.scope { if scope.pod { @@ -62,8 +64,12 @@ impl SecretClassVolume { .context(SecretOperatorVolumeSnafu) } - pub fn to_volume(&self, volume_name: &str) -> Result { - let ephemeral = self.to_ephemeral_volume_source()?; + pub fn to_volume( + &self, + volume_name: &str, + provision_parts: SecretOperatorVolumeProvisionParts, + ) -> Result { + let ephemeral = self.to_ephemeral_volume_source(provision_parts)?; Ok(VolumeBuilder::new(volume_name).ephemeral(ephemeral).build()) } } @@ -111,7 +117,8 @@ mod tests { listener_volumes: vec!["mylistener".to_string()], }), } - .to_ephemeral_volume_source() + // Let's assume we need some form of private data (e.g. a certificate or S3 credentials) + .to_ephemeral_volume_source(SecretOperatorVolumeProvisionParts::Full) .unwrap(); let expected_volume_attributes = BTreeMap::from([ @@ -123,6 +130,10 @@ mod tests { "secrets.stackable.tech/scope".to_string(), "pod,service=myservice,listener-volume=mylistener".to_string(), ), + ( + "secrets.stackable.tech/provision-parts".to_string(), + "full".to_string(), + ), ]); assert_eq!( diff --git a/crates/stackable-operator/src/commons/tls_verification.rs b/crates/stackable-operator/src/commons/tls_verification.rs index 1e399b0cf..ebac05554 100644 --- a/crates/stackable-operator/src/commons/tls_verification.rs +++ b/crates/stackable-operator/src/commons/tls_verification.rs @@ -6,7 +6,11 @@ use snafu::{ResultExt, Snafu}; use crate::{ builder::{ self, - pod::{PodBuilder, container::ContainerBuilder, volume::VolumeMountBuilder}, + pod::{ + PodBuilder, + container::ContainerBuilder, + volume::{SecretOperatorVolumeProvisionParts, VolumeMountBuilder}, + }, }, commons::secret_class::{SecretClassVolume, SecretClassVolumeError}, constants::secret::SECRET_BASE_PATH, @@ -72,7 +76,8 @@ impl TlsClientDetails { let volume_name = format!("{secret_class}-ca-cert"); let secret_class_volume = SecretClassVolume::new(secret_class.clone(), None); let volume = secret_class_volume - .to_volume(&volume_name) + // We only need the public CA cert + .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::Public) .context(SecretClassVolumeSnafu)?; volumes.push(volume); diff --git a/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs b/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs index 674ddbd91..2c499de42 100644 --- a/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs +++ b/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs @@ -5,7 +5,11 @@ use url::Url; use crate::{ builder::{ self, - pod::{PodBuilder, container::ContainerBuilder, volume::VolumeMountBuilder}, + pod::{ + PodBuilder, + container::ContainerBuilder, + volume::{SecretOperatorVolumeProvisionParts, VolumeMountBuilder}, + }, }, commons::{secret_class::SecretClassVolumeError, tls_verification::TlsClientDetailsError}, constants::secret::SECRET_BASE_PATH, @@ -94,7 +98,8 @@ impl AuthenticationProvider { let secret_class = &bind_credentials.secret_class; let volume_name = format!("{secret_class}-bind-credentials"); let volume = bind_credentials - .to_volume(&volume_name) + // We need the private LDAP bind credentials + .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::Full) .context(BindCredentialsSnafu)?; volumes.push(volume); @@ -234,7 +239,10 @@ mod tests { secret_class: "ldap-ca-cert".to_string(), scope: None, } - .to_volume("ldap-ca-cert-ca-cert") + .to_volume( + "ldap-ca-cert-ca-cert", + SecretOperatorVolumeProvisionParts::Public + ) .unwrap() ] ); @@ -263,13 +271,19 @@ mod tests { secret_class: "openldap-bind-credentials".to_string(), scope: None, } - .to_volume("openldap-bind-credentials-bind-credentials") + .to_volume( + "openldap-bind-credentials-bind-credentials", + SecretOperatorVolumeProvisionParts::Full + ) .unwrap(), SecretClassVolume { secret_class: "ldap-ca-cert".to_string(), scope: None, } - .to_volume("ldap-ca-cert-ca-cert") + .to_volume( + "ldap-ca-cert-ca-cert", + SecretOperatorVolumeProvisionParts::Public + ) .unwrap() ] ); diff --git a/crates/stackable-operator/src/crd/s3/connection/mod.rs b/crates/stackable-operator/src/crd/s3/connection/mod.rs index b41c280d8..2955c4f8b 100644 --- a/crates/stackable-operator/src/crd/s3/connection/mod.rs +++ b/crates/stackable-operator/src/crd/s3/connection/mod.rs @@ -174,10 +174,16 @@ mod tests { .unwrap() .annotations .unwrap(), - &BTreeMap::from([( - "secrets.stackable.tech/class".to_string(), - "ionos-s3-credentials".to_string() - )]), + &BTreeMap::from([ + ( + "secrets.stackable.tech/class".to_string(), + "ionos-s3-credentials".to_string() + ), + ( + "secrets.stackable.tech/provision-parts".to_string(), + "full".to_string() + ) + ]), ); assert_eq!(mount.name, volume.name); diff --git a/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs b/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs index 0c6c1efd7..a7db249af 100644 --- a/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs +++ b/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs @@ -3,7 +3,11 @@ use snafu::{ResultExt as _, Snafu}; use url::Url; use crate::{ - builder::pod::{PodBuilder, container::ContainerBuilder, volume::VolumeMountBuilder}, + builder::pod::{ + PodBuilder, + container::ContainerBuilder, + volume::{SecretOperatorVolumeProvisionParts, VolumeMountBuilder}, + }, client::Client, commons::{secret_class::SecretClassVolumeError, tls_verification::TlsClientDetailsError}, constants::secret::SECRET_BASE_PATH, @@ -110,7 +114,8 @@ impl ConnectionSpec { volumes.push( credentials - .to_volume(&volume_name) + // We need the private S3 credentials + .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::Full) .context(AddS3CredentialVolumesSnafu)?, ); mounts.push( diff --git a/crates/stackable-operator/src/kvp/annotation/mod.rs b/crates/stackable-operator/src/kvp/annotation/mod.rs index efe7ec388..6d97a2298 100644 --- a/crates/stackable-operator/src/kvp/annotation/mod.rs +++ b/crates/stackable-operator/src/kvp/annotation/mod.rs @@ -18,7 +18,7 @@ use std::{ use delegate::delegate; use crate::{ - builder::pod::volume::SecretOperatorVolumeScope, + builder::pod::volume::{SecretOperatorVolumeProvisionParts, SecretOperatorVolumeScope}, iter::TryFromIterator, kvp::{Key, KeyValuePair, KeyValuePairError, KeyValuePairs, KeyValuePairsError}, }; @@ -80,6 +80,15 @@ impl Annotation { self.0 } + /// Constructs a `secrets.stackable.tech/provision-parts` annotation. + pub fn secret_provision_parts( + provision_parts: &SecretOperatorVolumeProvisionParts, + ) -> Result { + let kvp = + KeyValuePair::try_from(("secrets.stackable.tech/provision-parts", provision_parts))?; + Ok(Self(kvp)) + } + /// Constructs a `secrets.stackable.tech/class` annotation. pub fn secret_class(secret_class: &str) -> Result { let kvp = KeyValuePair::try_from(("secrets.stackable.tech/class", secret_class))?; From dee67f60c9510bbf1070f652d9c0aff7373450ee Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 20 Feb 2026 10:31:52 +0100 Subject: [PATCH 2/8] Full -> All --- crates/stackable-operator/src/builder/pod/volume.rs | 2 +- crates/stackable-operator/src/commons/secret_class.rs | 2 +- .../src/crd/authentication/ldap/v1alpha1_impl.rs | 4 ++-- .../stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/stackable-operator/src/builder/pod/volume.rs b/crates/stackable-operator/src/builder/pod/volume.rs index 1ea5824af..f8eb051dc 100644 --- a/crates/stackable-operator/src/builder/pod/volume.rs +++ b/crates/stackable-operator/src/builder/pod/volume.rs @@ -444,7 +444,7 @@ pub enum SecretOperatorVolumeProvisionParts { /// Provision all parts, which includes all [`Public`](SecretOperatorVolumeProvisionParts::Public) /// ones as well as additional private parts, such as a TLS cert + private key, a keystore or a /// keytab. - Full, + All, } /// Reference to a listener class or listener name diff --git a/crates/stackable-operator/src/commons/secret_class.rs b/crates/stackable-operator/src/commons/secret_class.rs index ba757e020..7f63300f8 100644 --- a/crates/stackable-operator/src/commons/secret_class.rs +++ b/crates/stackable-operator/src/commons/secret_class.rs @@ -118,7 +118,7 @@ mod tests { }), } // Let's assume we need some form of private data (e.g. a certificate or S3 credentials) - .to_ephemeral_volume_source(SecretOperatorVolumeProvisionParts::Full) + .to_ephemeral_volume_source(SecretOperatorVolumeProvisionParts::All) .unwrap(); let expected_volume_attributes = BTreeMap::from([ diff --git a/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs b/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs index 2c499de42..0e04eb470 100644 --- a/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs +++ b/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs @@ -99,7 +99,7 @@ impl AuthenticationProvider { let volume_name = format!("{secret_class}-bind-credentials"); let volume = bind_credentials // We need the private LDAP bind credentials - .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::Full) + .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::All) .context(BindCredentialsSnafu)?; volumes.push(volume); @@ -273,7 +273,7 @@ mod tests { } .to_volume( "openldap-bind-credentials-bind-credentials", - SecretOperatorVolumeProvisionParts::Full + SecretOperatorVolumeProvisionParts::All ) .unwrap(), SecretClassVolume { diff --git a/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs b/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs index a7db249af..85d9151e0 100644 --- a/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs +++ b/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs @@ -115,7 +115,7 @@ impl ConnectionSpec { volumes.push( credentials // We need the private S3 credentials - .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::Full) + .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::All) .context(AddS3CredentialVolumesSnafu)?, ); mounts.push( From 55df1a9b9b706bdd09e706198d76029e2a14ffd9 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Fri, 20 Feb 2026 10:32:59 +0100 Subject: [PATCH 3/8] fix tests --- crates/stackable-operator/src/commons/secret_class.rs | 2 +- crates/stackable-operator/src/crd/s3/connection/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/stackable-operator/src/commons/secret_class.rs b/crates/stackable-operator/src/commons/secret_class.rs index 7f63300f8..82d0bed1c 100644 --- a/crates/stackable-operator/src/commons/secret_class.rs +++ b/crates/stackable-operator/src/commons/secret_class.rs @@ -132,7 +132,7 @@ mod tests { ), ( "secrets.stackable.tech/provision-parts".to_string(), - "full".to_string(), + "all".to_string(), ), ]); diff --git a/crates/stackable-operator/src/crd/s3/connection/mod.rs b/crates/stackable-operator/src/crd/s3/connection/mod.rs index 2955c4f8b..38fed7980 100644 --- a/crates/stackable-operator/src/crd/s3/connection/mod.rs +++ b/crates/stackable-operator/src/crd/s3/connection/mod.rs @@ -181,7 +181,7 @@ mod tests { ), ( "secrets.stackable.tech/provision-parts".to_string(), - "full".to_string() + "all".to_string() ) ]), ); From 9e360aa0b17804a6e403c718806cfd34a6fe2a39 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 5 Mar 2026 08:48:17 +0100 Subject: [PATCH 4/8] Rename all -> public-private --- crates/stackable-operator/src/builder/pod/volume.rs | 2 +- crates/stackable-operator/src/commons/secret_class.rs | 4 ++-- .../src/crd/authentication/ldap/v1alpha1_impl.rs | 4 ++-- crates/stackable-operator/src/crd/s3/connection/mod.rs | 2 +- .../stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/stackable-operator/src/builder/pod/volume.rs b/crates/stackable-operator/src/builder/pod/volume.rs index f8eb051dc..204d0ddf4 100644 --- a/crates/stackable-operator/src/builder/pod/volume.rs +++ b/crates/stackable-operator/src/builder/pod/volume.rs @@ -444,7 +444,7 @@ pub enum SecretOperatorVolumeProvisionParts { /// Provision all parts, which includes all [`Public`](SecretOperatorVolumeProvisionParts::Public) /// ones as well as additional private parts, such as a TLS cert + private key, a keystore or a /// keytab. - All, + PublicPrivate, } /// Reference to a listener class or listener name diff --git a/crates/stackable-operator/src/commons/secret_class.rs b/crates/stackable-operator/src/commons/secret_class.rs index 82d0bed1c..df0c8d53e 100644 --- a/crates/stackable-operator/src/commons/secret_class.rs +++ b/crates/stackable-operator/src/commons/secret_class.rs @@ -118,7 +118,7 @@ mod tests { }), } // Let's assume we need some form of private data (e.g. a certificate or S3 credentials) - .to_ephemeral_volume_source(SecretOperatorVolumeProvisionParts::All) + .to_ephemeral_volume_source(SecretOperatorVolumeProvisionParts::PublicPrivate) .unwrap(); let expected_volume_attributes = BTreeMap::from([ @@ -132,7 +132,7 @@ mod tests { ), ( "secrets.stackable.tech/provision-parts".to_string(), - "all".to_string(), + "public-private".to_string(), ), ]); diff --git a/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs b/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs index 0e04eb470..57c2296e1 100644 --- a/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs +++ b/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs @@ -99,7 +99,7 @@ impl AuthenticationProvider { let volume_name = format!("{secret_class}-bind-credentials"); let volume = bind_credentials // We need the private LDAP bind credentials - .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::All) + .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::PublicPrivate) .context(BindCredentialsSnafu)?; volumes.push(volume); @@ -273,7 +273,7 @@ mod tests { } .to_volume( "openldap-bind-credentials-bind-credentials", - SecretOperatorVolumeProvisionParts::All + SecretOperatorVolumeProvisionParts::PublicPrivate ) .unwrap(), SecretClassVolume { diff --git a/crates/stackable-operator/src/crd/s3/connection/mod.rs b/crates/stackable-operator/src/crd/s3/connection/mod.rs index 38fed7980..bf0ccad7e 100644 --- a/crates/stackable-operator/src/crd/s3/connection/mod.rs +++ b/crates/stackable-operator/src/crd/s3/connection/mod.rs @@ -181,7 +181,7 @@ mod tests { ), ( "secrets.stackable.tech/provision-parts".to_string(), - "all".to_string() + "public-private".to_string() ) ]), ); diff --git a/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs b/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs index 85d9151e0..1749085a6 100644 --- a/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs +++ b/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs @@ -115,7 +115,7 @@ impl ConnectionSpec { volumes.push( credentials // We need the private S3 credentials - .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::All) + .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::PublicPrivate) .context(AddS3CredentialVolumesSnafu)?, ); mounts.push( From 94784044d824a4650cc539f2beec73088c5d843d Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 5 Mar 2026 08:50:55 +0100 Subject: [PATCH 5/8] changelog --- crates/stackable-operator/CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/stackable-operator/CHANGELOG.md b/crates/stackable-operator/CHANGELOG.md index 64d9bde2c..d432b1958 100644 --- a/crates/stackable-operator/CHANGELOG.md +++ b/crates/stackable-operator/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Changed + +- BREAKING: Add mandatory `provison_parts` argument to `SecretOperatorVolumeSourceBuilder::new` ([#1165]). + It now forces the caller to make an explicit choice if the public parts are sufficient or if private + (e.g. a certificate for the Pod) parts are needed as well. This is done to avoid accidentally requesting + too much parts. For details see [this issue](https://github.com/stackabletech/issues/issues/547). + +[#1165]: https://github.com/stackabletech/operator-rs/pull/1165 + ## [0.106.2] - 2026-02-26 ### Changed From 26d8c63801036126e854bb801bfe5ee84d89367a Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 5 Mar 2026 08:59:53 +0100 Subject: [PATCH 6/8] Move enum --- .../src/builder/pod/volume.rs | 21 +++------------- crates/stackable-operator/src/client.rs | 2 +- .../src/commons/secret_class.rs | 25 +++++++++++++++---- .../src/commons/tls_verification.rs | 12 ++++----- .../crd/authentication/ldap/v1alpha1_impl.rs | 19 +++++++------- .../src/crd/s3/connection/v1alpha1_impl.rs | 13 +++++----- .../src/kvp/annotation/mod.rs | 5 ++-- 7 files changed, 48 insertions(+), 49 deletions(-) diff --git a/crates/stackable-operator/src/builder/pod/volume.rs b/crates/stackable-operator/src/builder/pod/volume.rs index 204d0ddf4..d5a53e965 100644 --- a/crates/stackable-operator/src/builder/pod/volume.rs +++ b/crates/stackable-operator/src/builder/pod/volume.rs @@ -14,6 +14,7 @@ use tracing::warn; use crate::{ builder::meta::ObjectMetaBuilder, + commons::secret_class::SecretClassVolumeProvisionParts, kvp::{Annotation, AnnotationError, Annotations, LabelError, Labels}, }; @@ -281,12 +282,12 @@ pub struct SecretOperatorVolumeSourceBuilder { kerberos_service_names: Vec, tls_pkcs12_password: Option, auto_tls_cert_lifetime: Option, - provision_parts: SecretOperatorVolumeProvisionParts, + provision_parts: SecretClassVolumeProvisionParts, } impl SecretOperatorVolumeSourceBuilder { /// Creates a builder for a secret-operator volume that uses the specified SecretClass to - /// request the specified [`SecretOperatorVolumeProvisionParts`]. + /// request the specified [`SecretClassVolumeProvisionParts`]. /// /// This function forces the caller to make an explicit choice if the public parts are /// sufficient or if private (e.g. a certificate for the Pod) parts are needed as well. @@ -294,7 +295,7 @@ impl SecretOperatorVolumeSourceBuilder { /// [this issue](https://github.com/stackabletech/issues/issues/547). pub fn new( secret_class: impl Into, - provision_parts: SecretOperatorVolumeProvisionParts, + provision_parts: SecretClassVolumeProvisionParts, ) -> Self { Self { secret_class: secret_class.into(), @@ -433,20 +434,6 @@ pub enum SecretOperatorVolumeScope { ListenerVolume { name: String }, } -/// What parts secret-operator should provision into the requested volume. -#[derive(Clone, Debug, PartialEq, Eq, strum::AsRefStr)] -#[strum(serialize_all = "kebab-case")] -pub enum SecretOperatorVolumeProvisionParts { - /// Only provision public parts, such as the CA certificate (either as PEM or truststore) or - /// `krb5.conf`. - Public, - - /// Provision all parts, which includes all [`Public`](SecretOperatorVolumeProvisionParts::Public) - /// ones as well as additional private parts, such as a TLS cert + private key, a keystore or a - /// keytab. - PublicPrivate, -} - /// Reference to a listener class or listener name #[derive(Clone, Debug, Eq, PartialEq)] pub enum ListenerReference { diff --git a/crates/stackable-operator/src/client.rs b/crates/stackable-operator/src/client.rs index bb9ceb122..3c27863ba 100644 --- a/crates/stackable-operator/src/client.rs +++ b/crates/stackable-operator/src/client.rs @@ -590,7 +590,7 @@ impl Client { pub trait GetApi: Resource + Sized { /// The namespace type for `Self`'s scope. /// - /// This will be [`str`] for namespaced resource, and [`()`] for cluster-scoped resources. + /// This will be [`str`] for namespaced resource, and `()` for cluster-scoped resources. type Namespace: ?Sized; /// Get a [`kube::Api`] for `Self`'s native scope.. fn get_api(client: kube::Client, ns: &Self::Namespace) -> kube::Api diff --git a/crates/stackable-operator/src/commons/secret_class.rs b/crates/stackable-operator/src/commons/secret_class.rs index df0c8d53e..a9a4152fe 100644 --- a/crates/stackable-operator/src/commons/secret_class.rs +++ b/crates/stackable-operator/src/commons/secret_class.rs @@ -4,8 +4,7 @@ use serde::{Deserialize, Serialize}; use snafu::{ResultExt, Snafu}; use crate::builder::pod::volume::{ - SecretOperatorVolumeProvisionParts, SecretOperatorVolumeSourceBuilder, - SecretOperatorVolumeSourceBuilderError, VolumeBuilder, + SecretOperatorVolumeSourceBuilder, SecretOperatorVolumeSourceBuilderError, VolumeBuilder, }; #[derive(Debug, PartialEq, Snafu)] @@ -39,7 +38,7 @@ impl SecretClassVolume { pub fn to_ephemeral_volume_source( &self, - provision_parts: SecretOperatorVolumeProvisionParts, + provision_parts: SecretClassVolumeProvisionParts, ) -> Result { let mut secret_operator_volume_builder = SecretOperatorVolumeSourceBuilder::new(&self.secret_class, provision_parts); @@ -67,7 +66,7 @@ impl SecretClassVolume { pub fn to_volume( &self, volume_name: &str, - provision_parts: SecretOperatorVolumeProvisionParts, + provision_parts: SecretClassVolumeProvisionParts, ) -> Result { let ephemeral = self.to_ephemeral_volume_source(provision_parts)?; Ok(VolumeBuilder::new(volume_name).ephemeral(ephemeral).build()) @@ -100,6 +99,22 @@ pub struct SecretClassVolumeScope { pub listener_volumes: Vec, } +/// What parts secret-operator should provision into the requested volume. +// +// There intentionally isn't a global [`Default`] impl, as it's secret-ops concern what it chooses +// as a default. +#[derive(Clone, Debug, PartialEq, Eq, strum::AsRefStr)] +#[strum(serialize_all = "kebab-case")] +pub enum SecretClassVolumeProvisionParts { + /// Only provision public parts, such as the CA certificate (either as PEM or truststore) or + /// `krb5.conf`. + Public, + + /// Provision all parts, which includes all [`Public`](Self::Public) ones as well as additional + /// private parts, such as a TLS cert + private key, a keystore or a keytab. + PublicPrivate, +} + #[cfg(test)] mod tests { use std::collections::BTreeMap; @@ -118,7 +133,7 @@ mod tests { }), } // Let's assume we need some form of private data (e.g. a certificate or S3 credentials) - .to_ephemeral_volume_source(SecretOperatorVolumeProvisionParts::PublicPrivate) + .to_ephemeral_volume_source(SecretClassVolumeProvisionParts::PublicPrivate) .unwrap(); let expected_volume_attributes = BTreeMap::from([ diff --git a/crates/stackable-operator/src/commons/tls_verification.rs b/crates/stackable-operator/src/commons/tls_verification.rs index ebac05554..e0b97b440 100644 --- a/crates/stackable-operator/src/commons/tls_verification.rs +++ b/crates/stackable-operator/src/commons/tls_verification.rs @@ -6,13 +6,11 @@ use snafu::{ResultExt, Snafu}; use crate::{ builder::{ self, - pod::{ - PodBuilder, - container::ContainerBuilder, - volume::{SecretOperatorVolumeProvisionParts, VolumeMountBuilder}, - }, + pod::{PodBuilder, container::ContainerBuilder, volume::VolumeMountBuilder}, + }, + commons::secret_class::{ + SecretClassVolume, SecretClassVolumeError, SecretClassVolumeProvisionParts, }, - commons::secret_class::{SecretClassVolume, SecretClassVolumeError}, constants::secret::SECRET_BASE_PATH, }; @@ -77,7 +75,7 @@ impl TlsClientDetails { let secret_class_volume = SecretClassVolume::new(secret_class.clone(), None); let volume = secret_class_volume // We only need the public CA cert - .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::Public) + .to_volume(&volume_name, SecretClassVolumeProvisionParts::Public) .context(SecretClassVolumeSnafu)?; volumes.push(volume); diff --git a/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs b/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs index 57c2296e1..d0dbcc990 100644 --- a/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs +++ b/crates/stackable-operator/src/crd/authentication/ldap/v1alpha1_impl.rs @@ -5,13 +5,12 @@ use url::Url; use crate::{ builder::{ self, - pod::{ - PodBuilder, - container::ContainerBuilder, - volume::{SecretOperatorVolumeProvisionParts, VolumeMountBuilder}, - }, + pod::{PodBuilder, container::ContainerBuilder, volume::VolumeMountBuilder}, + }, + commons::{ + secret_class::{SecretClassVolumeError, SecretClassVolumeProvisionParts}, + tls_verification::TlsClientDetailsError, }, - commons::{secret_class::SecretClassVolumeError, tls_verification::TlsClientDetailsError}, constants::secret::SECRET_BASE_PATH, crd::authentication::ldap::v1alpha1::{AuthenticationProvider, FieldNames}, }; @@ -99,7 +98,7 @@ impl AuthenticationProvider { let volume_name = format!("{secret_class}-bind-credentials"); let volume = bind_credentials // We need the private LDAP bind credentials - .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::PublicPrivate) + .to_volume(&volume_name, SecretClassVolumeProvisionParts::PublicPrivate) .context(BindCredentialsSnafu)?; volumes.push(volume); @@ -241,7 +240,7 @@ mod tests { } .to_volume( "ldap-ca-cert-ca-cert", - SecretOperatorVolumeProvisionParts::Public + SecretClassVolumeProvisionParts::Public ) .unwrap() ] @@ -273,7 +272,7 @@ mod tests { } .to_volume( "openldap-bind-credentials-bind-credentials", - SecretOperatorVolumeProvisionParts::PublicPrivate + SecretClassVolumeProvisionParts::PublicPrivate ) .unwrap(), SecretClassVolume { @@ -282,7 +281,7 @@ mod tests { } .to_volume( "ldap-ca-cert-ca-cert", - SecretOperatorVolumeProvisionParts::Public + SecretClassVolumeProvisionParts::Public ) .unwrap() ] diff --git a/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs b/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs index 1749085a6..78f5c701f 100644 --- a/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs +++ b/crates/stackable-operator/src/crd/s3/connection/v1alpha1_impl.rs @@ -3,13 +3,12 @@ use snafu::{ResultExt as _, Snafu}; use url::Url; use crate::{ - builder::pod::{ - PodBuilder, - container::ContainerBuilder, - volume::{SecretOperatorVolumeProvisionParts, VolumeMountBuilder}, - }, + builder::pod::{PodBuilder, container::ContainerBuilder, volume::VolumeMountBuilder}, client::Client, - commons::{secret_class::SecretClassVolumeError, tls_verification::TlsClientDetailsError}, + commons::{ + secret_class::{SecretClassVolumeError, SecretClassVolumeProvisionParts}, + tls_verification::TlsClientDetailsError, + }, constants::secret::SECRET_BASE_PATH, crd::s3::{ connection::ResolvedConnection, @@ -115,7 +114,7 @@ impl ConnectionSpec { volumes.push( credentials // We need the private S3 credentials - .to_volume(&volume_name, SecretOperatorVolumeProvisionParts::PublicPrivate) + .to_volume(&volume_name, SecretClassVolumeProvisionParts::PublicPrivate) .context(AddS3CredentialVolumesSnafu)?, ); mounts.push( diff --git a/crates/stackable-operator/src/kvp/annotation/mod.rs b/crates/stackable-operator/src/kvp/annotation/mod.rs index 6d97a2298..402fa362b 100644 --- a/crates/stackable-operator/src/kvp/annotation/mod.rs +++ b/crates/stackable-operator/src/kvp/annotation/mod.rs @@ -18,7 +18,8 @@ use std::{ use delegate::delegate; use crate::{ - builder::pod::volume::{SecretOperatorVolumeProvisionParts, SecretOperatorVolumeScope}, + builder::pod::volume::SecretOperatorVolumeScope, + commons::secret_class::SecretClassVolumeProvisionParts, iter::TryFromIterator, kvp::{Key, KeyValuePair, KeyValuePairError, KeyValuePairs, KeyValuePairsError}, }; @@ -82,7 +83,7 @@ impl Annotation { /// Constructs a `secrets.stackable.tech/provision-parts` annotation. pub fn secret_provision_parts( - provision_parts: &SecretOperatorVolumeProvisionParts, + provision_parts: &SecretClassVolumeProvisionParts, ) -> Result { let kvp = KeyValuePair::try_from(("secrets.stackable.tech/provision-parts", provision_parts))?; From 4f32fa14b5d4478d0167df987d5a2f22a85eed7d Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 5 Mar 2026 09:02:34 +0100 Subject: [PATCH 7/8] Derive Copy --- crates/stackable-operator/src/commons/secret_class.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/stackable-operator/src/commons/secret_class.rs b/crates/stackable-operator/src/commons/secret_class.rs index a9a4152fe..ddd48075e 100644 --- a/crates/stackable-operator/src/commons/secret_class.rs +++ b/crates/stackable-operator/src/commons/secret_class.rs @@ -103,7 +103,7 @@ pub struct SecretClassVolumeScope { // // There intentionally isn't a global [`Default`] impl, as it's secret-ops concern what it chooses // as a default. -#[derive(Clone, Debug, PartialEq, Eq, strum::AsRefStr)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, strum::AsRefStr)] #[strum(serialize_all = "kebab-case")] pub enum SecretClassVolumeProvisionParts { /// Only provision public parts, such as the CA certificate (either as PEM or truststore) or From 8c228c7fd8ecb0ae896f6ffb0598d246fb936772 Mon Sep 17 00:00:00 2001 From: Sebastian Bernauer Date: Thu, 5 Mar 2026 09:23:15 +0100 Subject: [PATCH 8/8] Improve changelog --- crates/stackable-operator/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/stackable-operator/CHANGELOG.md b/crates/stackable-operator/CHANGELOG.md index d432b1958..425ab61c0 100644 --- a/crates/stackable-operator/CHANGELOG.md +++ b/crates/stackable-operator/CHANGELOG.md @@ -6,11 +6,14 @@ All notable changes to this project will be documented in this file. ### Changed -- BREAKING: Add mandatory `provison_parts` argument to `SecretOperatorVolumeSourceBuilder::new` ([#1165]). +- BREAKING: Add mandatory `provision_parts` argument to `SecretOperatorVolumeSourceBuilder::new` ([#1165]). It now forces the caller to make an explicit choice if the public parts are sufficient or if private (e.g. a certificate for the Pod) parts are needed as well. This is done to avoid accidentally requesting too much parts. For details see [this issue](https://github.com/stackabletech/issues/issues/547). + Additionally, `SecretClassVolume::to_volume` and `SecretClassVolume::to_ephemeral_volume_source` + also take the same new argument. + [#1165]: https://github.com/stackabletech/operator-rs/pull/1165 ## [0.106.2] - 2026-02-26