From a87d01ea79c50789d90f1f94d40357fec118dfe7 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Thu, 12 Mar 2026 03:23:51 +0100 Subject: [PATCH] ldap: reorganize and regroup options Now that we have more experience with how we use the LDAP module options we can make smarter decisions in how to organize them. We can also explain much better what these options imply, which results in more extensive option documentation. --- default.nix | 119 +++++++++++++++++++++++++--------------- mail-server/dovecot.nix | 6 +- mail-server/postfix.nix | 10 ++-- tests/ldap.nix | 4 +- 4 files changed, 86 insertions(+), 53 deletions(-) diff --git a/default.nix b/default.nix index ad3164e..d8c39d0 100644 --- a/default.nix +++ b/default.nix @@ -319,7 +319,11 @@ in ] ''; description = '' - URIs where your LDAP server can be reached + List of LDAP server URIs. Multiple can be specified. + + Use `ldaps://` for implicit TLS or `ldap://` for a plain connection. See + also {option}`mailserver.ldap.startTls` to enable StartTLS on plain + connections. ''; }; @@ -327,16 +331,16 @@ in type = types.bool; default = false; description = '' - Whether to enable StartTLS upon connection to the server. + Whether to enable StartTLS on ``ldap://`` connections. ''; }; - tlsCAFile = mkOption { + caFile = mkOption { type = types.path; default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; - defaultText = literalMD "see [source](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/master/default.nix)"; + defaultText = lib.literalExpression "\${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; description = '' - Certificate trust anchors used to verify the LDAP server certificate. + Bundle of CA certificates used to authenticate the LDAP server certificate. ''; }; @@ -345,37 +349,44 @@ in type = types.str; example = "cn=mail,ou=accounts,dc=example,dc=com"; description = '' - Distinguished name used by the mail server to do lookups - against the LDAP servers. + DN used to bind against the LDAP server. + + The server uses this account to lookup and filter accounts. ''; }; passwordFile = mkOption { - type = types.str; + type = types.pathWith { inStore = false; }; example = "/run/my-secret"; description = '' - A file containing the password required to authenticate against the LDAP servers. + File containing the password required to bind against the LDAP server. ''; }; }; - searchBase = mkOption { + base = mkOption { type = types.str; example = "ou=people,ou=accounts,dc=example,dc=com"; description = '' - Base DN at below which to search for users accounts. + Base DN below which user accounts are searched for. ''; }; - searchScope = mkOption { + scope = mkOption { type = types.enum [ - "sub" "base" "one" + "sub" ]; default = "sub"; description = '' - Search scope below which users accounts are looked for. + Search scope relative to the {option}`mailserver.ldap.base`. + + - base: Only the exact Base DN + - one: Immediate child entries of the Base DN, but not the Base DN itself. + - sub: Base DN and all descendant entries at any depth. + + In practice only `one` or `sub` are suitable for multiple LDAP users. ''; }; @@ -395,6 +406,17 @@ in ''; }; + username = mkOption { + type = types.str; + default = "uid"; + example = "name"; + description = '' + The LDAP attribute referencing the username used to login with. + + Typically the `uid` attribute which is part of the `inetOrgPerson` schema. + ''; + }; + password = mkOption { type = types.str; default = "userPassword"; @@ -405,6 +427,19 @@ in Typically the `userPassword` attribute which is part of the `inetOrgPerson` schema. ''; }; + + mail = mkOption { + type = types.str; + default = "mail"; + example = "maildrop"; + description = '' + The attribute name used for looking up accounts by mail address. + + Typically this can be the `mail` attribute from the `inetOrgPerson` + schema, or the `maildrop` attribute from the unofficial Postfix + schema. + ''; + }; }; dovecot = { @@ -413,11 +448,12 @@ in default = "mail=%{user}"; example = "(&(objectClass=inetOrgPerson)(mail=%{user}))"; description = '' - Filter for user lookups in Dovecot. + LDAP filter used for LMTP delivery from Postfix and post-login + information construction, like the home directory. - See the user_filter reference at - https://doc.dovecot.org/2.3/configuration_manual/authentication/ldap_settings_auth/#user-filter - in the Dovecot manual. + See the [user_filter] reference at in the Dovecot manual. + + [user_filter]: https://doc.dovecot.org/2.3/configuration_manual/authentication/ldap_settings_auth/#user-filter ''; }; @@ -426,11 +462,12 @@ in default = "mail=%{user}"; example = "(&(objectClass=inetOrgPerson)(mail=%{user}))"; description = '' - Filter for password lookups in Dovecot. + LDAP filter used to restrict which users are eligible to + authenticate against Dovecot. - See the pass_filter reference for - https://doc.dovecot.org/2.3/configuration_manual/authentication/ldap_settings_auth/#pass-filter - in the Dovecot manual. + See the [pass_filter] reference in the Dovecot manual. + + [pass_filter]: https://doc.dovecot.org/2.3/configuration_manual/authentication/ldap_settings_auth/#pass-filter ''; }; }; @@ -438,29 +475,14 @@ in postfix = { filter = mkOption { type = types.str; - default = "mail=%s"; + default = with cfg.ldap.attributes; "${mail}=%s"; + defaultText = lib.literalExpression '' + with config.mailserver.ldap.attributes; "''${mail}=%s"; + ''; example = "(&(objectClass=inetOrgPerson)(mail=%s))"; description = '' - LDAP filter used to search for an account by mail, where - `%s` is a substitute for the address in - question. - ''; - }; - - uidAttribute = mkOption { - type = types.str; - default = "mail"; - example = "uid"; - description = '' - The LDAP attribute referencing the account name for a user. - ''; - }; - - mailAttribute = mkOption { - type = types.str; - default = "mail"; - description = '' - The LDAP attribute holding mail addresses for a user. + LDAP filter used to search for an account by mail, where `%s` is a + substitute for the address in question. ''; }; }; @@ -1640,5 +1662,16 @@ in (mkRemovedOptionModule [ "mailserver" "ldap" "dovecot" "passAttrs" ] '' The pass_attrs field is now used internally. You can customize the `mailserver.ldap.attributes.password` field instead. '') + (mkRenamedOptionModule [ "mailserver" "ldap" "tlsCAFile" ] [ "mailserver" "ldap" "caFile" ]) + (mkRenamedOptionModule [ "mailserver" "ldap" "searchBase" ] [ "mailserver" "ldap" "base" ]) + (mkRenamedOptionModule [ "mailserver" "ldap" "searchScope" ] [ "mailserver" "ldap" "scope" ]) + (mkRenamedOptionModule + [ "mailserver" "ldap" "postfix" "uidAttribute" ] + [ "mailserver" "ldap" "attributes" "username" ] + ) + (mkRenamedOptionModule + [ "mailserver" "ldap" "postfix" "mailAttribute" ] + [ "mailserver" "ldap" "attributes" "mail" ] + ) ]; } diff --git a/mail-server/dovecot.nix b/mail-server/dovecot.nix index 148c97d..124ecf6 100644 --- a/mail-server/dovecot.nix +++ b/mail-server/dovecot.nix @@ -71,12 +71,12 @@ let tls = yes ''} tls_require_cert = hard - tls_ca_cert_file = ${cfg.ldap.tlsCAFile} + tls_ca_cert_file = ${cfg.ldap.caFile} dn = ${cfg.ldap.bind.dn} sasl_bind = no auth_bind = yes - base = ${cfg.ldap.searchBase} - scope = ${mkLdapSearchScope cfg.ldap.searchScope} + base = ${cfg.ldap.base} + scope = ${mkLdapSearchScope cfg.ldap.scope} user_attrs = \ ${ldapUuidAttribute}=${ldapUuidAttribute}, \ =home=/var/vmail/ldap/%{ldap:${ldapUuidAttribute}}, \ diff --git a/mail-server/postfix.nix b/mail-server/postfix.nix index d860cbd..bf51b63 100644 --- a/mail-server/postfix.nix +++ b/mail-server/postfix.nix @@ -209,11 +209,11 @@ let server_host = ${lib.concatStringsSep " " cfg.ldap.uris} start_tls = ${if cfg.ldap.startTls then "yes" else "no"} version = 3 - tls_ca_cert_file = ${cfg.ldap.tlsCAFile} + tls_ca_cert_file = ${cfg.ldap.caFile} tls_require_cert = yes - search_base = ${cfg.ldap.searchBase} - scope = ${cfg.ldap.searchScope} + search_base = ${cfg.ldap.base} + scope = ${cfg.ldap.scope} bind = yes bind_dn = ${cfg.ldap.bind.dn} @@ -222,7 +222,7 @@ let ldapSenderLoginMap = pkgs.writeText "ldap-sender-login-map.cf" '' ${commonLdapConfig} query_filter = ${cfg.ldap.postfix.filter} - result_attribute = ${cfg.ldap.postfix.mailAttribute} + result_attribute = ${cfg.ldap.attributes.mail} ''; ldapSenderLoginMapFile = "/run/postfix/ldap-sender-login-map.cf"; appendPwdInSenderLoginMap = appendLdapBindPwd { @@ -236,7 +236,7 @@ let ldapVirtualMailboxMap = pkgs.writeText "ldap-virtual-mailbox-map.cf" '' ${commonLdapConfig} query_filter = ${cfg.ldap.postfix.filter} - result_attribute = ${cfg.ldap.postfix.uidAttribute} + result_attribute = ${cfg.ldap.attributes.username} ''; ldapVirtualMailboxMapFile = "/run/postfix/ldap-virtual-mailbox-map.cf"; appendPwdInVirtualMailboxMap = appendLdapBindPwd { diff --git a/tests/ldap.nix b/tests/ldap.nix index d2e89fa..7402a67 100644 --- a/tests/ldap.nix +++ b/tests/ldap.nix @@ -110,8 +110,8 @@ in dn = "cn=mail,dc=example"; passwordFile = "/etc/bind-password"; }; - searchBase = "ou=users,dc=example"; - searchScope = "sub"; + base = "ou=users,dc=example"; + scope = "sub"; }; forwards = {