@@ -4,10 +4,13 @@ use serde::{Deserialize, Serialize};
44use snafu:: { ensure, ResultExt , Snafu } ;
55use 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
8389type 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
146142impl 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) ]
318355mod 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
0 commit comments