From 02325a70178f1166fdd93213b71a516b388b58ab Mon Sep 17 00:00:00 2001 From: Kaare Hoff Skovgaard Date: Wed, 30 Jul 2025 21:36:48 +0200 Subject: [PATCH] Begin preparing to move LDAP accounts into passdb only This should allow LDAP accounts to have password set in LDAP, as well as provisioning service accounts statically in nix. This will also move alias configuration of all accounts into nix as well. --- .../infrastructure/mailserver/accounts.nix | 128 ++++++++++++++++++ .../nixos/infrastructure/mailserver/ldap.nix | 70 ++++------ 2 files changed, 158 insertions(+), 40 deletions(-) create mode 100644 nix/modules/nixos/infrastructure/mailserver/accounts.nix diff --git a/nix/modules/nixos/infrastructure/mailserver/accounts.nix b/nix/modules/nixos/infrastructure/mailserver/accounts.nix new file mode 100644 index 0000000..5a948b6 --- /dev/null +++ b/nix/modules/nixos/infrastructure/mailserver/accounts.nix @@ -0,0 +1,128 @@ +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.khscodes.infrastructure.mailserver; + accountOption = lib.khscodes.mkSubmodule { + description = "mail account"; + options = { + name = lib.mkOption { + type = lib.types.str; + example = "user1@example.com"; + description = "Username"; + }; + + aliases = lib.mkOption { + type = lib.types.listOf lib.types.str; + example = [ + "abuse@example.com" + "postmaster@example.com" + ]; + default = [ ]; + description = '' + A list of aliases of this login account. + Note: Use list entries like "@example.com" to create a catchAll + that allows sending from all email addresses in these domain. + ''; + }; + + aliasesRegexp = lib.mkOption { + type = lib.types.listOf lib.types.str; + example = [ ''/^tom\..*@domain\.com$/'' ]; + default = [ ]; + description = '' + Same as {option}`mailserver.aliases` but using PCRE (Perl compatible regex). + ''; + }; + + catchAll = lib.typs.mkOption { + type = lib.types.listOf (lib.types.enum config.mailserver.domains); + example = [ + "example.com" + "example2.com" + ]; + default = [ ]; + description = '' + For which domains should this account act as a catch all? + Note: Does not allow sending from all addresses of these domains. + ''; + }; + + quota = lib.mkOption { + type = lib.types.nullOr lib.types.str; + default = null; + example = "2G"; + description = '' + Per user quota rules. Accepted sizes are `xx k/M/G/T` with the + obvious meaning. Leave blank for the standard quota `100G`. + ''; + }; + + sieveScript = lib.mkOption { + type = lib.types.nullOr lib.types.lines; + default = null; + example = '' + require ["fileinto", "mailbox"]; + + if address :is "from" "gitlab@mg.gitlab.com" { + fileinto :create "GitLab"; + stop; + } + + # This must be the last rule, it will check if list-id is set, and + # file the message into the Lists folder for further investigation + elsif header :matches "list-id" "" { + fileinto :create "Lists"; + stop; + } + ''; + description = '' + Per-user sieve script. + ''; + }; + + sendOnly = lib.types.mkOption { + type = lib.types.bool; + default = false; + description = '' + Specifies if the account should be a send-only account. + Emails sent to send-only accounts will be rejected from + unauthorized senders with the `sendOnlyRejectMessage` + stating the reason. + ''; + }; + + sendOnlyRejectMessage = lib.mkOption { + type = lib.types.str; + default = "This account cannot receive emails."; + description = '' + The message that will be returned to the sender when an email is + sent to a send-only account. Only used if the account is marked + as send-only. + ''; + }; + + isLdapAccount = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Marks the account as an LDAP account. Non LDAP accounts gets passwords provisioned, such that other services can use them"; + }; + }; + }; + + systemAccounts = lib.attrsets.foldlAttrs ( + acc: name: value: + if value.isLdapAccount then acc else acc ++ [ name ] + ) [ ] cfg.accounts; +in +{ + options.khscodes.infrastructure.mailserver.accounts = lib.mkOption { + type = lib.types.attrsOf accountOption; + default = { }; + }; + config = lib.mkIf cfg.enable { + }; +} diff --git a/nix/modules/nixos/infrastructure/mailserver/ldap.nix b/nix/modules/nixos/infrastructure/mailserver/ldap.nix index 8699fbc..2b0ae43 100644 --- a/nix/modules/nixos/infrastructure/mailserver/ldap.nix +++ b/nix/modules/nixos/infrastructure/mailserver/ldap.nix @@ -1,7 +1,12 @@ -{ lib, config, ... }: +{ + lib, + config, + pkgs, + ... +}: let cfg = config.khscodes.infrastructure.mailserver; - secretFile = "/run/secret/dovecot/ldap"; + secretFile = "/run/secret/dovecot/dovecot-ldap.conf.ext"; in { options.khscodes.infrastructure.mailserver = { @@ -16,45 +21,20 @@ in }; config = lib.mkIf cfg.enable { - mailserver.ldap = { - enable = true; - uris = [ "ldaps://login.kaareskovgaard.net" ]; - searchBase = "dc=login,dc=kaareskovgaard,dc=net"; - searchScope = "sub"; - bind = { - dn = "dn=token"; - passwordFile = secretFile; - }; - dovecot = { - # Map LDAP uid to dovecot user, and ldap userPassword to dovecot password - passAttrs = "uid=user"; - passFilter = "(&(class=account)(memberOf=mail_user)(uid=%u))"; - # This filter is used both when receiving mail (thus needing to lookup by mail address, and when authenticating, requriing the lookup by uid.) - # Note that the pass filter only allows looking up by uid, so should still only be able to authenticate using that. - userFilter = "(&(class=account)(memberOf=mail_user)(|(mail=%u)(uid=%u)))"; - userAttrs = "uid=user"; - }; - postfix = { - filter = "(&(class=account)(memberOf=mail_user)(mail=%s))"; - mailAttribute = "uid"; - uidAttribute = "uid"; - }; - }; + services.dovecot2.extraConfig = '' + passdb { + driver = ldap + args = ${secretFile} + } + ''; systemd.services = { dovecot2 = { unitConfig = { ConditionPathExists = [ secretFile ]; }; - }; - postfix = { - unitConfig = { - ConditionPathExists = [ secretFile ]; - }; - }; - postfix-setup = { - unitConfig = { - ConditionPathExists = [ secretFile ]; - }; + serviceConfig.ReadOnlyPaths = [ + secretFile + ]; }; }; @@ -62,15 +42,25 @@ in { contents = '' {{- with secret "${cfg.ldap.mount}/data/${cfg.ldap.path}" -}} - {{ .Data.data.apiToken }} + ldap_version = 3 + uris = ldaps://login.kaareskovgaard.net + + tls_require_cert = hard + tls_ca_cert_file = ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt + dn = dn=token + dnpass = {{ .Data.data.apiToken }} + auth_bind = yes + base = dc=login,dc=kaareskovgaard,dc=net + scope = subtree + pass_attrs = uid=user + pass_filter = (&(class=account)(memberOf=mail_user)(uid=%u) {{- end -}} ''; destination = secretFile; - owner = "dovecot"; - group = "dovecot"; + owner = "root"; + group = "root"; restartUnits = [ "dovecot2.service" - "postfix.service" ]; } ];