lldap: automatic provision + system users + stalwart + whatever

This commit is contained in:
eyjhb 2025-02-03 18:15:53 +01:00
parent 4a0129585a
commit 82caf96d36
Signed by: eyjhb
GPG key ID: 609F508E3239F920
19 changed files with 405 additions and 285 deletions

View file

@ -19,10 +19,88 @@ index 6f42473..b3746a1 100644
&config,
'';
whoamiPatch = pkgs.writeText "lldap-whoami.patch" ''
diff --git a/server/src/infra/ldap_handler.rs b/server/src/infra/ldap_handler.rs
index 7257c31..feda03c 100644
--- a/server/src/infra/ldap_handler.rs
+++ b/server/src/infra/ldap_handler.rs
@@ -26,7 +26,7 @@ use ldap3_proto::proto::{
LdapDerefAliases, LdapExtendedRequest, LdapExtendedResponse, LdapFilter, LdapModify,
LdapModifyRequest, LdapModifyType, LdapOp, LdapPartialAttribute, LdapPasswordModifyRequest,
LdapResult as LdapResultOp, LdapResultCode, LdapSearchRequest, LdapSearchResultEntry,
- LdapSearchScope,
+ LdapSearchScope, OID_PASSWORD_MODIFY, OID_WHOAMI,
};
use std::collections::HashMap;
use tracing::{debug, instrument, warn};
@@ -181,7 +181,7 @@ fn root_dse_response(base_dn: &str) -> LdapOp {
LdapPartialAttribute {
atype: "supportedExtension".to_string(),
// Password modification extension.
- vals: vec![b"1.3.6.1.4.1.4203.1.11.1".to_vec()],
+ vals: vec![OID_PASSWORD_MODIFY.as_bytes().to_vec()],
},
LdapPartialAttribute {
atype: "supportedControl".to_string(),
@@ -204,6 +204,11 @@ fn root_dse_response(base_dn: &str) -> LdapOp {
atype: "isGlobalCatalogReady".to_string(),
vals: vec![b"false".to_vec()],
},
+ LdapPartialAttribute {
+ atype: "supportedExtension".to_string(),
+ // whoami extension.
+ vals: vec![OID_WHOAMI.as_bytes().to_vec()],
+ },
],
})
}
@@ -413,16 +418,33 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
#[instrument(skip_all, level = "debug")]
async fn do_extended_request(&mut self, request: &LdapExtendedRequest) -> Vec<LdapOp> {
- match LdapPasswordModifyRequest::try_from(request) {
- Ok(password_request) => self
+ if let Ok(password_request) = LdapPasswordModifyRequest::try_from(request) {
+ return self
.do_password_modification(&password_request)
.await
- .unwrap_or_else(|e: LdapError| vec![make_extended_response(e.code, e.message)]),
- Err(_) => vec![make_extended_response(
- LdapResultCode::UnwillingToPerform,
- format!("Unsupported extended operation: {}", &request.name),
- )],
+ .unwrap_or_else(|e: LdapError| vec![make_extended_response(e.code, e.message)]);
}
+
+ if request.name == OID_WHOAMI {
+ let dn = self
+ .user_info
+ .as_ref()
+ .map(|user_info| {
+ format!(
+ "dn:uid={},ou=people,{}",
+ user_info.user.clone().into_string(),
+ self.ldap_info.base_dn_str
+ )
+ })
+ .unwrap_or_default();
+
+ return vec![make_extended_response(LdapResultCode::Success, dn)];
+ }
+
+ return vec![make_extended_response(
+ LdapResultCode::UnwillingToPerform,
+ format!("Unsupported extended operation: {}", &request.name),
+ )];
}
async fn handle_modify_change(
'';
pkgLLDAPCli = pkgs.callPackage ./../../../../shared/pkgs/lldap-cli.nix {};
in {
imports = [
# ./test.nix
./provision.nix
];
environment.systemPackages = [
@ -33,7 +111,7 @@ in {
enable = true;
package = pkgs.lldap.overrideAttrs (old: {
patches = old.patches ++ [ resetPasswordStartPatch ];
patches = old.patches ++ [ resetPasswordStartPatch whoamiPatch ];
});
settings = {
@ -100,22 +178,29 @@ in {
users = {
admin = "admin";
bind = "bind_user";
};
# bind = "bind_user";
} // (lib.mapAttrs (n: v: v.user_id) config.services.lldap.provision.users);
groups = {
admin = "lldap_admin";
member = "base_member";
system = "system_service";
system_email = "system_email";
};
password_manager = "lldap_password_manager";
strict_readonly = "lldap_strict_readonly";
# system = "system_service";
# system_email = "system_email";
} // (lib.mapAttrs (n: v: v.display_name) config.services.lldap.provision.groups);
ou = {
groups = "groups";
users = "people";
};
attr = {
attr = let
toCamelCase = w: let
parts = lib.splitString "_" w;
cap = lib.imap0 (i: v: if i == 0 then v else (lib.toUpper (lib.substring 0 1 v)) + (lib.substring 1 (lib.stringLength v) v));
in lib.concatStrings (cap parts);
in {
uid = "uid";
firstname = "givenName";
lastname = "sn";
@ -124,9 +209,9 @@ in {
groupname = "cn";
# custom
member_email = "member_email";
mail_disk_quota = "mail_disk_quota";
};
# member_email = "member_email";
# mail_disk_quota = "mail_disk_quota";
} // (lib.mapAttrs (n: v: toCamelCase v.name) config.services.lldap.provision.user_attributes);
age_secret = config.age.secrets.lldap-bind-user-pass.path;
};
@ -156,6 +241,31 @@ in {
mkAnd = v: { type = "and"; values = v; };
mkOr = v: { type = "or"; values = v; };
# mkProvision helpers for creating users
mkProvisionEmail = name: "${name}@${config.mine.shared.settings.domain}";
mkProvisionUserNormal = name: config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
user_id = name;
membermail = mkProvisionEmail name;
mail = "env:EMAIL_${lib.toUpper name}";
groups = [ lconfig.groups.member ];
membermaildiskquota = 100*1024*1024; # mb
});
mkProvisionUserSystem = name: password_file: config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
user_id = name;
membermail = mkProvisionEmail name;
password = "file:${password_file}";
groups = [ lconfig.groups.system_mail lconfig.groups.system_service ];
membermaildiskquota = 10*1024*1024; # mb
});
mkProvisionUserAdmin = name: config.mine.shared.lib.ldap.mkScope (lconfig: llib: {
user_id = name;
membermail = mkProvisionEmail name;
groups = [ lconfig.groups.admin lconfig.groups.member ];
membermaildiskquota = 100*1024*1024; # mb
});
};
mine.shared.meta.lldap = {