From ecc46bb015b45426a9a0d2ebfe934f69e7c43358 Mon Sep 17 00:00:00 2001 From: William Brown Date: Sat, 28 Oct 2023 09:21:47 +1000 Subject: [PATCH] Add book chapter + cli --- book/src/integrations/ldap.md | 13 +++++++++++++ libs/client/src/lib.rs | 8 ++++++++ proto/src/constants.rs | 2 +- server/lib/src/constants/acp.rs | 4 +++- server/lib/src/constants/entries.rs | 6 +++--- server/lib/src/constants/schema.rs | 15 +++++++++------ server/lib/src/constants/uuids.rs | 4 +--- server/lib/src/idm/ldap.rs | 4 ++-- server/lib/src/server/migrations.rs | 2 +- server/lib/src/server/mod.rs | 2 +- tools/cli/src/cli/domain.rs | 10 +++++++++- tools/cli/src/opt/kanidm.rs | 8 ++++++++ 12 files changed, 59 insertions(+), 19 deletions(-) diff --git a/book/src/integrations/ldap.md b/book/src/integrations/ldap.md index c6b3f68d6..32e131740 100644 --- a/book/src/integrations/ldap.md +++ b/book/src/integrations/ldap.md @@ -76,6 +76,9 @@ permissions. All binds have the permissions of "anonymous" even if the anonymous The exception is service accounts which can use api-tokens during an LDAP bind for elevated read permissions. +The ability to bind with the POSIX password can be disabled to prevent password bruteforce attempts. +This does not prevent api-token binds. + ### Filtering Objects It is recommended that client applications filter accounts that can authenticate with @@ -183,6 +186,16 @@ these components and must only contain alphanumeric characters. After the basedn is changed, the new value will take effect after a server restart. If you have a replicated topology, you must restart all servers. +## Disable POSIX Password Binds + +If you do not have applications that require LDAP password binds, then you should disable this +function to limit access. + +``` +kanidm system domain set-ldap-allow-unix-password-bind [true|false] +kanidm system domain set-ldap-allow-unix-password-bind -D admin false +``` + ## Examples Given a default install with domain "idm.example.com" the configured LDAP DN will be diff --git a/libs/client/src/lib.rs b/libs/client/src/lib.rs index 25f742707..08c138efb 100644 --- a/libs/client/src/lib.rs +++ b/libs/client/src/lib.rs @@ -1832,6 +1832,14 @@ impl KanidmClient { .await } + pub async fn idm_set_ldap_allow_unix_password_bind( + &self, + enable: bool, + ) -> Result<(), ClientError> { + self.perform_put_request("/v1/domain/_attr/ldap_allow_unix_pw_bind", vec![enable]) + .await + } + pub async fn idm_domain_get_ssid(&self) -> Result { self.perform_get_request("/v1/domain/_attr/domain_ssid") .await diff --git a/proto/src/constants.rs b/proto/src/constants.rs index 49f275974..21126b145 100644 --- a/proto/src/constants.rs +++ b/proto/src/constants.rs @@ -66,7 +66,6 @@ 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"; @@ -94,6 +93,7 @@ pub const ATTR_IPANTHASH: &str = "ipanthash"; pub const ATTR_IPASSHPUBKEY: &str = "ipasshpubkey"; pub const ATTR_JWS_ES256_PRIVATE_KEY: &str = "jws_es256_private_key"; pub const ATTR_LAST_MODIFIED_CID: &str = "last_modified_cid"; +pub const ATTR_LDAP_ALLOW_UNIX_PW_BIND: &str = "ldap_allow_unix_pw_bind"; pub const ATTR_LEGALNAME: &str = "legalname"; pub const ATTR_LOGINSHELL: &str = "loginshell"; pub const ATTR_MAIL: &str = "mail"; diff --git a/server/lib/src/constants/acp.rs b/server/lib/src/constants/acp.rs index 6a9f93727..68d2b3d82 100644 --- a/server/lib/src/constants/acp.rs +++ b/server/lib/src/constants/acp.rs @@ -1395,6 +1395,7 @@ lazy_static! { Attribute::Es256PrivateKeyDer, Attribute::FernetPrivateKeyStr, Attribute::CookiePrivateKey, + Attribute::LdapAllowUnixPwBind, ], modify_removed_attrs: vec![ Attribute::DomainDisplayName, @@ -1403,12 +1404,13 @@ lazy_static! { Attribute::Es256PrivateKeyDer, Attribute::CookiePrivateKey, Attribute::FernetPrivateKeyStr, + Attribute::LdapAllowUnixPwBind, ], modify_present_attrs: vec![ Attribute::DomainDisplayName, Attribute::DomainLdapBasedn, Attribute::DomainSsid, - + Attribute::LdapAllowUnixPwBind, ], ..Default::default() }; diff --git a/server/lib/src/constants/entries.rs b/server/lib/src/constants/entries.rs index c6dbc4772..ad9fdcabf 100644 --- a/server/lib/src/constants/entries.rs +++ b/server/lib/src/constants/entries.rs @@ -71,7 +71,6 @@ pub enum Attribute { DomainDisplayName, DomainLdapBasedn, DomainName, - DomainLdapAllowUnixPwBind, DomainSsid, DomainTokenKey, DomainUuid, @@ -96,6 +95,7 @@ pub enum Attribute { IpaSshPubKey, JwsEs256PrivateKey, LastModifiedCid, + LdapAllowUnixPwBind, /// An LDAP Compatible emailAddress LdapEmailAddress, /// An LDAP Compatible sshkeys virtual attribute @@ -248,7 +248,6 @@ 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, @@ -273,6 +272,7 @@ impl TryFrom for Attribute { ATTR_IPASSHPUBKEY => Attribute::IpaSshPubKey, ATTR_JWS_ES256_PRIVATE_KEY => Attribute::JwsEs256PrivateKey, ATTR_LAST_MODIFIED_CID => Attribute::LastModifiedCid, + ATTR_LDAP_ALLOW_UNIX_PW_BIND => Attribute::LdapAllowUnixPwBind, ATTR_LDAP_EMAIL_ADDRESS => Attribute::LdapEmailAddress, ATTR_LDAP_KEYS => Attribute::LdapKeys, ATTR_SSH_PUBLICKEY => Attribute::SshPublicKey, @@ -399,7 +399,6 @@ 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, @@ -426,6 +425,7 @@ impl From for &'static str { Attribute::IpaSshPubKey => ATTR_IPASSHPUBKEY, Attribute::JwsEs256PrivateKey => ATTR_JWS_ES256_PRIVATE_KEY, Attribute::LastModifiedCid => ATTR_LAST_MODIFIED_CID, + Attribute::LdapAllowUnixPwBind => ATTR_LDAP_ALLOW_UNIX_PW_BIND, Attribute::LdapEmailAddress => ATTR_LDAP_EMAIL_ADDRESS, Attribute::LdapKeys => ATTR_LDAP_KEYS, Attribute::LdapSshPublicKey => ATTR_LDAP_SSHPUBLICKEY, diff --git a/server/lib/src/constants/schema.rs b/server/lib/src/constants/schema.rs index 18d4a8f45..f019645c6 100644 --- a/server/lib/src/constants/schema.rs +++ b/server/lib/src/constants/schema.rs @@ -111,10 +111,10 @@ 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(), +pub static ref SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND: SchemaAttribute = SchemaAttribute { + uuid: UUID_SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND, + name: Attribute::LdapAllowUnixPwBind.into(), + description: "Configuration to enable binds to LDAP objects using their UNIX password.".to_string(), unique: false, syntax: SyntaxType::Boolean, ..Default::default() @@ -716,8 +716,11 @@ pub static ref SCHEMA_CLASS_DOMAIN_INFO: SchemaClass = SchemaClass { uuid: UUID_SCHEMA_CLASS_DOMAIN_INFO, name: EntryClass::DomainInfo.into(), description: "Local domain information and partial configuration.to_string().".to_string(), - - systemmay: vec![Attribute::DomainSsid.into(), Attribute::DomainLdapBasedn.into(),Attribute::DomainLdapAllowUnixPwBind.into()], + systemmay: vec![ + Attribute::DomainSsid.into(), + Attribute::DomainLdapBasedn.into(), + Attribute::LdapAllowUnixPwBind.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 ac55449dc..cb53d3faa 100644 --- a/server/lib/src/constants/uuids.rs +++ b/server/lib/src/constants/uuids.rs @@ -241,9 +241,7 @@ pub const UUID_SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY: Uuid = uuid!("00000000-0000-0000-0000-ffff00000142"); pub const UUID_SCHEMA_ATTR_IMAGE: Uuid = uuid!("00000000-0000-0000-0000-ffff00000143"); 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 = +pub const UUID_SCHEMA_ATTR_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"); diff --git a/server/lib/src/idm/ldap.rs b/server/lib/src/idm/ldap.rs index c4d9c2089..e4c718a07 100644 --- a/server/lib/src/idm/ldap.rs +++ b/server/lib/src/idm/ldap.rs @@ -665,7 +665,7 @@ mod tests { 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)), + ModifyList::new_purge_and_set(Attribute::LdapAllowUnixPwBind, Value::Bool(false)), ); assert!(idms_prox_write .qs_write @@ -684,7 +684,7 @@ mod tests { 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)), + ModifyList::new_purge_and_set(Attribute::LdapAllowUnixPwBind, Value::Bool(true)), ); assert!(idms_prox_write.qs_write.modify(&allow_unix_pw_flag).is_ok()); assert!(idms_prox_write.commit().is_ok()); diff --git a/server/lib/src/server/migrations.rs b/server/lib/src/server/migrations.rs index 2bea98305..fa2d30abe 100644 --- a/server/lib/src/server/migrations.rs +++ b/server/lib/src/server/migrations.rs @@ -565,7 +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_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 c5257da0c..565021575 100644 --- a/server/lib/src/server/mod.rs +++ b/server/lib/src/server/mod.rs @@ -817,7 +817,7 @@ pub trait QueryServerTransaction<'a> { 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) + .get_ava_single_bool(Attribute::LdapAllowUnixPwBind) .unwrap_or(true) }) } diff --git a/tools/cli/src/cli/domain.rs b/tools/cli/src/cli/domain.rs index bf7f99971..691eee55f 100644 --- a/tools/cli/src/cli/domain.rs +++ b/tools/cli/src/cli/domain.rs @@ -5,7 +5,8 @@ impl DomainOpt { pub fn debug(&self) -> bool { match self { DomainOpt::SetDisplayName(copt) => copt.copt.debug, - DomainOpt::SetLdapBasedn { copt, .. } => copt.debug, + DomainOpt::SetLdapBasedn { copt, .. } + | DomainOpt::SetLdapAllowUnixPasswordBind { copt, .. } => copt.debug, DomainOpt::Show(copt) | DomainOpt::ResetTokenKey(copt) => copt.debug, } } @@ -37,6 +38,13 @@ impl DomainOpt { Err(e) => handle_client_error(e, &copt.output_mode), } } + DomainOpt::SetLdapAllowUnixPasswordBind { copt, enable } => { + let client = copt.to_client(OpType::Write).await; + match client.idm_set_ldap_allow_unix_password_bind(*enable).await { + Ok(_) => println!("Success"), + Err(e) => handle_client_error(e, &copt.output_mode), + } + } DomainOpt::Show(copt) => { let client = copt.to_client(OpType::Read).await; match client.idm_domain_get().await { diff --git a/tools/cli/src/opt/kanidm.rs b/tools/cli/src/opt/kanidm.rs index 5029eebf6..eed0059d2 100644 --- a/tools/cli/src/opt/kanidm.rs +++ b/tools/cli/src/opt/kanidm.rs @@ -870,6 +870,14 @@ pub enum DomainOpt { #[clap(name = "new-basedn")] new_basedn: String, }, + /// Enable or disable unix passwords being used to bind via LDAP. Unless you have a specific + /// requirement for this, you should disable this. + SetLdapAllowUnixPasswordBind { + #[clap(flatten)] + copt: CommonOpt, + #[clap(name = "allow", action = clap::ArgAction::Set)] + enable: bool, + }, #[clap(name = "show")] /// Show information about this system's domain Show(CommonOpt),