Add book chapter + cli

This commit is contained in:
William Brown 2023-10-28 09:21:47 +10:00 committed by Firstyear
parent b80a3b271c
commit ecc46bb015
12 changed files with 59 additions and 19 deletions

View file

@ -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 The exception is service accounts which can use api-tokens during an LDAP bind for elevated read
permissions. 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 ### Filtering Objects
It is recommended that client applications filter accounts that can authenticate with 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 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. 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 ## Examples
Given a default install with domain "idm.example.com" the configured LDAP DN will be Given a default install with domain "idm.example.com" the configured LDAP DN will be

View file

@ -1832,6 +1832,14 @@ impl KanidmClient {
.await .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<String, ClientError> { pub async fn idm_domain_get_ssid(&self) -> Result<String, ClientError> {
self.perform_get_request("/v1/domain/_attr/domain_ssid") self.perform_get_request("/v1/domain/_attr/domain_ssid")
.await .await

View file

@ -66,7 +66,6 @@ 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";
@ -94,6 +93,7 @@ pub const ATTR_IPANTHASH: &str = "ipanthash";
pub const ATTR_IPASSHPUBKEY: &str = "ipasshpubkey"; pub const ATTR_IPASSHPUBKEY: &str = "ipasshpubkey";
pub const ATTR_JWS_ES256_PRIVATE_KEY: &str = "jws_es256_private_key"; 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_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_LEGALNAME: &str = "legalname";
pub const ATTR_LOGINSHELL: &str = "loginshell"; pub const ATTR_LOGINSHELL: &str = "loginshell";
pub const ATTR_MAIL: &str = "mail"; pub const ATTR_MAIL: &str = "mail";

View file

@ -1395,6 +1395,7 @@ lazy_static! {
Attribute::Es256PrivateKeyDer, Attribute::Es256PrivateKeyDer,
Attribute::FernetPrivateKeyStr, Attribute::FernetPrivateKeyStr,
Attribute::CookiePrivateKey, Attribute::CookiePrivateKey,
Attribute::LdapAllowUnixPwBind,
], ],
modify_removed_attrs: vec![ modify_removed_attrs: vec![
Attribute::DomainDisplayName, Attribute::DomainDisplayName,
@ -1403,12 +1404,13 @@ lazy_static! {
Attribute::Es256PrivateKeyDer, Attribute::Es256PrivateKeyDer,
Attribute::CookiePrivateKey, Attribute::CookiePrivateKey,
Attribute::FernetPrivateKeyStr, Attribute::FernetPrivateKeyStr,
Attribute::LdapAllowUnixPwBind,
], ],
modify_present_attrs: vec![ modify_present_attrs: vec![
Attribute::DomainDisplayName, Attribute::DomainDisplayName,
Attribute::DomainLdapBasedn, Attribute::DomainLdapBasedn,
Attribute::DomainSsid, Attribute::DomainSsid,
Attribute::LdapAllowUnixPwBind,
], ],
..Default::default() ..Default::default()
}; };

View file

@ -71,7 +71,6 @@ pub enum Attribute {
DomainDisplayName, DomainDisplayName,
DomainLdapBasedn, DomainLdapBasedn,
DomainName, DomainName,
DomainLdapAllowUnixPwBind,
DomainSsid, DomainSsid,
DomainTokenKey, DomainTokenKey,
DomainUuid, DomainUuid,
@ -96,6 +95,7 @@ pub enum Attribute {
IpaSshPubKey, IpaSshPubKey,
JwsEs256PrivateKey, JwsEs256PrivateKey,
LastModifiedCid, LastModifiedCid,
LdapAllowUnixPwBind,
/// An LDAP Compatible emailAddress /// An LDAP Compatible emailAddress
LdapEmailAddress, LdapEmailAddress,
/// An LDAP Compatible sshkeys virtual attribute /// An LDAP Compatible sshkeys virtual attribute
@ -248,7 +248,6 @@ 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,
@ -273,6 +272,7 @@ impl TryFrom<String> for Attribute {
ATTR_IPASSHPUBKEY => Attribute::IpaSshPubKey, ATTR_IPASSHPUBKEY => Attribute::IpaSshPubKey,
ATTR_JWS_ES256_PRIVATE_KEY => Attribute::JwsEs256PrivateKey, ATTR_JWS_ES256_PRIVATE_KEY => Attribute::JwsEs256PrivateKey,
ATTR_LAST_MODIFIED_CID => Attribute::LastModifiedCid, ATTR_LAST_MODIFIED_CID => Attribute::LastModifiedCid,
ATTR_LDAP_ALLOW_UNIX_PW_BIND => Attribute::LdapAllowUnixPwBind,
ATTR_LDAP_EMAIL_ADDRESS => Attribute::LdapEmailAddress, ATTR_LDAP_EMAIL_ADDRESS => Attribute::LdapEmailAddress,
ATTR_LDAP_KEYS => Attribute::LdapKeys, ATTR_LDAP_KEYS => Attribute::LdapKeys,
ATTR_SSH_PUBLICKEY => Attribute::SshPublicKey, ATTR_SSH_PUBLICKEY => Attribute::SshPublicKey,
@ -399,7 +399,6 @@ 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,
@ -426,6 +425,7 @@ impl From<Attribute> for &'static str {
Attribute::IpaSshPubKey => ATTR_IPASSHPUBKEY, Attribute::IpaSshPubKey => ATTR_IPASSHPUBKEY,
Attribute::JwsEs256PrivateKey => ATTR_JWS_ES256_PRIVATE_KEY, Attribute::JwsEs256PrivateKey => ATTR_JWS_ES256_PRIVATE_KEY,
Attribute::LastModifiedCid => ATTR_LAST_MODIFIED_CID, Attribute::LastModifiedCid => ATTR_LAST_MODIFIED_CID,
Attribute::LdapAllowUnixPwBind => ATTR_LDAP_ALLOW_UNIX_PW_BIND,
Attribute::LdapEmailAddress => ATTR_LDAP_EMAIL_ADDRESS, Attribute::LdapEmailAddress => ATTR_LDAP_EMAIL_ADDRESS,
Attribute::LdapKeys => ATTR_LDAP_KEYS, Attribute::LdapKeys => ATTR_LDAP_KEYS,
Attribute::LdapSshPublicKey => ATTR_LDAP_SSHPUBLICKEY, Attribute::LdapSshPublicKey => ATTR_LDAP_SSHPUBLICKEY,

View file

@ -111,10 +111,10 @@ 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 { pub static ref SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND: SchemaAttribute = SchemaAttribute {
uuid: UUID_SCHEMA_ATTR_DOMAIN_LDAP_ALLOW_UNIX_PW_BIND, uuid: UUID_SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND,
name: Attribute::DomainLdapAllowUnixPwBind.into(), name: Attribute::LdapAllowUnixPwBind.into(),
description: "Configuration to allow binds to LDAP objects using UNIX passwords.".to_string(), description: "Configuration to enable binds to LDAP objects using their UNIX password.".to_string(),
unique: false, unique: false,
syntax: SyntaxType::Boolean, syntax: SyntaxType::Boolean,
..Default::default() ..Default::default()
@ -716,8 +716,11 @@ pub static ref SCHEMA_CLASS_DOMAIN_INFO: SchemaClass = SchemaClass {
uuid: UUID_SCHEMA_CLASS_DOMAIN_INFO, uuid: UUID_SCHEMA_CLASS_DOMAIN_INFO,
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![
systemmay: vec![Attribute::DomainSsid.into(), Attribute::DomainLdapBasedn.into(),Attribute::DomainLdapAllowUnixPwBind.into()], Attribute::DomainSsid.into(),
Attribute::DomainLdapBasedn.into(),
Attribute::LdapAllowUnixPwBind.into()
],
systemmust: vec![ systemmust: vec![
Attribute::Name.into(), Attribute::Name.into(),
Attribute::DomainUuid.into(), Attribute::DomainUuid.into(),

View file

@ -241,9 +241,7 @@ pub const UUID_SCHEMA_ATTR_AUTH_PRIVILEGE_EXPIRY: Uuid =
uuid!("00000000-0000-0000-0000-ffff00000142"); uuid!("00000000-0000-0000-0000-ffff00000142");
pub const UUID_SCHEMA_ATTR_IMAGE: Uuid = uuid!("00000000-0000-0000-0000-ffff00000143"); 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"); pub const UUID_SCHEMA_ATTR_DENIED_NAME: Uuid = uuid!("00000000-0000-0000-0000-ffff00000144");
pub const UUID_SCHEMA_ATTR_LDAP_ALLOW_UNIX_PW_BIND: Uuid =
// 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"); 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");

View file

@ -665,7 +665,7 @@ mod tests {
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await;
let disallow_unix_pw_flag = ModifyEvent::new_internal_invalid( let disallow_unix_pw_flag = ModifyEvent::new_internal_invalid(
filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))), 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 assert!(idms_prox_write
.qs_write .qs_write
@ -684,7 +684,7 @@ mod tests {
let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await; let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await;
let allow_unix_pw_flag = ModifyEvent::new_internal_invalid( let allow_unix_pw_flag = ModifyEvent::new_internal_invalid(
filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))), 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.qs_write.modify(&allow_unix_pw_flag).is_ok());
assert!(idms_prox_write.commit().is_ok()); assert!(idms_prox_write.commit().is_ok());

View file

@ -565,7 +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_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

@ -817,7 +817,7 @@ pub trait QueryServerTransaction<'a> {
fn get_domain_ldap_allow_unix_pw_bind(&mut self) -> Result<bool, OperationError> { fn get_domain_ldap_allow_unix_pw_bind(&mut self) -> Result<bool, OperationError> {
self.internal_search_uuid(UUID_DOMAIN_INFO).map(|entry| { self.internal_search_uuid(UUID_DOMAIN_INFO).map(|entry| {
entry entry
.get_ava_single_bool(Attribute::DomainLdapAllowUnixPwBind) .get_ava_single_bool(Attribute::LdapAllowUnixPwBind)
.unwrap_or(true) .unwrap_or(true)
}) })
} }

View file

@ -5,7 +5,8 @@ impl DomainOpt {
pub fn debug(&self) -> bool { pub fn debug(&self) -> bool {
match self { match self {
DomainOpt::SetDisplayName(copt) => copt.copt.debug, 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, DomainOpt::Show(copt) | DomainOpt::ResetTokenKey(copt) => copt.debug,
} }
} }
@ -37,6 +38,13 @@ impl DomainOpt {
Err(e) => handle_client_error(e, &copt.output_mode), 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) => { DomainOpt::Show(copt) => {
let client = copt.to_client(OpType::Read).await; let client = copt.to_client(OpType::Read).await;
match client.idm_domain_get().await { match client.idm_domain_get().await {

View file

@ -870,6 +870,14 @@ pub enum DomainOpt {
#[clap(name = "new-basedn")] #[clap(name = "new-basedn")]
new_basedn: String, 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")] #[clap(name = "show")]
/// Show information about this system's domain /// Show information about this system's domain
Show(CommonOpt), Show(CommonOpt),