Restrict posix passwords on ldap bind with config

Signed-off-by: NavinShrinivas <karupal2002@gmail.com>
This commit is contained in:
NavinShrinivas 2023-10-27 10:38:39 +05:30 committed by Firstyear
parent e02328ae8b
commit 12ea1c8702
8 changed files with 79 additions and 4 deletions

View file

@ -66,6 +66,7 @@ pub const ATTR_DN: &str = "dn";
pub const ATTR_DOMAIN_DISPLAY_NAME: &str = "domain_display_name"; pub const ATTR_DOMAIN_DISPLAY_NAME: &str = "domain_display_name";
pub const ATTR_DOMAIN_LDAP_BASEDN: &str = "domain_ldap_basedn"; pub const ATTR_DOMAIN_LDAP_BASEDN: &str = "domain_ldap_basedn";
pub const ATTR_DOMAIN_NAME: &str = "domain_name"; 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_SSID: &str = "domain_ssid";
pub const ATTR_DOMAIN_TOKEN_KEY: &str = "domain_token_key"; pub const ATTR_DOMAIN_TOKEN_KEY: &str = "domain_token_key";
pub const ATTR_DOMAIN_UUID: &str = "domain_uuid"; pub const ATTR_DOMAIN_UUID: &str = "domain_uuid";

View file

@ -71,6 +71,7 @@ pub enum Attribute {
DomainDisplayName, DomainDisplayName,
DomainLdapBasedn, DomainLdapBasedn,
DomainName, DomainName,
DomainLdapAllowUnixPwBind,
DomainSsid, DomainSsid,
DomainTokenKey, DomainTokenKey,
DomainUuid, DomainUuid,
@ -247,6 +248,7 @@ impl TryFrom<String> for Attribute {
ATTR_DOMAIN_DISPLAY_NAME => Attribute::DomainDisplayName, ATTR_DOMAIN_DISPLAY_NAME => Attribute::DomainDisplayName,
ATTR_DOMAIN_LDAP_BASEDN => Attribute::DomainLdapBasedn, ATTR_DOMAIN_LDAP_BASEDN => Attribute::DomainLdapBasedn,
ATTR_DOMAIN_NAME => Attribute::DomainName, ATTR_DOMAIN_NAME => Attribute::DomainName,
ATTR_DOMAIN_LDAP_ALLOW_UNIX_PW_BIND => Attribute::DomainLdapAllowUnixPwBind,
ATTR_DOMAIN_SSID => Attribute::DomainSsid, ATTR_DOMAIN_SSID => Attribute::DomainSsid,
ATTR_DOMAIN_TOKEN_KEY => Attribute::DomainTokenKey, ATTR_DOMAIN_TOKEN_KEY => Attribute::DomainTokenKey,
ATTR_DOMAIN_UUID => Attribute::DomainUuid, ATTR_DOMAIN_UUID => Attribute::DomainUuid,
@ -397,6 +399,7 @@ impl From<Attribute> for &'static str {
Attribute::Dn => ATTR_DN, Attribute::Dn => ATTR_DN,
Attribute::Domain => ATTR_DOMAIN, Attribute::Domain => ATTR_DOMAIN,
Attribute::DomainDisplayName => ATTR_DOMAIN_DISPLAY_NAME, Attribute::DomainDisplayName => ATTR_DOMAIN_DISPLAY_NAME,
Attribute::DomainLdapAllowUnixPwBind => ATTR_DOMAIN_LDAP_ALLOW_UNIX_PW_BIND,
Attribute::DomainLdapBasedn => ATTR_DOMAIN_LDAP_BASEDN, Attribute::DomainLdapBasedn => ATTR_DOMAIN_LDAP_BASEDN,
Attribute::DomainName => ATTR_DOMAIN_NAME, Attribute::DomainName => ATTR_DOMAIN_NAME,
Attribute::DomainSsid => ATTR_DOMAIN_SSID, Attribute::DomainSsid => ATTR_DOMAIN_SSID,

View file

@ -111,6 +111,15 @@ pub static ref SCHEMA_ATTR_DOMAIN_NAME: SchemaAttribute = SchemaAttribute {
..Default::default() ..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 { pub static ref SCHEMA_ATTR_DOMAIN_LDAP_BASEDN: SchemaAttribute = SchemaAttribute {
uuid: UUID_SCHEMA_ATTR_DOMAIN_LDAP_BASEDN, uuid: UUID_SCHEMA_ATTR_DOMAIN_LDAP_BASEDN,
name: Attribute::DomainLdapBasedn.into(), name: Attribute::DomainLdapBasedn.into(),
@ -708,7 +717,7 @@ pub static ref SCHEMA_CLASS_DOMAIN_INFO: SchemaClass = SchemaClass {
name: EntryClass::DomainInfo.into(), name: EntryClass::DomainInfo.into(),
description: "Local domain information and partial configuration.to_string().".to_string(), 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![ systemmust: vec![
Attribute::Name.into(), Attribute::Name.into(),
Attribute::DomainUuid.into(), Attribute::DomainUuid.into(),

View file

@ -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"); pub const UUID_SCHEMA_ATTR_DENIED_NAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000144");
// Leave 145 for ldap unix pw bind // 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"); pub const UUID_SCHEMA_CLASS_ACCOUNT_POLICY: Uuid = uuid!("00000000-0000-0000-0000-ffff00000146");
// System and domain infos // System and domain infos

View file

@ -450,7 +450,7 @@ impl LdapServer {
idm_auth.auth_ldap(&lae, ct).await.and_then(|r| { idm_auth.auth_ldap(&lae, ct).await.and_then(|r| {
idm_auth.commit().map(|_| { idm_auth.commit().map(|_| {
if r.is_some() { if r.is_some() {
security_info!(%dn, "✅ LDAP Bind success"); security_info!(%dn, "✅ LDAP Bind (password) success");
} else { } else {
security_info!(%dn, "❌ LDAP Bind failure"); security_info!(%dn, "❌ LDAP Bind failure");
}; };
@ -643,13 +643,51 @@ mod tests {
let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD); let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD);
assert!(idms_prox_write.set_unix_account_password(&pce).is_ok()); 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(); let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
assert!(anon_t.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS)); assert!(anon_t.effective_session == LdapSession::UnixBind(UUID_ANONYMOUS));
assert!( assert!(
ldaps.do_bind(idms, "", "test").await.unwrap_err() == OperationError::NotAuthenticated 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 // Now test the admin and various DN's
let admin_t = ldaps let admin_t = ldaps

View file

@ -1325,6 +1325,10 @@ impl<'a> IdmServerAuthTransaction<'a> {
effective_session: LdapSession::UnixBind(UUID_ANONYMOUS), effective_session: LdapSession::UnixBind(UUID_ANONYMOUS),
})) }))
} else { } 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 = let account =
UnixUserAccount::try_from_entry_ro(account_entry.as_ref(), &mut self.qs_read)?; UnixUserAccount::try_from_entry_ro(account_entry.as_ref(), &mut self.qs_read)?;

View file

@ -565,6 +565,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
SCHEMA_ATTR_DOMAIN_DISPLAY_NAME.clone().into(), SCHEMA_ATTR_DOMAIN_DISPLAY_NAME.clone().into(),
SCHEMA_ATTR_DOMAIN_LDAP_BASEDN.clone().into(), SCHEMA_ATTR_DOMAIN_LDAP_BASEDN.clone().into(),
SCHEMA_ATTR_DOMAIN_NAME.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_SSID.clone().into(),
SCHEMA_ATTR_DOMAIN_TOKEN_KEY.clone().into(), SCHEMA_ATTR_DOMAIN_TOKEN_KEY.clone().into(),
SCHEMA_ATTR_DOMAIN_UUID.clone().into(), SCHEMA_ATTR_DOMAIN_UUID.clone().into(),

View file

@ -65,6 +65,7 @@ pub struct DomainInfo {
pub(crate) d_name: String, pub(crate) d_name: String,
pub(crate) d_display: String, pub(crate) d_display: String,
pub(crate) d_vers: DomainVersion, pub(crate) d_vers: DomainVersion,
pub(crate) d_ldap_allow_unix_pw_bind: bool,
} }
#[derive(Debug, Clone, PartialEq, Eq, Default)] #[derive(Debug, Clone, PartialEq, Eq, Default)]
@ -813,7 +814,13 @@ pub trait QueryServerTransaction<'a> {
e e
}) })
} }
fn get_domain_ldap_allow_unix_pw_bind(&mut self) -> Result<bool, OperationError> {
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> { fn get_domain_cookie_key(&mut self) -> Result<[u8; 64], OperationError> {
self.internal_search_uuid(UUID_DOMAIN_INFO) self.internal_search_uuid(UUID_DOMAIN_INFO)
.and_then(|e| { .and_then(|e| {
@ -1130,6 +1137,7 @@ impl QueryServer {
// we set the domain_display_name to the configuration file's domain_name // 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. // here because the database is not started, so we cannot pull it from there.
d_display: domain_name, d_display: domain_name,
d_ldap_allow_unix_pw_bind: false,
})); }));
// These default to empty, but they'll be populated shortly. // 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> { pub(crate) fn reload_domain_info(&mut self) -> Result<(), OperationError> {
let domain_name = self.get_db_domain_name()?; let domain_name = self.get_db_domain_name()?;
let display_name = self.get_db_domain_display_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 domain_uuid = self.be_txn.get_db_d_uuid()?;
let mut_d_info = self.d_info.get_mut(); 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 { if mut_d_info.d_uuid != domain_uuid {
admin_warn!( admin_warn!(
"Using domain uuid from the database {} - was {} in memory", "Using domain uuid from the database {} - was {} in memory",