From 12ea1c8702fdbd7cf30043aed72a6c14fa998565 Mon Sep 17 00:00:00 2001 From: NavinShrinivas Date: Fri, 27 Oct 2023 10:38:39 +0530 Subject: [PATCH] Restrict posix passwords on ldap bind with config Signed-off-by: NavinShrinivas --- proto/src/constants.rs | 1 + server/lib/src/constants/entries.rs | 3 +++ server/lib/src/constants/schema.rs | 11 +++++++- server/lib/src/constants/uuids.rs | 3 +++ server/lib/src/idm/ldap.rs | 42 +++++++++++++++++++++++++++-- server/lib/src/idm/server.rs | 4 +++ server/lib/src/server/migrations.rs | 1 + server/lib/src/server/mod.rs | 18 ++++++++++++- 8 files changed, 79 insertions(+), 4 deletions(-) diff --git a/proto/src/constants.rs b/proto/src/constants.rs index 912bee3b1..54f333755 100644 --- a/proto/src/constants.rs +++ b/proto/src/constants.rs @@ -66,6 +66,7 @@ pub const ATTR_DN: &str = "dn"; pub const ATTR_DOMAIN_DISPLAY_NAME: &str = "domain_display_name"; pub const ATTR_DOMAIN_LDAP_BASEDN: &str = "domain_ldap_basedn"; pub const ATTR_DOMAIN_NAME: &str = "domain_name"; +pub const ATTR_DOMAIN_LDAP_ALLOW_UNIX_PW_BIND : &str = "domain_ldap_allow_unix_pw_bind"; pub const ATTR_DOMAIN_SSID: &str = "domain_ssid"; pub const ATTR_DOMAIN_TOKEN_KEY: &str = "domain_token_key"; pub const ATTR_DOMAIN_UUID: &str = "domain_uuid"; diff --git a/server/lib/src/constants/entries.rs b/server/lib/src/constants/entries.rs index 742a3a2f4..c6dbc4772 100644 --- a/server/lib/src/constants/entries.rs +++ b/server/lib/src/constants/entries.rs @@ -71,6 +71,7 @@ pub enum Attribute { DomainDisplayName, DomainLdapBasedn, DomainName, + DomainLdapAllowUnixPwBind, DomainSsid, DomainTokenKey, DomainUuid, @@ -247,6 +248,7 @@ impl TryFrom for Attribute { ATTR_DOMAIN_DISPLAY_NAME => Attribute::DomainDisplayName, ATTR_DOMAIN_LDAP_BASEDN => Attribute::DomainLdapBasedn, ATTR_DOMAIN_NAME => Attribute::DomainName, + ATTR_DOMAIN_LDAP_ALLOW_UNIX_PW_BIND => Attribute::DomainLdapAllowUnixPwBind, ATTR_DOMAIN_SSID => Attribute::DomainSsid, ATTR_DOMAIN_TOKEN_KEY => Attribute::DomainTokenKey, ATTR_DOMAIN_UUID => Attribute::DomainUuid, @@ -397,6 +399,7 @@ impl From for &'static str { Attribute::Dn => ATTR_DN, Attribute::Domain => ATTR_DOMAIN, Attribute::DomainDisplayName => ATTR_DOMAIN_DISPLAY_NAME, + Attribute::DomainLdapAllowUnixPwBind => ATTR_DOMAIN_LDAP_ALLOW_UNIX_PW_BIND, Attribute::DomainLdapBasedn => ATTR_DOMAIN_LDAP_BASEDN, Attribute::DomainName => ATTR_DOMAIN_NAME, Attribute::DomainSsid => ATTR_DOMAIN_SSID, diff --git a/server/lib/src/constants/schema.rs b/server/lib/src/constants/schema.rs index c0a2b4104..18d4a8f45 100644 --- a/server/lib/src/constants/schema.rs +++ b/server/lib/src/constants/schema.rs @@ -111,6 +111,15 @@ pub static ref SCHEMA_ATTR_DOMAIN_NAME: SchemaAttribute = SchemaAttribute { ..Default::default() }; +pub static ref SCHEMA_ATTR_DOMAIN_LDAP_ALLOW_UNIX_PW_BIND: SchemaAttribute = SchemaAttribute { + uuid: UUID_SCHEMA_ATTR_DOMAIN_LDAP_ALLOW_UNIX_PW_BIND, + name: Attribute::DomainLdapAllowUnixPwBind.into(), + description: "Configuration to allow binds to LDAP objects using UNIX passwords.".to_string(), + unique: false, + syntax: SyntaxType::Boolean, + ..Default::default() +}; + pub static ref SCHEMA_ATTR_DOMAIN_LDAP_BASEDN: SchemaAttribute = SchemaAttribute { uuid: UUID_SCHEMA_ATTR_DOMAIN_LDAP_BASEDN, name: Attribute::DomainLdapBasedn.into(), @@ -708,7 +717,7 @@ pub static ref SCHEMA_CLASS_DOMAIN_INFO: SchemaClass = SchemaClass { name: EntryClass::DomainInfo.into(), description: "Local domain information and partial configuration.to_string().".to_string(), - systemmay: vec![Attribute::DomainSsid.into(), Attribute::DomainLdapBasedn.into()], + systemmay: vec![Attribute::DomainSsid.into(), Attribute::DomainLdapBasedn.into(),Attribute::DomainLdapAllowUnixPwBind.into()], systemmust: vec![ Attribute::Name.into(), Attribute::DomainUuid.into(), diff --git a/server/lib/src/constants/uuids.rs b/server/lib/src/constants/uuids.rs index 8e3271eaa..ac55449dc 100644 --- a/server/lib/src/constants/uuids.rs +++ b/server/lib/src/constants/uuids.rs @@ -243,6 +243,9 @@ pub const UUID_SCHEMA_ATTR_IMAGE: Uuid = uuid!("00000000-0000-0000-0000-ffff0000 pub const UUID_SCHEMA_ATTR_DENIED_NAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000144"); // Leave 145 for ldap unix pw bind +pub const UUID_SCHEMA_ATTR_DOMAIN_LDAP_ALLOW_UNIX_PW_BIND: Uuid = + uuid!("00000000-0000-0000-0000-ffff00000145"); + pub const UUID_SCHEMA_CLASS_ACCOUNT_POLICY: Uuid = uuid!("00000000-0000-0000-0000-ffff00000146"); // System and domain infos diff --git a/server/lib/src/idm/ldap.rs b/server/lib/src/idm/ldap.rs index 48a4d931b..f66a3488d 100644 --- a/server/lib/src/idm/ldap.rs +++ b/server/lib/src/idm/ldap.rs @@ -450,7 +450,7 @@ impl LdapServer { idm_auth.auth_ldap(&lae, ct).await.and_then(|r| { idm_auth.commit().map(|_| { if r.is_some() { - security_info!(%dn, "✅ LDAP Bind success"); + security_info!(%dn, "✅ LDAP Bind (password) success"); } else { security_info!(%dn, "❌ LDAP Bind failure"); }; @@ -643,13 +643,51 @@ mod tests { let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); assert!(idms_prox_write.set_unix_account_password(&pce).is_ok()); - assert!(idms_prox_write.commit().is_ok()); + assert!(idms_prox_write.commit().is_ok()); // Committing all configs + + // default UNIX_PW bind (default is set to true) + // Hence allows all unix binds + let admin_t = ldaps + .do_bind(idms, "admin", TEST_PASSWORD) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + let admin_t = ldaps + .do_bind(idms, "admin@example.com", TEST_PASSWORD) + .await + .unwrap() + .unwrap(); + assert!(admin_t.effective_session == LdapSession::UnixBind(UUID_ADMIN)); + // Setting UNIX_PW_BIND flag to false: + // Hence all of the below authentication will fail (asserts are still satisfied) + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + let disallow_unix_pw_flag = ModifyEvent::new_internal_invalid( + filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))), + ModifyList::new_purge_and_set(Attribute::DomainLdapAllowUnixPwBind, Value::Bool(false)), + ); + assert!(idms_prox_write + .qs_write + .modify(&disallow_unix_pw_flag) + .is_ok()); + assert!(idms_prox_write.commit().is_ok()); let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap(); assert!(anon_t.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS)); assert!( ldaps.do_bind(idms, "", "test").await.unwrap_err() == OperationError::NotAuthenticated ); + let admin_t = ldaps.do_bind(idms, "admin", TEST_PASSWORD).await.unwrap(); + assert!(admin_t.is_none() == true); + + // Setting UNIX_PW_BIND flag to true : + let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; + let allow_unix_pw_flag = ModifyEvent::new_internal_invalid( + filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))), + ModifyList::new_purge_and_set(Attribute::DomainLdapAllowUnixPwBind, Value::Bool(true)), + ); + assert!(idms_prox_write.qs_write.modify(&allow_unix_pw_flag).is_ok()); + assert!(idms_prox_write.commit().is_ok()); // Now test the admin and various DN's let admin_t = ldaps diff --git a/server/lib/src/idm/server.rs b/server/lib/src/idm/server.rs index e12cd548b..14efe35bb 100644 --- a/server/lib/src/idm/server.rs +++ b/server/lib/src/idm/server.rs @@ -1325,6 +1325,10 @@ impl<'a> IdmServerAuthTransaction<'a> { effective_session: LdapSession::UnixBind(UUID_ANONYMOUS), })) } else { + if !self.qs_read.d_info.d_ldap_allow_unix_pw_bind { + security_info!("Bind not allowed through Unix passwords."); + return Ok(None); + } let account = UnixUserAccount::try_from_entry_ro(account_entry.as_ref(), &mut self.qs_read)?; diff --git a/server/lib/src/server/migrations.rs b/server/lib/src/server/migrations.rs index 0da9641f5..2bea98305 100644 --- a/server/lib/src/server/migrations.rs +++ b/server/lib/src/server/migrations.rs @@ -565,6 +565,7 @@ impl<'a> QueryServerWriteTransaction<'a> { SCHEMA_ATTR_DOMAIN_DISPLAY_NAME.clone().into(), SCHEMA_ATTR_DOMAIN_LDAP_BASEDN.clone().into(), SCHEMA_ATTR_DOMAIN_NAME.clone().into(), + SCHEMA_ATTR_DOMAIN_LDAP_ALLOW_UNIX_PW_BIND.clone().into(), SCHEMA_ATTR_DOMAIN_SSID.clone().into(), SCHEMA_ATTR_DOMAIN_TOKEN_KEY.clone().into(), SCHEMA_ATTR_DOMAIN_UUID.clone().into(), diff --git a/server/lib/src/server/mod.rs b/server/lib/src/server/mod.rs index b77a4d5dc..c5257da0c 100644 --- a/server/lib/src/server/mod.rs +++ b/server/lib/src/server/mod.rs @@ -65,6 +65,7 @@ pub struct DomainInfo { pub(crate) d_name: String, pub(crate) d_display: String, pub(crate) d_vers: DomainVersion, + pub(crate) d_ldap_allow_unix_pw_bind: bool, } #[derive(Debug, Clone, PartialEq, Eq, Default)] @@ -813,7 +814,13 @@ pub trait QueryServerTransaction<'a> { e }) } - + fn get_domain_ldap_allow_unix_pw_bind(&mut self) -> Result { + self.internal_search_uuid(UUID_DOMAIN_INFO).map(|entry| { + entry + .get_ava_single_bool(Attribute::DomainLdapAllowUnixPwBind) + .unwrap_or(true) + }) + } fn get_domain_cookie_key(&mut self) -> Result<[u8; 64], OperationError> { self.internal_search_uuid(UUID_DOMAIN_INFO) .and_then(|e| { @@ -1130,6 +1137,7 @@ impl QueryServer { // we set the domain_display_name to the configuration file's domain_name // here because the database is not started, so we cannot pull it from there. d_display: domain_name, + d_ldap_allow_unix_pw_bind: false, })); // These default to empty, but they'll be populated shortly. @@ -1563,8 +1571,16 @@ impl<'a> QueryServerWriteTransaction<'a> { pub(crate) fn reload_domain_info(&mut self) -> Result<(), OperationError> { let domain_name = self.get_db_domain_name()?; let display_name = self.get_db_domain_display_name()?; + let domain_ldap_allow_unix_pw_bind = match self.get_domain_ldap_allow_unix_pw_bind() { + Ok(v) => v, + _ => { + admin_warn!("Defaulting ldap_allow_unix_pw_bind to true"); + true + } + }; let domain_uuid = self.be_txn.get_db_d_uuid()?; let mut_d_info = self.d_info.get_mut(); + mut_d_info.d_ldap_allow_unix_pw_bind = domain_ldap_allow_unix_pw_bind; if mut_d_info.d_uuid != domain_uuid { admin_warn!( "Using domain uuid from the database {} - was {} in memory",