diff --git a/rust/operator-binary/src/crd/authentication.rs b/rust/operator-binary/src/crd/authentication.rs index bdcd08b9..6ee646d3 100644 --- a/rust/operator-binary/src/crd/authentication.rs +++ b/rust/operator-binary/src/crd/authentication.rs @@ -51,32 +51,64 @@ pub mod versioned { } #[derive(Clone, Debug)] -/// Helper struct that contains resolved AuthenticationClasses to reduce network API calls. -pub struct ResolvedAuthenticationClasses { - resolved_authentication_classes: Vec, +/// Helper struct that contains dereferenced AuthenticationClasses to reduce network API calls. +pub struct DereferencedAuthenticationClasses { + dereferenced_authentication_classes: Vec, } -impl ResolvedAuthenticationClasses { +impl DereferencedAuthenticationClasses { + /// Fetch the referenced AuthenticationClasses from the Kubernetes API without validating them. + /// + /// Call [`Self::validate`] on the result to enforce the constraints documented there. + pub async fn fetch_references( + client: &Client, + auth_classes: &Vec, + ) -> Result { + let mut dereferenced_authentication_classes: Vec = + vec![]; + + for auth_class in auth_classes { + dereferenced_authentication_classes.push( + core::v1alpha1::AuthenticationClass::resolve( + client, + &auth_class.authentication_class, + ) + .await + .context(AuthenticationClassRetrievalSnafu { + authentication_class: ObjectRef::::new( + &auth_class.authentication_class, + ), + })?, + ); + } + + Ok(DereferencedAuthenticationClasses { + dereferenced_authentication_classes, + }) + } + /// Return the (first) TLS `AuthenticationClass` if available pub fn get_tls_authentication_class(&self) -> Option<&core::v1alpha1::AuthenticationClass> { - self.resolved_authentication_classes.iter().find(|auth| { - matches!( - auth.spec.provider, - core::v1alpha1::AuthenticationClassProvider::Tls(_) - ) - }) + self.dereferenced_authentication_classes + .iter() + .find(|auth| { + matches!( + auth.spec.provider, + core::v1alpha1::AuthenticationClassProvider::Tls(_) + ) + }) } - /// Validates the resolved AuthenticationClasses. + /// Validates the dereferenced AuthenticationClasses. /// Currently errors out if: /// - More than one AuthenticationClass was provided /// - AuthenticationClass mechanism was not supported pub fn validate(&self) -> Result { - if self.resolved_authentication_classes.len() > 1 { + if self.dereferenced_authentication_classes.len() > 1 { return Err(Error::MultipleAuthenticationClassesProvided); } - for auth_class in &self.resolved_authentication_classes { + for auth_class in &self.dereferenced_authentication_classes { match &auth_class.spec.provider { core::v1alpha1::AuthenticationClassProvider::Tls(_) => {} core::v1alpha1::AuthenticationClassProvider::Ldap(_) @@ -96,36 +128,8 @@ impl ResolvedAuthenticationClasses { /// USE ONLY IN TESTS! We can not put it behind `#[cfg(test)]` because of pub fn new_for_tests() -> Self { - ResolvedAuthenticationClasses { - resolved_authentication_classes: vec![], + DereferencedAuthenticationClasses { + dereferenced_authentication_classes: vec![], } } } - -/// Resolve provided AuthenticationClasses via API calls and validate the contents. -/// Currently errors out if: -/// - AuthenticationClass could not be resolved -/// - Validation failed -pub async fn resolve_authentication_classes( - client: &Client, - auth_classes: &Vec, -) -> Result { - let mut resolved_authentication_classes: Vec = vec![]; - - for auth_class in auth_classes { - resolved_authentication_classes.push( - core::v1alpha1::AuthenticationClass::resolve(client, &auth_class.authentication_class) - .await - .context(AuthenticationClassRetrievalSnafu { - authentication_class: ObjectRef::::new( - &auth_class.authentication_class, - ), - })?, - ); - } - - ResolvedAuthenticationClasses { - resolved_authentication_classes, - } - .validate() -} diff --git a/rust/operator-binary/src/crd/security.rs b/rust/operator-binary/src/crd/security.rs index 2f7c43b4..12a95b26 100644 --- a/rust/operator-binary/src/crd/security.rs +++ b/rust/operator-binary/src/crd/security.rs @@ -26,7 +26,7 @@ use stackable_operator::{ }; use crate::{ - crd::{authentication::ResolvedAuthenticationClasses, tls, v1alpha1}, + crd::{authentication::DereferencedAuthenticationClasses, tls, v1alpha1}, zk_controller::LISTENER_VOLUME_NAME, }; @@ -51,7 +51,7 @@ pub enum Error { /// Helper struct combining TLS settings for server and quorum with the resolved AuthenticationClasses pub struct ZookeeperSecurity { - resolved_authentication_classes: ResolvedAuthenticationClasses, + resolved_authentication_classes: DereferencedAuthenticationClasses, server_secret_class: Option, quorum_secret_class: String, } @@ -90,11 +90,11 @@ impl ZookeeperSecurity { pub const SYSTEM_TRUST_STORE_DIR: &'static str = "/etc/pki/java/cacerts"; /// Build a `ZookeeperSecurity` from a [`v1alpha1::ZookeeperCluster`] and already-resolved - /// [`ResolvedAuthenticationClasses`]. Synchronous; intended to be called from the validate + /// [`DereferencedAuthenticationClasses`]. Synchronous; intended to be called from the validate /// step of the controllers. pub fn new( zk: &v1alpha1::ZookeeperCluster, - resolved_authentication_classes: ResolvedAuthenticationClasses, + resolved_authentication_classes: DereferencedAuthenticationClasses, ) -> Self { ZookeeperSecurity { resolved_authentication_classes, @@ -351,7 +351,7 @@ impl ZookeeperSecurity { /// USE ONLY IN TESTS! We can not put it behind `#[cfg(test)]` because of pub fn new_for_tests() -> Self { ZookeeperSecurity { - resolved_authentication_classes: ResolvedAuthenticationClasses::new_for_tests(), + resolved_authentication_classes: DereferencedAuthenticationClasses::new_for_tests(), server_secret_class: Some("tls".to_owned()), quorum_secret_class: "tls".to_string(), } diff --git a/rust/operator-binary/src/zk_controller/dereference.rs b/rust/operator-binary/src/zk_controller/dereference.rs index f7cbd17b..4b911431 100644 --- a/rust/operator-binary/src/zk_controller/dereference.rs +++ b/rust/operator-binary/src/zk_controller/dereference.rs @@ -9,21 +9,22 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::client::Client; use crate::crd::{ - authentication::{self, ResolvedAuthenticationClasses}, + authentication::{self, DereferencedAuthenticationClasses}, v1alpha1, }; #[derive(Snafu, Debug)] pub enum Error { - #[snafu(display("failed to resolve authentication classes"))] - ResolveAuthenticationClasses { source: authentication::Error }, + #[snafu(display("failed to fetch authentication classes"))] + FetchAuthenticationClasses { source: authentication::Error }, } type Result = std::result::Result; -/// Kubernetes objects referenced from the [`v1alpha1::ZookeeperCluster`] spec, already fetched. +/// Kubernetes objects referenced from the [`v1alpha1::ZookeeperCluster`] spec, already fetched but +/// not yet validated. pub struct DereferencedObjects { - pub resolved_authentication_classes: ResolvedAuthenticationClasses, + pub authentication_classes: DereferencedAuthenticationClasses, } /// Fetches all Kubernetes objects referenced from the [`v1alpha1::ZookeeperCluster`] spec. @@ -31,14 +32,14 @@ pub async fn dereference( client: &Client, zk: &v1alpha1::ZookeeperCluster, ) -> Result { - let resolved_authentication_classes = authentication::resolve_authentication_classes( + let authentication_classes = DereferencedAuthenticationClasses::fetch_references( client, &zk.spec.cluster_config.authentication, ) .await - .context(ResolveAuthenticationClassesSnafu)?; + .context(FetchAuthenticationClassesSnafu)?; Ok(DereferencedObjects { - resolved_authentication_classes, + authentication_classes, }) } diff --git a/rust/operator-binary/src/zk_controller/validate.rs b/rust/operator-binary/src/zk_controller/validate.rs index 9f1fe157..e58841f5 100644 --- a/rust/operator-binary/src/zk_controller/validate.rs +++ b/rust/operator-binary/src/zk_controller/validate.rs @@ -17,7 +17,7 @@ use stackable_operator::{ use crate::{ crd::{ CONTAINER_IMAGE_BASE_NAME, JVM_SECURITY_PROPERTIES_FILE, ZOOKEEPER_PROPERTIES_FILE, - ZookeeperRole, security::ZookeeperSecurity, v1alpha1, + ZookeeperRole, authentication, security::ZookeeperSecurity, v1alpha1, }, zk_controller::dereference::DereferencedObjects, }; @@ -29,6 +29,9 @@ pub enum Error { source: product_image_selection::Error, }, + #[snafu(display("failed to validate authentication classes"))] + InvalidAuthenticationClassConfiguration { source: authentication::Error }, + #[snafu(display("object defines no server role"))] NoServerRole, @@ -69,10 +72,12 @@ pub fn validate( ) .context(ResolveProductImageSnafu)?; - let zookeeper_security = ZookeeperSecurity::new( - zk, - dereferenced_objects.resolved_authentication_classes.clone(), - ); + let resolved_authentication_classes = dereferenced_objects + .authentication_classes + .validate() + .context(InvalidAuthenticationClassConfigurationSnafu)?; + + let zookeeper_security = ZookeeperSecurity::new(zk, resolved_authentication_classes); let validated_role_config = validated_product_config(zk, &resolved_product_image.product_version, product_config)?; diff --git a/rust/operator-binary/src/znode_controller.rs b/rust/operator-binary/src/znode_controller.rs index 6e8d0da2..414f57ab 100644 --- a/rust/operator-binary/src/znode_controller.rs +++ b/rust/operator-binary/src/znode_controller.rs @@ -267,7 +267,7 @@ pub async fn reconcile_znode( // block finalizer removal. let zookeeper_security = ZookeeperSecurity::new( &dereferenced.zk, - dereferenced.resolved_authentication_classes.clone(), + dereferenced.authentication_classes.clone(), ); reconcile_cleanup(client, dereferenced.zk, &zookeeper_security, &znode_path) .await diff --git a/rust/operator-binary/src/znode_controller/dereference.rs b/rust/operator-binary/src/znode_controller/dereference.rs index 1e79bfd4..d29ee402 100644 --- a/rust/operator-binary/src/znode_controller/dereference.rs +++ b/rust/operator-binary/src/znode_controller/dereference.rs @@ -1,8 +1,9 @@ //! The dereference step in the ZookeeperZnode controller. //! //! Fetches the parent [`v1alpha1::ZookeeperCluster`] referenced by the znode's -//! `spec.clusterRef`, plus the [`ResolvedAuthenticationClasses`] of that cluster. Both Apply -//! and Cleanup paths in `reconcile_znode` share this output. +//! `spec.clusterRef`, plus the [`DereferencedAuthenticationClasses`] of that cluster. Both Apply +//! and Cleanup paths in `reconcile_znode` share this output. Synchronous validation of the +//! fetched objects happens in the validate step. use snafu::{ResultExt, Snafu}; use stackable_operator::{ @@ -11,7 +12,7 @@ use stackable_operator::{ }; use crate::crd::{ - authentication::{self, ResolvedAuthenticationClasses}, + authentication::{self, DereferencedAuthenticationClasses}, v1alpha1, }; @@ -32,8 +33,8 @@ pub enum Error { zk: ObjectRef, }, - #[snafu(display("failed to resolve authentication classes"))] - ResolveAuthenticationClasses { source: authentication::Error }, + #[snafu(display("failed to fetch authentication classes"))] + FetchAuthenticationClasses { source: authentication::Error }, } type Result = std::result::Result; @@ -41,7 +42,7 @@ type Result = std::result::Result; /// Kubernetes objects referenced from the [`v1alpha1::ZookeeperZnode`] spec, already fetched. pub struct DereferencedObjects { pub zk: v1alpha1::ZookeeperCluster, - pub resolved_authentication_classes: ResolvedAuthenticationClasses, + pub authentication_classes: DereferencedAuthenticationClasses, } /// Fetches all Kubernetes objects referenced from the [`v1alpha1::ZookeeperZnode`] spec. @@ -51,16 +52,16 @@ pub async fn dereference( ) -> Result { let zk = find_zk_of_znode(client, znode).await?; - let resolved_authentication_classes = authentication::resolve_authentication_classes( + let authentication_classes = DereferencedAuthenticationClasses::fetch_references( client, &zk.spec.cluster_config.authentication, ) .await - .context(ResolveAuthenticationClassesSnafu)?; + .context(FetchAuthenticationClassesSnafu)?; Ok(DereferencedObjects { zk, - resolved_authentication_classes, + authentication_classes, }) } diff --git a/rust/operator-binary/src/znode_controller/validate.rs b/rust/operator-binary/src/znode_controller/validate.rs index 27ec2e90..84fab0ce 100644 --- a/rust/operator-binary/src/znode_controller/validate.rs +++ b/rust/operator-binary/src/znode_controller/validate.rs @@ -10,7 +10,7 @@ use stackable_operator::{ }; use crate::{ - crd::{CONTAINER_IMAGE_BASE_NAME, security::ZookeeperSecurity, v1alpha1}, + crd::{CONTAINER_IMAGE_BASE_NAME, authentication, security::ZookeeperSecurity, v1alpha1}, znode_controller::dereference::DereferencedObjects, }; @@ -20,6 +20,9 @@ pub enum Error { ResolveProductImage { source: product_image_selection::Error, }, + + #[snafu(display("failed to validate authentication classes"))] + InvalidAuthenticationClassConfiguration { source: authentication::Error }, } type Result = std::result::Result; @@ -47,10 +50,13 @@ pub fn validate( ) .context(ResolveProductImageSnafu)?; - let zookeeper_security = ZookeeperSecurity::new( - &dereferenced_objects.zk, - dereferenced_objects.resolved_authentication_classes.clone(), - ); + let resolved_authentication_classes = dereferenced_objects + .authentication_classes + .validate() + .context(InvalidAuthenticationClassConfigurationSnafu)?; + + let zookeeper_security = + ZookeeperSecurity::new(&dereferenced_objects.zk, resolved_authentication_classes); Ok(ValidatedInputs { resolved_product_image,