Skip to content

Commit 57585c2

Browse files
committed
test: add unit test for listener, pdb and oidc
1 parent f223e90 commit 57585c2

4 files changed

Lines changed: 273 additions & 25 deletions

File tree

rust/operator-binary/src/controller/build/authentication/mod.rs

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,9 @@ mod test {
234234
use super::*;
235235
use crate::crd::authentication::{AuthenticationClassResolved, AuthenticationClassesResolved};
236236

237-
#[test]
238-
fn test_ldap_config_is_added() {
239-
let auth_config =
240-
DruidAuthenticationConfig::from_auth_classes(AuthenticationClassesResolved {
241-
auth_classes: vec![AuthenticationClassResolved::Ldap {
237+
fn ldap_auth_config() -> DruidAuthenticationConfig {
238+
DruidAuthenticationConfig::from_auth_classes(AuthenticationClassesResolved {
239+
auth_classes: vec![AuthenticationClassResolved::Ldap {
242240
auth_class_name: "ldap".to_string(),
243241
provider: serde_yaml::from_str::<
244242
stackable_operator::crd::authentication::ldap::v1alpha1::AuthenticationProvider,
@@ -251,13 +249,50 @@ mod test {
251249
)
252250
.unwrap(),
253251
}],
254-
})
252+
})
253+
.unwrap()
254+
}
255+
256+
#[test]
257+
fn test_ldap_config_is_added() {
258+
let got = generate_runtime_properties_config(&ldap_auth_config(), &DruidRole::Coordinator)
255259
.unwrap();
256260

257-
let role = DruidRole::Coordinator;
261+
assert!(got.contains_key("druid.auth.authenticator.Ldap.type"));
262+
}
258263

259-
let got = generate_runtime_properties_config(&auth_config, &role).unwrap();
264+
/// The Druid system authenticator, escalator and system authorizer are always configured
265+
/// alongside the provider-specific (LDAP/OIDC) config.
266+
#[test]
267+
fn common_system_auth_config_is_added() {
268+
let got = generate_runtime_properties_config(&ldap_auth_config(), &DruidRole::Coordinator)
269+
.unwrap();
260270

261-
assert!(got.contains_key("druid.auth.authenticator.Ldap.type"));
271+
assert_eq!(
272+
got.get("druid.auth.authenticator.DruidSystemAuthenticator.type"),
273+
Some(&"basic".to_owned())
274+
);
275+
assert_eq!(
276+
got.get("druid.auth.authenticator.DruidSystemAuthenticator.authorizerName"),
277+
Some(&"DruidSystemAuthorizer".to_owned())
278+
);
279+
assert_eq!(got.get("druid.escalator.type"), Some(&"basic".to_owned()));
280+
assert_eq!(
281+
got.get("druid.auth.authorizer.DruidSystemAuthorizer.type"),
282+
Some(&"allowAll".to_owned())
283+
);
284+
}
285+
286+
/// Pure TLS authentication is rendered in `controller::build::security`, so the auth module
287+
/// contributes nothing to runtime.properties.
288+
#[test]
289+
fn tls_only_produces_no_runtime_properties() {
290+
let got = generate_runtime_properties_config(
291+
&DruidAuthenticationConfig::Tls {},
292+
&DruidRole::Broker,
293+
)
294+
.unwrap();
295+
296+
assert!(got.is_empty());
262297
}
263298
}

rust/operator-binary/src/controller/build/authentication/oidc.rs

Lines changed: 90 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -152,22 +152,14 @@ pub(super) fn add_volumes_and_mounts(
152152
#[cfg(test)]
153153
mod tests {
154154
use rstest::rstest;
155-
use stackable_operator::commons::tls_verification::{Tls, TlsClientDetails};
155+
use stackable_operator::commons::tls_verification::{
156+
CaCert, Tls, TlsClientDetails, TlsServerVerification, TlsVerification,
157+
};
156158

157159
use super::*;
158160

159-
#[rstest]
160-
#[case("/realms/sdp")]
161-
#[case("/realms/sdp/")]
162-
#[case("/realms/sdp/////")]
163-
fn test_add_authenticator_config(#[case] root_path: String) {
164-
use stackable_operator::{
165-
commons::tls_verification::{CaCert, TlsServerVerification, TlsVerification},
166-
crd::authentication::oidc,
167-
};
168-
169-
let mut properties = BTreeMap::new();
170-
let provider = oidc::v1alpha1::AuthenticationProvider::new(
161+
fn test_provider(root_path: String) -> oidc::v1alpha1::AuthenticationProvider {
162+
oidc::v1alpha1::AuthenticationProvider::new(
171163
"keycloak.mycorp.org".to_owned().try_into().unwrap(),
172164
Some(443),
173165
root_path,
@@ -181,15 +173,28 @@ mod tests {
181173
"preferred_username".to_owned(),
182174
vec!["openid".to_owned()],
183175
Some(oidc::v1alpha1::IdentityProviderHint::Keycloak),
184-
);
185-
let oidc = DruidClientAuthenticationOptions {
176+
)
177+
}
178+
179+
fn test_options() -> DruidClientAuthenticationOptions {
180+
DruidClientAuthenticationOptions {
186181
client_credentials_secret_ref: "nifi-keycloak-client".to_owned(),
187182
extra_scopes: vec![],
188183
product_specific_fields: oidc::v1alpha1::ClientAuthenticationMethodOption {
189184
client_authentication_method:
190185
oidc::v1alpha1::ClientAuthenticationMethod::ClientSecretPost,
191186
},
192-
};
187+
}
188+
}
189+
190+
#[rstest]
191+
#[case("/realms/sdp")]
192+
#[case("/realms/sdp/")]
193+
#[case("/realms/sdp/////")]
194+
fn test_add_authenticator_config(#[case] root_path: String) {
195+
let provider = test_provider(root_path);
196+
let oidc = test_options();
197+
let mut properties = BTreeMap::new();
193198

194199
add_authenticator_config(&provider, &oidc, &mut properties)
195200
.expect("OIDC config adding failed");
@@ -230,4 +235,73 @@ mod tests {
230235
assert!(properties.contains_key("druid.auth.pac4j.oidc.clientSecret"));
231236
assert!(properties.contains_key("druid.auth.pac4j.cookiePassphrase"));
232237
}
238+
239+
/// The MiddleManager must not get an Oidc authenticator/authorizer, only the Druid system
240+
/// authenticator in the chain (OIDC on MiddleManagers breaks coordinator<->mm ingest).
241+
#[test]
242+
fn middlemanager_is_not_configured_for_oidc() {
243+
let provider = test_provider("/realms/sdp".to_owned());
244+
let oidc = test_options();
245+
let mut config = BTreeMap::new();
246+
247+
generate_runtime_properties_config(
248+
&provider,
249+
&oidc,
250+
&DruidRole::MiddleManager,
251+
&mut config,
252+
)
253+
.expect("generate runtime properties");
254+
255+
assert_eq!(
256+
config.get("druid.auth.authenticatorChain"),
257+
Some(&r#"["DruidSystemAuthenticator"]"#.to_owned())
258+
);
259+
assert!(!config.contains_key("druid.auth.authenticator.Oidc.type"));
260+
assert!(!config.contains_key("druid.auth.authorizers"));
261+
}
262+
263+
/// Every non-MiddleManager role gets the full Oidc authenticator and authorizer.
264+
#[test]
265+
fn non_middlemanager_is_configured_for_oidc() {
266+
let provider = test_provider("/realms/sdp".to_owned());
267+
let oidc = test_options();
268+
let mut config = BTreeMap::new();
269+
270+
generate_runtime_properties_config(&provider, &oidc, &DruidRole::Broker, &mut config)
271+
.expect("generate runtime properties");
272+
273+
assert_eq!(
274+
config.get("druid.auth.authenticator.Oidc.type"),
275+
Some(&"pac4j".to_owned())
276+
);
277+
assert_eq!(
278+
config.get("druid.auth.authenticator.Oidc.authorizerName"),
279+
Some(&"OidcAuthorizer".to_owned())
280+
);
281+
assert!(config.contains_key("druid.auth.authorizers"));
282+
}
283+
284+
/// The MiddleManager mounts no OIDC env vars (OIDC is not configured on it).
285+
#[test]
286+
fn middlemanager_gets_no_oidc_env_vars() {
287+
let oidc = test_options();
288+
289+
let envs = get_env_var_mounts(&DruidRole::MiddleManager, &oidc, "internal-secret");
290+
291+
assert!(envs.is_empty());
292+
}
293+
294+
/// Non-MiddleManager roles mount the OIDC credentials and the cookie-passphrase env var.
295+
#[test]
296+
fn non_middlemanager_gets_oidc_env_vars() {
297+
let oidc = test_options();
298+
299+
let envs = get_env_var_mounts(&DruidRole::Broker, &oidc, "internal-secret");
300+
301+
assert!(
302+
envs.iter()
303+
.any(|e| e.name.as_str() == COOKIE_PASSPHRASE_ENV),
304+
"expected the cookie passphrase env var to be mounted"
305+
);
306+
}
233307
}

rust/operator-binary/src/controller/build/resource/listener.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,91 @@ pub fn secret_volume_listener_scope(role: &DruidRole) -> Option<String> {
132132
DruidRole::Historical | DruidRole::MiddleManager => None,
133133
}
134134
}
135+
136+
#[cfg(test)]
137+
mod tests {
138+
use std::{collections::BTreeMap, str::FromStr};
139+
140+
use stackable_operator::{kube::api::ObjectMeta, v2::types::kubernetes::SecretClassName};
141+
142+
use super::*;
143+
use crate::controller::validate::test_support::{
144+
MINIMAL_DRUID_YAML, druid_from_yaml, validated_cluster,
145+
};
146+
147+
fn cluster() -> ValidatedCluster {
148+
validated_cluster(&druid_from_yaml(MINIMAL_DRUID_YAML))
149+
}
150+
151+
#[test]
152+
fn group_listener_name_only_for_externally_reachable_roles() {
153+
let cluster = cluster();
154+
assert!(group_listener_name(&cluster, &DruidRole::Broker).is_some());
155+
assert!(group_listener_name(&cluster, &DruidRole::Coordinator).is_some());
156+
assert!(group_listener_name(&cluster, &DruidRole::Router).is_some());
157+
assert!(group_listener_name(&cluster, &DruidRole::Historical).is_none());
158+
assert!(group_listener_name(&cluster, &DruidRole::MiddleManager).is_none());
159+
}
160+
161+
#[test]
162+
fn group_listener_name_is_cluster_and_role_scoped() {
163+
let name =
164+
group_listener_name(&cluster(), &DruidRole::Broker).expect("broker has a listener");
165+
assert_eq!(name.to_string(), "simple-druid-broker");
166+
}
167+
168+
#[test]
169+
fn secret_volume_listener_scope_only_for_externally_reachable_roles() {
170+
assert_eq!(
171+
secret_volume_listener_scope(&DruidRole::Broker),
172+
Some("listener".to_string())
173+
);
174+
assert!(secret_volume_listener_scope(&DruidRole::Historical).is_none());
175+
assert!(secret_volume_listener_scope(&DruidRole::MiddleManager).is_none());
176+
}
177+
178+
/// A listener exposing both the plaintext and TLS ports, so the connection-string builder's
179+
/// port selection can be exercised.
180+
fn listener_with_both_ports() -> Listener {
181+
Listener {
182+
metadata: ObjectMeta::default(),
183+
spec: listener::v1alpha1::ListenerSpec::default(),
184+
status: Some(listener::v1alpha1::ListenerStatus {
185+
service_name: None,
186+
ingress_addresses: Some(vec![listener::v1alpha1::ListenerIngress {
187+
address: "druid.example.com".to_string(),
188+
address_type: listener::v1alpha1::AddressType::Hostname,
189+
ports: BTreeMap::from([
190+
(PLAINTEXT_PORT_NAME.to_string(), 8888),
191+
(TLS_PORT_NAME.to_string(), 9088),
192+
]),
193+
}]),
194+
node_ports: None,
195+
}),
196+
}
197+
}
198+
199+
#[test]
200+
fn connection_string_uses_plaintext_port_without_tls() {
201+
let tls = DruidTlsSecurity::new(false, None);
202+
let conn = build_listener_connection_string(
203+
listener_with_both_ports(),
204+
&tls,
205+
&"router".to_string(),
206+
)
207+
.expect("a connection string");
208+
assert_eq!(conn, "druid.example.com:8888");
209+
}
210+
211+
#[test]
212+
fn connection_string_uses_tls_port_with_tls() {
213+
let tls = DruidTlsSecurity::new(false, Some(SecretClassName::from_str("tls").unwrap()));
214+
let conn = build_listener_connection_string(
215+
listener_with_both_ports(),
216+
&tls,
217+
&"router".to_string(),
218+
)
219+
.expect("a connection string");
220+
assert_eq!(conn, "druid.example.com:9088");
221+
}
222+
}

rust/operator-binary/src/controller/build/resource/pdb.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,54 @@ fn max_unavailable_middle_managers() -> u16 {
5656
fn max_unavailable_routers() -> u16 {
5757
1
5858
}
59+
60+
#[cfg(test)]
61+
mod tests {
62+
use stackable_operator::{
63+
commons::pdb::PdbConfig, k8s_openapi::apimachinery::pkg::util::intstr::IntOrString,
64+
};
65+
66+
use super::*;
67+
use crate::controller::validate::test_support::{
68+
MINIMAL_DRUID_YAML, druid_from_yaml, validated_cluster,
69+
};
70+
71+
fn cluster() -> ValidatedCluster {
72+
validated_cluster(&druid_from_yaml(MINIMAL_DRUID_YAML))
73+
}
74+
75+
#[test]
76+
fn disabled_pdb_returns_none() {
77+
let pdb = PdbConfig {
78+
enabled: false,
79+
max_unavailable: None,
80+
};
81+
assert!(build_pdb(&pdb, &cluster(), &DruidRole::Broker).is_none());
82+
}
83+
84+
#[test]
85+
fn enabled_pdb_uses_role_default() {
86+
let pdb = PdbConfig {
87+
enabled: true,
88+
max_unavailable: None,
89+
};
90+
let built = build_pdb(&pdb, &cluster(), &DruidRole::Broker).expect("a PDB is built");
91+
assert_eq!(
92+
built.spec.and_then(|spec| spec.max_unavailable),
93+
Some(IntOrString::Int(1))
94+
);
95+
}
96+
97+
#[test]
98+
fn enabled_pdb_respects_explicit_max_unavailable() {
99+
let pdb = PdbConfig {
100+
enabled: true,
101+
max_unavailable: Some(3),
102+
};
103+
let built = build_pdb(&pdb, &cluster(), &DruidRole::Broker).expect("a PDB is built");
104+
assert_eq!(
105+
built.spec.and_then(|spec| spec.max_unavailable),
106+
Some(IntOrString::Int(3))
107+
);
108+
}
109+
}

0 commit comments

Comments
 (0)