diff --git a/crates/stackable-operator/CHANGELOG.md b/crates/stackable-operator/CHANGELOG.md index 69ca8485f..90d9293db 100644 --- a/crates/stackable-operator/CHANGELOG.md +++ b/crates/stackable-operator/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Add new configuration field `CommonConfiguration::min_secret_lifetime` and supporting `SecretOperatorVolumeSourceBuilder` code to use it ([#908]). + +[#908]: https://github.com/stackabletech/operator-rs/pull/908 + ## [0.81.0] - 2024-11-05 ### Added diff --git a/crates/stackable-operator/src/builder/pod/volume.rs b/crates/stackable-operator/src/builder/pod/volume.rs index 8f160d1ec..4d4e1e6a6 100644 --- a/crates/stackable-operator/src/builder/pod/volume.rs +++ b/crates/stackable-operator/src/builder/pod/volume.rs @@ -15,6 +15,7 @@ use tracing::warn; use crate::{ builder::meta::ObjectMetaBuilder, kvp::{Annotation, AnnotationError, Annotations, LabelError, Labels}, + time::Duration, }; /// A builder to build [`Volume`] objects. May only contain one `volume_source` @@ -280,6 +281,7 @@ pub struct SecretOperatorVolumeSourceBuilder { format: Option, kerberos_service_names: Vec, tls_pkcs12_password: Option, + secret_lifetime: Option, } impl SecretOperatorVolumeSourceBuilder { @@ -290,9 +292,15 @@ impl SecretOperatorVolumeSourceBuilder { format: None, kerberos_service_names: Vec::new(), tls_pkcs12_password: None, + secret_lifetime: None, } } + pub fn with_secret_lifetime(&mut self, lifetime: Option) -> &mut Self { + self.secret_lifetime = lifetime; + self + } + pub fn with_node_scope(&mut self) -> &mut Self { self.scopes.push(SecretOperatorVolumeScope::Node); self @@ -364,6 +372,13 @@ impl SecretOperatorVolumeSourceBuilder { } } + if let Some(lifetime) = &self.secret_lifetime { + annotations.insert( + Annotation::auto_tls_cert_lifetime(&lifetime.to_string()) + .context(ParseAnnotationSnafu)?, + ); + } + Ok(EphemeralVolumeSource { volume_claim_template: Some(PersistentVolumeClaimTemplate { metadata: Some(ObjectMetaBuilder::new().annotations(annotations).build()), diff --git a/crates/stackable-operator/src/kvp/annotation/mod.rs b/crates/stackable-operator/src/kvp/annotation/mod.rs index ed9ee4215..a64f327e0 100644 --- a/crates/stackable-operator/src/kvp/annotation/mod.rs +++ b/crates/stackable-operator/src/kvp/annotation/mod.rs @@ -137,6 +137,15 @@ impl Annotation { ))?; Ok(Self(kvp)) } + + /// Constructs a `secrets.stackable.tech/backend.autotls.cert.lifetime` annotation. + pub fn auto_tls_cert_lifetime(lifetime: &str) -> Result { + let kvp = KeyValuePair::try_from(( + "secrets.stackable.tech/backend.autotls.cert.lifetime", + lifetime, + ))?; + Ok(Self(kvp)) + } } /// A validated set/list of Kubernetes annotations. diff --git a/crates/stackable-operator/src/product_config_utils.rs b/crates/stackable-operator/src/product_config_utils.rs index 1d1a68c6a..2718d636e 100644 --- a/crates/stackable-operator/src/product_config_utils.rs +++ b/crates/stackable-operator/src/product_config_utils.rs @@ -523,6 +523,7 @@ mod tests { use super::*; use crate::role_utils::{Role, RoleGroup}; + use crate::time::Duration; use k8s_openapi::api::core::v1::PodTemplateSpec; use rstest::*; use std::collections::HashMap; @@ -617,6 +618,7 @@ mod tests { env_overrides: env_overrides.unwrap_or_default(), cli_overrides: cli_overrides.unwrap_or_default(), pod_overrides: PodTemplateSpec::default(), + min_secret_lifetime: Duration::from_secs(0), } } diff --git a/crates/stackable-operator/src/role_utils.rs b/crates/stackable-operator/src/role_utils.rs index 35941ea66..00a09789a 100644 --- a/crates/stackable-operator/src/role_utils.rs +++ b/crates/stackable-operator/src/role_utils.rs @@ -92,6 +92,7 @@ use crate::{ merge::Merge, }, product_config_utils::Configuration, + time::Duration, utils::crds::raw_object_schema, }; use derivative::Derivative; @@ -100,7 +101,7 @@ use kube::{runtime::reflector::ObjectRef, Resource}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)] #[serde( rename_all = "camelCase", bound(deserialize = "T: Default + Deserialize<'de>") @@ -144,6 +145,34 @@ pub struct CommonConfiguration { #[serde(default)] #[schemars(schema_with = "raw_object_schema")] pub pod_overrides: PodTemplateSpec, + + /// The minimum lifetime of secrets generated by the secret operator. + /// Some secrets, such as self signed certificates are constrained to a maximum lifetime by the + /// [SecretClass](DOCS_BASE_URL_PLACEHOLDER/secret-operator/secretclass) object it's self. + /// Currently this property covers self signed certificates but in the future it may be extended to other + /// secret types such as Kerberos keytabs. + #[serde(default = "default_min_secret_lifetime")] + pub min_secret_lifetime: Duration, +} + +impl Default for CommonConfiguration +where + T: Default, +{ + fn default() -> Self { + Self { + config: T::default(), + config_overrides: HashMap::default(), + env_overrides: HashMap::default(), + cli_overrides: BTreeMap::default(), + pod_overrides: PodTemplateSpec::default(), + min_secret_lifetime: Duration::from_secs(0), + } + } +} + +fn default_min_secret_lifetime() -> Duration { + Duration::from_hours_unchecked(24) } fn config_schema_default() -> serde_json::Value { @@ -203,6 +232,7 @@ where env_overrides: self.config.env_overrides, cli_overrides: self.config.cli_overrides, pod_overrides: self.config.pod_overrides, + min_secret_lifetime: self.config.min_secret_lifetime, }, role_config: self.role_config, role_groups: self @@ -219,6 +249,7 @@ where env_overrides: group.config.env_overrides, cli_overrides: group.config.cli_overrides, pod_overrides: group.config.pod_overrides, + min_secret_lifetime: group.config.min_secret_lifetime, }, replicas: group.replicas, },