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 ) ]
8720pub 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
9831impl 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}
0 commit comments