Skip to content

Commit 424d91f

Browse files
committed
refactor: consolidate authentication mod in validate and build
1 parent 101aa09 commit 424d91f

10 files changed

Lines changed: 307 additions & 287 deletions

File tree

Lines changed: 29 additions & 258 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,20 @@
1-
use std::collections::BTreeMap;
1+
//! The validated Druid authentication decision.
2+
//!
3+
//! [`DruidAuthenticationConfig`] is the validated representation of the cluster's authentication
4+
//! settings, produced by [`DruidAuthenticationConfig::from_auth_classes`] during the validate step.
5+
//! The Kubernetes/config rendering derived from it (runtime.properties, container commands, volumes,
6+
//! env vars) lives in the build step (`controller::build::authentication`).
27
3-
use snafu::Snafu;
4-
use stackable_operator::{
5-
builder::pod::{PodBuilder, container::ContainerBuilder},
6-
crd::authentication,
7-
k8s_openapi::api::core::v1::EnvVar,
8-
};
8+
use stackable_operator::crd::authentication;
99

10-
use crate::{
11-
controller::validate::ValidatedCluster,
12-
crd::{
13-
DruidRole,
14-
authentication::{AuthenticationClassResolved, AuthenticationClassesResolved},
15-
env_var_reference,
16-
security::INTERNAL_INITIAL_CLIENT_PASSWORD_ENV,
17-
},
18-
internal_secret::{build_shared_internal_secret_name, env_var_from_secret},
19-
};
20-
21-
pub mod ldap;
22-
pub mod oidc;
23-
24-
// It seems this needs to be the same password for Druid to work, so we re-use the existing env variable.
25-
const ESCALATOR_INTERNAL_CLIENT_PASSWORD_ENV: &str = INTERNAL_INITIAL_CLIENT_PASSWORD_ENV;
26-
27-
/// Configures the given provider authorizer (e.g. `"LdapAuthorizer"`) alongside the Druid system
28-
/// authorizer, both with the `allowAll` authorizer type. Shared by the LDAP and OIDC providers.
29-
fn add_authorizer_config(config: &mut BTreeMap<String, String>, authorizer_name: &str) {
30-
config.insert(
31-
"druid.auth.authorizers".to_string(),
32-
format!(r#"["{authorizer_name}", "DruidSystemAuthorizer"]"#),
33-
);
34-
config.insert(
35-
format!("druid.auth.authorizer.{authorizer_name}.type"),
36-
"allowAll".to_string(),
37-
);
38-
}
39-
40-
/// Sets `druid.auth.authenticatorChain` to the Druid system authenticator followed by the given
41-
/// provider authenticators (e.g. `["Ldap"]`). Shared by the LDAP and OIDC providers.
42-
fn set_authenticator_chain(
43-
config: &mut BTreeMap<String, String>,
44-
provider_authenticators: &[&str],
45-
) {
46-
let authenticators: Vec<String> = std::iter::once("DruidSystemAuthenticator")
47-
.chain(provider_authenticators.iter().copied())
48-
.map(|name| format!("\"{name}\""))
49-
.collect();
50-
config.insert(
51-
"druid.auth.authenticatorChain".to_string(),
52-
format!("[{}]", authenticators.join(", ")),
53-
);
54-
}
10+
use crate::crd::authentication::{AuthenticationClassResolved, AuthenticationClassesResolved};
5511

56-
type Result<T, E = Error> = std::result::Result<T, E>;
57-
58-
#[derive(Snafu, Debug)]
59-
pub enum Error {
60-
#[snafu(display("failed to create LDAP endpoint url."))]
61-
ConstructLdapEndpointUrl {
62-
source: stackable_operator::crd::authentication::ldap::v1alpha1::Error,
63-
},
64-
65-
#[snafu(display("failed to create the OIDC well-known url."))]
66-
ConstructOidcWellKnownUrl {
67-
source: stackable_operator::crd::authentication::oidc::v1alpha1::Error,
68-
},
69-
70-
#[snafu(display("failed to add LDAP Volumes and VolumeMounts to the Pod and containers"))]
71-
AddLdapVolumes {
72-
source: stackable_operator::crd::authentication::ldap::v1alpha1::Error,
73-
},
74-
75-
#[snafu(display("failed to add OIDC Volumes and VolumeMounts to the Pod and containers"))]
76-
AddOidcVolumes {
77-
source: stackable_operator::commons::tls_verification::TlsClientDetailsError,
78-
},
79-
80-
#[snafu(display(
81-
"failed to access bind credentials although they are required for LDAP to work"
82-
))]
83-
MissingLdapBindCredentials,
84-
}
12+
/// Type alias for Druid's OIDC client authentication options, opting in to the
13+
/// `clientAuthenticationMethod` field via [`oidc::v1alpha1::ClientAuthenticationMethodOption`].
14+
pub type DruidClientAuthenticationOptions =
15+
authentication::oidc::v1alpha1::ClientAuthenticationOptions<
16+
authentication::oidc::v1alpha1::ClientAuthenticationMethodOption,
17+
>;
8518

8619
#[derive(Clone, Debug)]
8720
pub enum DruidAuthenticationConfig {
@@ -91,192 +24,30 @@ pub enum DruidAuthenticationConfig {
9124
},
9225
Oidc {
9326
provider: authentication::oidc::v1alpha1::AuthenticationProvider,
94-
oidc: oidc::DruidClientAuthenticationOptions,
27+
oidc: DruidClientAuthenticationOptions,
9528
},
9629
}
9730

9831
impl DruidAuthenticationConfig {
99-
pub fn try_from(
100-
auth_classes_resolved: AuthenticationClassesResolved,
101-
) -> Result<Option<Self>, Error> {
102-
// Currently only one auth mechanism is supported in Druid. This is checked in
103-
// `rust/crd/src/authentication.rs` and just a fail-safe here. For Future changes,
104-
// this is not just a "from" without error handling.
32+
/// Maps the resolved `AuthenticationClass` references to the Druid authentication decision.
33+
///
34+
/// Returns `None` when no authentication class is configured. Currently only one auth mechanism
35+
/// is supported in Druid (checked when resolving the authentication classes), so only the first
36+
/// entry is considered. This is a total mapping today; if future multi-mechanism validation is
37+
/// added here, this should become fallible again.
38+
pub fn from_auth_classes(auth_classes_resolved: AuthenticationClassesResolved) -> Option<Self> {
10539
match auth_classes_resolved.auth_classes.first() {
106-
None => Ok(None),
107-
Some(auth_class_resolved) => match &auth_class_resolved {
108-
AuthenticationClassResolved::Tls { .. } => Ok(Some(Self::Tls {})),
109-
AuthenticationClassResolved::Ldap { provider, .. } => Ok(Some(Self::Ldap {
40+
None => None,
41+
Some(auth_class_resolved) => match auth_class_resolved {
42+
AuthenticationClassResolved::Tls { .. } => Some(Self::Tls {}),
43+
AuthenticationClassResolved::Ldap { provider, .. } => Some(Self::Ldap {
11044
provider: provider.clone(),
111-
})),
112-
AuthenticationClassResolved::Oidc { provider, oidc, .. } => Ok(Some(Self::Oidc {
45+
}),
46+
AuthenticationClassResolved::Oidc { provider, oidc, .. } => Some(Self::Oidc {
11347
provider: provider.clone(),
11448
oidc: oidc.clone(),
115-
})),
49+
}),
11650
},
11751
}
11852
}
119-
120-
/// Creates the authentication and authorization parts of the runtime.properties config file.
121-
/// Configuration related to TLS authentication is added in `rust/crd/src/security.rs`.
122-
pub fn generate_runtime_properties_config(
123-
&self,
124-
role: &DruidRole,
125-
) -> Result<BTreeMap<String, String>, Error> {
126-
let mut config: BTreeMap<String, String> = BTreeMap::new();
127-
128-
match self {
129-
DruidAuthenticationConfig::Ldap { provider, .. } => {
130-
self.generate_common_runtime_properties_config(&mut config);
131-
ldap::generate_runtime_properties_config(provider, &mut config)?
132-
}
133-
DruidAuthenticationConfig::Oidc { provider, oidc, .. } => {
134-
self.generate_common_runtime_properties_config(&mut config);
135-
oidc::generate_runtime_properties_config(provider, oidc, role, &mut config)?
136-
}
137-
DruidAuthenticationConfig::Tls { .. } => (),
138-
}
139-
Ok(config)
140-
}
141-
142-
/// Creates authentication config that is required by LDAP and OIDC and doesn't depend on user input.
143-
fn generate_common_runtime_properties_config(&self, config: &mut BTreeMap<String, String>) {
144-
self.add_druid_system_authenticator_config(config);
145-
self.add_escalator_config(config);
146-
147-
config.insert(
148-
"druid.auth.authorizer.DruidSystemAuthorizer.type".to_string(),
149-
r#"allowAll"#.to_string(),
150-
);
151-
}
152-
153-
pub fn main_container_commands(&self) -> Vec<String> {
154-
let mut command = vec![];
155-
if let DruidAuthenticationConfig::Oidc { provider, .. } = self {
156-
oidc::main_container_commands(provider, &mut command)
157-
}
158-
command
159-
}
160-
161-
pub fn prepare_container_commands(&self) -> Vec<String> {
162-
let mut command = vec![];
163-
if let DruidAuthenticationConfig::Ldap { provider } = self {
164-
ldap::prepare_container_commands(provider, &mut command)
165-
}
166-
command
167-
}
168-
169-
pub fn get_env_var_mounts(&self, cluster: &ValidatedCluster, role: &DruidRole) -> Vec<EnvVar> {
170-
let mut envs = vec![];
171-
let internal_secret_name = build_shared_internal_secret_name(cluster);
172-
envs.push(env_var_from_secret(
173-
&internal_secret_name,
174-
None,
175-
INTERNAL_INITIAL_CLIENT_PASSWORD_ENV,
176-
));
177-
178-
if let DruidAuthenticationConfig::Oidc { oidc, .. } = self {
179-
envs.extend(oidc::get_env_var_mounts(role, oidc, &internal_secret_name))
180-
}
181-
envs
182-
}
183-
184-
pub fn add_volumes_and_mounts(
185-
&self,
186-
pb: &mut PodBuilder,
187-
cb_druid: &mut ContainerBuilder,
188-
cb_prepare: &mut ContainerBuilder,
189-
) -> Result<(), Error> {
190-
match self {
191-
DruidAuthenticationConfig::Ldap { provider, .. } => {
192-
ldap::add_volumes_and_mounts(provider, pb, cb_druid, cb_prepare)
193-
}
194-
DruidAuthenticationConfig::Oidc { provider, .. } => {
195-
oidc::add_volumes_and_mounts(provider, pb, cb_druid, cb_prepare)
196-
}
197-
DruidAuthenticationConfig::Tls { .. } => Ok(()),
198-
}
199-
}
200-
201-
/// Creates the authenticatior config for the internal communication by Druid processes using basic auth.
202-
/// When using LDAP or OIDC the DruidSystemAuthenticator is always tried first and skipped if no basic auth credentials were supplied.
203-
/// We don't want to create an admin user for the internal authentication, so this line is left out of the config:
204-
/// # druid.auth.authenticator.DruidSystemAuthenticator.initialAdminPassword: XXX
205-
fn add_druid_system_authenticator_config(&self, config: &mut BTreeMap<String, String>) {
206-
config.insert(
207-
"druid.auth.authenticator.DruidSystemAuthenticator.type".to_string(),
208-
"basic".to_string(),
209-
);
210-
config.insert(
211-
"druid.auth.authenticator.DruidSystemAuthenticator.credentialsValidator.type"
212-
.to_string(),
213-
"metadata".to_string(),
214-
);
215-
216-
config.insert(
217-
"druid.auth.authenticator.DruidSystemAuthenticator.initialInternalClientPassword"
218-
.to_string(),
219-
env_var_reference(INTERNAL_INITIAL_CLIENT_PASSWORD_ENV),
220-
);
221-
config.insert(
222-
"druid.auth.authenticator.DruidSystemAuthenticator.authorizerName".to_string(),
223-
"DruidSystemAuthorizer".to_string(),
224-
);
225-
config.insert(
226-
"druid.auth.authenticator.DruidSystemAuthenticator.skipOnFailure".to_string(),
227-
"true".to_string(),
228-
);
229-
}
230-
231-
/// Creates the escalator config: <https://druid.apache.org/docs/latest/operations/auth/#escalator>.
232-
/// This configures Druid processes to use the basic auth authentication added in `add_druid_system_authenticator_config` for internal communication.
233-
fn add_escalator_config(&self, config: &mut BTreeMap<String, String>) {
234-
config.insert("druid.escalator.type".to_string(), "basic".to_string());
235-
config.insert(
236-
"druid.escalator.internalClientUsername".to_string(),
237-
"druid_system".to_string(),
238-
);
239-
config.insert(
240-
"druid.escalator.internalClientPassword".to_string(),
241-
env_var_reference(ESCALATOR_INTERNAL_CLIENT_PASSWORD_ENV),
242-
);
243-
config.insert(
244-
"druid.escalator.authorizerName".to_string(),
245-
"DruidSystemAuthorizer".to_string(),
246-
);
247-
}
248-
}
249-
250-
#[cfg(test)]
251-
mod test {
252-
use super::*;
253-
254-
#[test]
255-
fn test_ldap_config_is_added() {
256-
let auth_config = DruidAuthenticationConfig::try_from(AuthenticationClassesResolved {
257-
auth_classes: vec![AuthenticationClassResolved::Ldap {
258-
auth_class_name: "ldap".to_string(),
259-
provider: serde_yaml::from_str::<
260-
authentication::ldap::v1alpha1::AuthenticationProvider,
261-
>(
262-
"
263-
hostname: openldap
264-
searchBase: ou=users,dc=example,dc=org
265-
searchFilter: (uid=%s)
266-
",
267-
)
268-
.unwrap(),
269-
}],
270-
})
271-
.unwrap()
272-
.unwrap();
273-
274-
let role = DruidRole::Coordinator;
275-
276-
let got = auth_config
277-
.generate_runtime_properties_config(&role)
278-
.unwrap();
279-
280-
assert!(got.contains_key("druid.auth.authenticator.Ldap.type"));
281-
}
28253
}

rust/operator-binary/src/authentication/ldap.rs renamed to rust/operator-binary/src/controller/build/authentication/ldap.rs

File renamed without changes.

0 commit comments

Comments
 (0)