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" ]; } ];