Skip to content

Commit 7249f91

Browse files
fix: TLS CA cert path used in REQUESTS_CA_BUNDLE
1 parent 6529f52 commit 7249f91

File tree

4 files changed

+153
-29
lines changed

4 files changed

+153
-29
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
Use the env var `KUBERNETES_CLUSTER_DOMAIN` or the operator Helm chart property `kubernetesClusterDomain` to set a non-default cluster domain ([#518]).
1010
- Support for `2.9.3` ([#494]).
1111
- Experimental Support for `2.10.2` ([#512]).
12-
- Add support for OpenID Connect ([#524])
12+
- Add support for OpenID Connect ([#524], [#529])
1313

1414
### Changed
1515

@@ -32,6 +32,7 @@
3232
[#518]: https://github.com/stackabletech/airflow-operator/pull/518
3333
[#520]: https://github.com/stackabletech/airflow-operator/pull/520
3434
[#524]: https://github.com/stackabletech/airflow-operator/pull/524
35+
[#529]: https://github.com/stackabletech/airflow-operator/pull/529
3536

3637
## [24.7.0] - 2024-07-24
3738

rust/crd/src/authentication.rs

Lines changed: 138 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ use serde::{Deserialize, Serialize};
44
use snafu::{ensure, ResultExt, Snafu};
55
use stackable_operator::{
66
client::Client,
7-
commons::authentication::{
8-
ldap,
9-
oidc::{self, IdentityProviderHint},
10-
AuthenticationClass, AuthenticationClassProvider, ClientAuthenticationDetails,
7+
commons::{
8+
authentication::{
9+
ldap,
10+
oidc::{self, IdentityProviderHint},
11+
AuthenticationClass, AuthenticationClassProvider, ClientAuthenticationDetails,
12+
},
13+
tls_verification::TlsClientDetails,
1114
},
1215
schemars::{self, JsonSchema},
1316
};
@@ -78,6 +81,9 @@ pub enum Error {
7881
supported: String,
7982
auth_class_name: String,
8083
},
84+
85+
#[snafu(display("Currently only one CA certificate is supported."))]
86+
MultipleCaCertsNotSupported,
8187
}
8288

8389
type Result<T, E = Error> = std::result::Result<T, E>;
@@ -130,17 +136,7 @@ pub struct AirflowClientAuthenticationDetailsResolved {
130136
pub user_registration: bool,
131137
pub user_registration_role: String,
132138
pub sync_roles_at: FlaskRolesSyncMoment,
133-
}
134-
135-
#[derive(Clone, Debug, Eq, PartialEq)]
136-
pub enum AirflowAuthenticationClassResolved {
137-
Ldap {
138-
provider: ldap::AuthenticationProvider,
139-
},
140-
Oidc {
141-
provider: oidc::AuthenticationProvider,
142-
oidc: oidc::ClientAuthenticationOptions<()>,
143-
},
139+
pub tls_ca_cert_mount_path: Option<String>,
144140
}
145141

146142
impl AirflowClientAuthenticationDetailsResolved {
@@ -254,12 +250,24 @@ impl AirflowClientAuthenticationDetailsResolved {
254250
None => sync_roles_at = Some(entry.sync_roles_at.to_owned()),
255251
}
256252
}
253+
254+
let mut tls_ca_cert_mount_paths = resolved_auth_classes
255+
.iter()
256+
.filter_map(AirflowAuthenticationClassResolved::tls_ca_cert_mount_path)
257+
.collect::<BTreeSet<_>>();
258+
let tls_ca_cert_mount_path = tls_ca_cert_mount_paths.pop_first();
259+
ensure!(
260+
tls_ca_cert_mount_paths.is_empty(),
261+
MultipleCaCertsNotSupportedSnafu
262+
);
263+
257264
Ok(AirflowClientAuthenticationDetailsResolved {
258265
authentication_classes_resolved: resolved_auth_classes,
259266
user_registration: user_registration.unwrap_or_else(default_user_registration),
260267
user_registration_role: user_registration_role
261268
.unwrap_or_else(default_user_registration_role),
262269
sync_roles_at: sync_roles_at.unwrap_or_else(FlaskRolesSyncMoment::default),
270+
tls_ca_cert_mount_path,
263271
})
264272
}
265273

@@ -314,6 +322,35 @@ impl AirflowClientAuthenticationDetailsResolved {
314322
}
315323
}
316324

325+
#[derive(Clone, Debug, Eq, PartialEq)]
326+
pub enum AirflowAuthenticationClassResolved {
327+
Ldap {
328+
provider: ldap::AuthenticationProvider,
329+
},
330+
Oidc {
331+
provider: oidc::AuthenticationProvider,
332+
oidc: oidc::ClientAuthenticationOptions<()>,
333+
},
334+
}
335+
336+
impl AirflowAuthenticationClassResolved {
337+
pub fn tls_ca_cert_mount_path(&self) -> Option<String> {
338+
self.tls_client_details().tls_ca_cert_mount_path()
339+
}
340+
341+
pub fn tls_client_details(&self) -> &TlsClientDetails {
342+
match self {
343+
AirflowAuthenticationClassResolved::Ldap {
344+
provider: ldap::AuthenticationProvider { tls, .. },
345+
} => tls,
346+
AirflowAuthenticationClassResolved::Oidc {
347+
provider: oidc::AuthenticationProvider { tls, .. },
348+
..
349+
} => tls,
350+
}
351+
}
352+
}
353+
317354
#[cfg(test)]
318355
mod tests {
319356
use std::pin::Pin;
@@ -336,7 +373,8 @@ mod tests {
336373
authentication_classes_resolved: Vec::default(),
337374
user_registration: default_user_registration(),
338375
user_registration_role: default_user_registration_role(),
339-
sync_roles_at: FlaskRolesSyncMoment::default()
376+
sync_roles_at: FlaskRolesSyncMoment::default(),
377+
tls_ca_cert_mount_path: None,
340378
},
341379
auth_details_resolved
342380
);
@@ -362,18 +400,32 @@ mod tests {
362400
provider:
363401
ldap:
364402
hostname: my.ldap.server
403+
tls:
404+
verification:
405+
server:
406+
caCert:
407+
secretClass: tls
365408
"},
366409
)
367410
.await;
368411

369412
assert_eq!(
370413
AirflowClientAuthenticationDetailsResolved {
371414
authentication_classes_resolved: vec![AirflowAuthenticationClassResolved::Ldap {
372-
provider: serde_yaml::from_str("hostname: my.ldap.server").unwrap()
415+
provider: serde_yaml::from_str(indoc! {"
416+
hostname: my.ldap.server
417+
tls:
418+
verification:
419+
server:
420+
caCert:
421+
secretClass: tls
422+
"})
423+
.unwrap()
373424
}],
374425
user_registration: false,
375426
user_registration_role: "Gamma".into(),
376-
sync_roles_at: FlaskRolesSyncMoment::Login
427+
sync_roles_at: FlaskRolesSyncMoment::Login,
428+
tls_ca_cert_mount_path: Some("/stackable/secrets/tls/ca.crt".into()),
377429
},
378430
auth_details_resolved
379431
);
@@ -437,6 +489,11 @@ mod tests {
437489
- openid
438490
- email
439491
- profile
492+
tls:
493+
verification:
494+
server:
495+
caCert:
496+
secretClass: tls
440497
"},
441498
)
442499
.await;
@@ -471,7 +528,13 @@ mod tests {
471528
HostName::try_from("second.oidc.server".to_string()).unwrap(),
472529
None,
473530
"/realms/test".into(),
474-
TlsClientDetails { tls: None },
531+
TlsClientDetails {
532+
tls: Some(Tls {
533+
verification: TlsVerification::Server(TlsServerVerification {
534+
ca_cert: CaCert::SecretClass("tls".into())
535+
})
536+
})
537+
},
475538
"preferred_username".into(),
476539
vec!["openid".into(), "email".into(), "profile".into()],
477540
None
@@ -485,7 +548,8 @@ mod tests {
485548
],
486549
user_registration: false,
487550
user_registration_role: "Gamma".into(),
488-
sync_roles_at: FlaskRolesSyncMoment::Login
551+
sync_roles_at: FlaskRolesSyncMoment::Login,
552+
tls_ca_cert_mount_path: Some("/stackable/secrets/tls/ca.crt".into()),
489553
},
490554
auth_details_resolved
491555
);
@@ -829,6 +893,60 @@ mod tests {
829893
);
830894
}
831895

896+
#[tokio::test]
897+
async fn reject_different_tls_ca_certs() {
898+
let error_message = test_resolve_and_expect_error(
899+
indoc! {"
900+
- authenticationClass: oidc1
901+
oidc:
902+
clientCredentialsSecret: airflow-oidc-client1
903+
- authenticationClass: oidc2
904+
oidc:
905+
clientCredentialsSecret: airflow-oidc-client2
906+
"},
907+
indoc! {"
908+
---
909+
apiVersion: authentication.stackable.tech/v1alpha1
910+
kind: AuthenticationClass
911+
metadata:
912+
name: oidc1
913+
spec:
914+
provider:
915+
oidc:
916+
hostname: first.oidc.server
917+
principalClaim: preferred_username
918+
scopes: []
919+
tls:
920+
verification:
921+
server:
922+
caCert:
923+
secretClass: tls1
924+
---
925+
apiVersion: authentication.stackable.tech/v1alpha1
926+
kind: AuthenticationClass
927+
metadata:
928+
name: oidc2
929+
spec:
930+
provider:
931+
oidc:
932+
hostname: second.oidc.server
933+
principalClaim: preferred_username
934+
scopes: []
935+
tls:
936+
verification:
937+
server:
938+
caCert:
939+
secretClass: tls2
940+
"},
941+
)
942+
.await;
943+
944+
assert_eq!(
945+
"Currently only one CA certificate is supported.",
946+
error_message
947+
);
948+
}
949+
832950
/// Call `AirflowClientAuthenticationDetailsResolved::resolve` with
833951
/// the given lists of `AirflowClientAuthenticationDetails` and
834952
/// `AuthenticationClass`es and return the

rust/operator-binary/src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ mod tests {
271271
user_registration: true,
272272
user_registration_role: "User".to_string(),
273273
sync_roles_at: FlaskRolesSyncMoment::Registration,
274+
tls_ca_cert_mount_path: None,
274275
};
275276

276277
let mut result = BTreeMap::new();
@@ -314,6 +315,7 @@ mod tests {
314315
user_registration: true,
315316
user_registration_role: "Admin".to_string(),
316317
sync_roles_at: FlaskRolesSyncMoment::Registration,
318+
tls_ca_cert_mount_path: Some("/stackable/secrets/openldap-tls/ca.crt".to_string()),
317319
};
318320

319321
let mut result = BTreeMap::new();
@@ -394,6 +396,7 @@ mod tests {
394396
user_registration: default_user_registration(),
395397
user_registration_role: "Admin".to_string(),
396398
sync_roles_at: default_sync_roles_at(),
399+
tls_ca_cert_mount_path: Some("keycloak-ca-cert".to_string()),
397400
};
398401

399402
let mut result = BTreeMap::new();

rust/operator-binary/src/env_vars.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -200,14 +200,16 @@ pub fn build_airflow_statefulset_envs(
200200
AirflowRole::Webserver => {
201201
let auth_vars = authentication_env_vars(auth_config);
202202
env.extend(auth_vars.into_iter().map(|var| (var.name.to_owned(), var)));
203-
env.insert(
204-
"REQUESTS_CA_BUNDLE".into(),
205-
EnvVar {
206-
name: "REQUESTS_CA_BUNDLE".to_string(),
207-
value: Some("/stackable/secrets/tls/ca.crt".to_string()),
208-
..Default::default()
209-
},
210-
);
203+
if let Some(tls_ca_cert_mount_path) = &auth_config.tls_ca_cert_mount_path {
204+
env.insert(
205+
"REQUESTS_CA_BUNDLE".into(),
206+
EnvVar {
207+
name: "REQUESTS_CA_BUNDLE".to_string(),
208+
value: Some(tls_ca_cert_mount_path.to_owned()),
209+
..Default::default()
210+
},
211+
);
212+
}
211213
}
212214
_ => {}
213215
}

0 commit comments

Comments
 (0)