From fb56bcf747d126be73be426efc809077af2058c9 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Sun, 15 Jun 2025 05:08:47 +0200 Subject: [PATCH 1/2] treewide: remove global `with lib` Instead inherit required functions from lib. --- default.nix | 53 +++++++++++++++++++++++---------------- mail-server/rsnapshot.nix | 7 ++++-- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/default.nix b/default.nix index 94b3186..60d9cec 100644 --- a/default.nix +++ b/default.nix @@ -21,9 +21,20 @@ ... }: -with lib; - let + inherit (lib) + literalExpression + literalMD + mkDefault + mkEnableOption + mkOption + mkOptionType + mkRemovedOptionModule + mkRenamedOptionModule + types + warn + ; + cfg = config.mailserver; in { @@ -269,7 +280,7 @@ in tlsCAFile = mkOption { type = types.path; default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"; - defaultText = lib.literalMD "see [source](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/master/default.nix)"; + defaultText = literalMD "see [source](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/master/default.nix)"; description = '' Certifificate trust anchors used to verify the LDAP server certificate. ''; @@ -1064,7 +1075,7 @@ in type = types.str; # read the default from nixos' redis module default = config.services.redis.servers.rspamd.unixSocket; - defaultText = lib.literalExpression "config.services.redis.servers.rspamd.unixSocket"; + defaultText = literalExpression "config.services.redis.servers.rspamd.unixSocket"; description = '' Path, IP address or hostname that Rspamd should use to contact Redis. ''; @@ -1073,7 +1084,7 @@ in port = mkOption { type = with types; nullOr port; default = null; - example = lib.literalExpression "config.services.redis.servers.rspamd.port"; + example = literalExpression "config.services.redis.servers.rspamd.port"; description = '' Port that Rspamd should use to contact Redis. ''; @@ -1082,7 +1093,7 @@ in password = mkOption { type = types.nullOr types.str; default = config.services.redis.servers.rspamd.requirePass; - defaultText = lib.literalExpression "config.services.redis.servers.rspamd.requirePass"; + defaultText = literalExpression "config.services.redis.servers.rspamd.requirePass"; description = '' Password that rspamd should use to contact redis, or null if not required. ''; @@ -1102,7 +1113,7 @@ in sendingFqdn = mkOption { type = types.str; default = cfg.fqdn; - defaultText = lib.literalMD "{option}`mailserver.fqdn`"; + defaultText = literalMD "{option}`mailserver.fqdn`"; example = "myserver.example.com"; description = '' The fully qualified domain name of the mail server used to @@ -1178,7 +1189,7 @@ in start program = "${pkgs.systemd}/bin/systemctl start rspamd" stop program = "${pkgs.systemd}/bin/systemctl stop rspamd" ''; - defaultText = lib.literalMD "see [source](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/master/default.nix)"; + defaultText = literalMD "see [source](https://gitlab.com/simple-nixos-mailserver/nixos-mailserver/-/blob/master/default.nix)"; description = '' The configuration used for monitoring via monit. Use a mail address that you actively check and set it via 'set alert ...'. @@ -1287,7 +1298,7 @@ in locations = mkOption { type = types.listOf types.path; default = [ cfg.mailDirectory ]; - defaultText = lib.literalExpression "[ config.mailserver.mailDirectory ]"; + defaultText = literalExpression "[ config.mailserver.mailDirectory ]"; description = "The locations that are to be backed up by borg."; }; @@ -1388,29 +1399,29 @@ in }; imports = [ - (lib.mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maintenance" "enable" ] '' + (mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maintenance" "enable" ] '' This option is not needed for fts-flatcurve '') - (lib.mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maintenance" "onCalendar" ] '' + (mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maintenance" "onCalendar" ] '' This option is not needed for fts-flatcurve '') - (lib.mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maintenance" "randomizedDelaySec" ] '' + (mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maintenance" "randomizedDelaySec" ] '' This option is not needed for fts-flatcurve '') - (lib.mkRemovedOptionModule [ "mailserver" "fullTextSearch" "minSize" ] '' + (mkRemovedOptionModule [ "mailserver" "fullTextSearch" "minSize" ] '' This option is not supported by fts-flatcurve '') - (lib.mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maxSize" ] '' + (mkRemovedOptionModule [ "mailserver" "fullTextSearch" "maxSize" ] '' This option is not needed since fts-xapian 1.8.3 '') - (lib.mkRemovedOptionModule [ "mailserver" "fullTextSearch" "indexAttachments" ] '' + (mkRemovedOptionModule [ "mailserver" "fullTextSearch" "indexAttachments" ] '' Text attachments are always indexed since fts-xapian 1.4.8 '') - (lib.mkRenamedOptionModule + (mkRenamedOptionModule [ "mailserver" "rebootAfterKernelUpgrade" "enable" ] [ "system" "autoUpgrade" "allowReboot" ] ) - (lib.mkRemovedOptionModule [ "mailserver" "rebootAfterKernelUpgrade" "method" ] '' + (mkRemovedOptionModule [ "mailserver" "rebootAfterKernelUpgrade" "method" ] '' Use `system.autoUpgrade` instead. '') ./mail-server/assertions.nix @@ -1427,17 +1438,17 @@ in ./mail-server/rspamd.nix ./mail-server/nginx.nix ./mail-server/kresd.nix - (lib.mkRemovedOptionModule [ "mailserver" "policydSPFExtraConfig" ] '' + (mkRemovedOptionModule [ "mailserver" "policydSPFExtraConfig" ] '' SPF checking has been migrated to Rspamd, which makes this config redundant. Please look into the rspamd config to migrate your settings. It may be that they are redundant and are already configured in rspamd like for skip_addresses. '') - (lib.mkRemovedOptionModule [ "mailserver" "dkimHeaderCanonicalization" ] '' + (mkRemovedOptionModule [ "mailserver" "dkimHeaderCanonicalization" ] '' DKIM signing has been migrated to Rspamd, which always uses relaxed canonicalization. '') - (lib.mkRemovedOptionModule [ "mailserver" "dkimBodyCanonicalization" ] '' + (mkRemovedOptionModule [ "mailserver" "dkimBodyCanonicalization" ] '' DKIM signing has been migrated to Rspamd, which always uses relaxed canonicalization. '') - (lib.mkRemovedOptionModule [ "mailserver" "smtpdForbidBareNewline" ] '' + (mkRemovedOptionModule [ "mailserver" "smtpdForbidBareNewline" ] '' The workaround for the SMTP Smuggling attack is default enabled in Postfix >3.9. Use `services.postfix.config.smtpd_forbid_bare_newline` if you need to deviate from its default. '') ]; diff --git a/mail-server/rsnapshot.nix b/mail-server/rsnapshot.nix index de4f13e..f01ff8d 100644 --- a/mail-server/rsnapshot.nix +++ b/mail-server/rsnapshot.nix @@ -21,9 +21,12 @@ ... }: -with lib; - let + inherit (lib) + optionalString + mkIf + ; + cfg = config.mailserver; preexecDefined = cfg.backup.cmdPreexec != null; From a2152f98073bce7d59cb64180b134d499547f716 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Sun, 15 Jun 2025 05:39:20 +0200 Subject: [PATCH 2/2] treewide: remove overly broad `with cfg` Makes it really hard to follow references and we were being explicit in most places already anyway. --- mail-server/dovecot.nix | 496 ++++++++++++++++++------------------ mail-server/environment.nix | 24 +- mail-server/networking.nix | 28 +- mail-server/postfix.nix | 338 ++++++++++++------------ mail-server/rspamd.nix | 394 ++++++++++++++-------------- mail-server/systemd.nix | 104 ++++---- 6 files changed, 686 insertions(+), 698 deletions(-) diff --git a/mail-server/dovecot.nix b/mail-server/dovecot.nix index 148befc..d2da51b 100644 --- a/mail-server/dovecot.nix +++ b/mail-server/dovecot.nix @@ -163,283 +163,281 @@ let in { - config = - with cfg; - lib.mkIf enable { - assertions = [ + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = junkMailboxNumber == 1; + message = "nixos-mailserver requires exactly one dovecot mailbox with the 'special use' flag set to 'Junk' (${builtins.toString junkMailboxNumber} have been found)"; + } + ]; + + warnings = + lib.optional + ( + (builtins.length cfg.fullTextSearch.languages > 1) + && (builtins.elem "stopwords" cfg.fullTextSearch.filters) + ) + '' + Using stopwords in `mailserver.fullTextSearch.filters` with multiple + languages in `mailserver.fullTextSearch.languages` configured WILL + cause some searches to fail. + + The recommended solution is to NOT use the stopword filter when + multiple languages are present in the configuration. + ''; + + # for sieve-test. Shelling it in on demand usually doesnt' work, as it reads + # the global config and tries to open shared libraries configured in there, + # which are usually not compatible. + environment.systemPackages = [ + pkgs.dovecot_pigeonhole + ] ++ lib.optional cfg.fullTextSearch.enable pkgs.dovecot-fts-flatcurve; + + # For compatibility with python imaplib + environment.etc."dovecot/modules".source = "/run/current-system/sw/lib/dovecot/modules"; + + services.dovecot2 = { + enable = true; + enableImap = cfg.enableImap || cfg.enableImapSsl; + enablePop3 = cfg.enablePop3 || cfg.enablePop3Ssl; + enablePAM = false; + enableQuota = true; + mailGroup = cfg.vmailGroupName; + mailUser = cfg.vmailUserName; + mailLocation = dovecotMaildir; + sslServerCert = certificatePath; + sslServerKey = keyPath; + enableDHE = lib.mkDefault false; + enableLmtp = true; + mailPlugins.globally.enable = lib.optionals cfg.fullTextSearch.enable [ + "fts" + "fts_flatcurve" + ]; + protocols = lib.optional cfg.enableManageSieve "sieve"; + + pluginSettings = { + sieve = "file:${cfg.sieveDirectory}/%{user}/scripts;active=${cfg.sieveDirectory}/%{user}/active.sieve"; + sieve_default = "file:${cfg.sieveDirectory}/%{user}/default.sieve"; + sieve_default_name = "default"; + } // (lib.optionalAttrs cfg.fullTextSearch.enable ftsPluginSettings); + + sieve = { + extensions = [ + "fileinto" + ]; + + scripts.after = builtins.toFile "spam.sieve" '' + require "fileinto"; + + if header :is "X-Spam" "Yes" { + fileinto "${junkMailboxName}"; + stop; + } + ''; + + pipeBins = map lib.getExe [ + (pkgs.writeShellScriptBin "rspamd-learn-ham.sh" "exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_ham") + (pkgs.writeShellScriptBin "rspamd-learn-spam.sh" "exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_spam") + ]; + }; + + imapsieve.mailbox = [ { - assertion = junkMailboxNumber == 1; - message = "nixos-mailserver requires exactly one dovecot mailbox with the 'special use' flag set to 'Junk' (${builtins.toString junkMailboxNumber} have been found)"; + name = junkMailboxName; + causes = [ + "COPY" + "APPEND" + ]; + before = ./dovecot/imap_sieve/report-spam.sieve; + } + { + name = "*"; + from = junkMailboxName; + causes = [ "COPY" ]; + before = ./dovecot/imap_sieve/report-ham.sieve; } ]; - warnings = - lib.optional - ( - (builtins.length cfg.fullTextSearch.languages > 1) - && (builtins.elem "stopwords" cfg.fullTextSearch.filters) - ) - '' - Using stopwords in `mailserver.fullTextSearch.filters` with multiple - languages in `mailserver.fullTextSearch.languages` configured WILL - cause some searches to fail. + mailboxes = cfg.mailboxes; - The recommended solution is to NOT use the stopword filter when - multiple languages are present in the configuration. - ''; + extraConfig = '' + #Extra Config + ${lib.optionalString cfg.debug '' + mail_debug = yes + auth_debug = yes + verbose_ssl = yes + ''} - # for sieve-test. Shelling it in on demand usually doesnt' work, as it reads - # the global config and tries to open shared libraries configured in there, - # which are usually not compatible. - environment.systemPackages = [ - pkgs.dovecot_pigeonhole - ] ++ lib.optional cfg.fullTextSearch.enable pkgs.dovecot-fts-flatcurve; - - # For compatibility with python imaplib - environment.etc."dovecot/modules".source = "/run/current-system/sw/lib/dovecot/modules"; - - services.dovecot2 = { - enable = true; - enableImap = enableImap || enableImapSsl; - enablePop3 = enablePop3 || enablePop3Ssl; - enablePAM = false; - enableQuota = true; - mailGroup = vmailGroupName; - mailUser = vmailUserName; - mailLocation = dovecotMaildir; - sslServerCert = certificatePath; - sslServerKey = keyPath; - enableDHE = lib.mkDefault false; - enableLmtp = true; - mailPlugins.globally.enable = lib.optionals cfg.fullTextSearch.enable [ - "fts" - "fts_flatcurve" - ]; - protocols = lib.optional cfg.enableManageSieve "sieve"; - - pluginSettings = { - sieve = "file:${cfg.sieveDirectory}/%{user}/scripts;active=${cfg.sieveDirectory}/%{user}/active.sieve"; - sieve_default = "file:${cfg.sieveDirectory}/%{user}/default.sieve"; - sieve_default_name = "default"; - } // (lib.optionalAttrs cfg.fullTextSearch.enable ftsPluginSettings); - - sieve = { - extensions = [ - "fileinto" - ]; - - scripts.after = builtins.toFile "spam.sieve" '' - require "fileinto"; - - if header :is "X-Spam" "Yes" { - fileinto "${junkMailboxName}"; - stop; - } - ''; - - pipeBins = map lib.getExe [ - (pkgs.writeShellScriptBin "rspamd-learn-ham.sh" "exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_ham") - (pkgs.writeShellScriptBin "rspamd-learn-spam.sh" "exec ${pkgs.rspamd}/bin/rspamc -h /run/rspamd/worker-controller.sock learn_spam") - ]; - }; - - imapsieve.mailbox = [ - { - name = junkMailboxName; - causes = [ - "COPY" - "APPEND" - ]; - before = ./dovecot/imap_sieve/report-spam.sieve; - } - { - name = "*"; - from = junkMailboxName; - causes = [ "COPY" ]; - before = ./dovecot/imap_sieve/report-ham.sieve; - } - ]; - - mailboxes = cfg.mailboxes; - - extraConfig = '' - #Extra Config - ${lib.optionalString debug '' - mail_debug = yes - auth_debug = yes - verbose_ssl = yes - ''} - - ${lib.optionalString (cfg.enableImap || cfg.enableImapSsl) '' - service imap-login { - inet_listener imap { - ${ - if cfg.enableImap then - '' - port = 143 - '' - else - '' - # see https://dovecot.org/pipermail/dovecot/2010-March/047479.html - port = 0 - '' - } - } - inet_listener imaps { - ${ - if cfg.enableImapSsl then - '' - port = 993 - ssl = yes - '' - else - '' - # see https://dovecot.org/pipermail/dovecot/2010-March/047479.html - port = 0 - '' - } + ${lib.optionalString (cfg.enableImap || cfg.enableImapSsl) '' + service imap-login { + inet_listener imap { + ${ + if cfg.enableImap then + '' + port = 143 + '' + else + '' + # see https://dovecot.org/pipermail/dovecot/2010-March/047479.html + port = 0 + '' } } - ''} - ${lib.optionalString (cfg.enablePop3 || cfg.enablePop3Ssl) '' - service pop3-login { - inet_listener pop3 { - ${ - if cfg.enablePop3 then - '' - port = 110 - '' - else - '' - # see https://dovecot.org/pipermail/dovecot/2010-March/047479.html - port = 0 - '' - } - } - inet_listener pop3s { - ${ - if cfg.enablePop3Ssl then - '' - port = 995 - ssl = yes - '' - else - '' - # see https://dovecot.org/pipermail/dovecot/2010-March/047479.html - port = 0 - '' - } + inet_listener imaps { + ${ + if cfg.enableImapSsl then + '' + port = 993 + ssl = yes + '' + else + '' + # see https://dovecot.org/pipermail/dovecot/2010-March/047479.html + port = 0 + '' } } - ''} - - protocol imap { - mail_max_userip_connections = ${toString cfg.maxConnectionsPerUser} - mail_plugins = $mail_plugins imap_sieve } - - service imap { - vsz_limit = ${builtins.toString cfg.imapMemoryLimit} MB - } - - protocol pop3 { - mail_max_userip_connections = ${toString cfg.maxConnectionsPerUser} - } - - mail_access_groups = ${vmailGroupName} - - # https://ssl-config.mozilla.org/#server=dovecot&version=2.3.21&config=intermediate&openssl=3.4.1&guideline=5.7 - ssl = required - ssl_min_protocol = TLSv1.2 - ssl_prefer_server_ciphers = no - ssl_curve_list = X25519:prime256v1:secp384r1 - - service lmtp { - unix_listener dovecot-lmtp { - group = ${postfixCfg.group} - mode = 0600 - user = ${postfixCfg.user} + ''} + ${lib.optionalString (cfg.enablePop3 || cfg.enablePop3Ssl) '' + service pop3-login { + inet_listener pop3 { + ${ + if cfg.enablePop3 then + '' + port = 110 + '' + else + '' + # see https://dovecot.org/pipermail/dovecot/2010-March/047479.html + port = 0 + '' + } } - vsz_limit = ${builtins.toString cfg.lmtpMemoryLimit} MB - } - - service quota-status { - inet_listener { - port = 0 + inet_listener pop3s { + ${ + if cfg.enablePop3Ssl then + '' + port = 995 + ssl = yes + '' + else + '' + # see https://dovecot.org/pipermail/dovecot/2010-March/047479.html + port = 0 + '' + } } - unix_listener quota-status { - user = postfix - } - vsz_limit = ${builtins.toString cfg.quotaStatusMemoryLimit} MB } + ''} - recipient_delimiter = ${cfg.recipientDelimiter} - lmtp_save_to_detail_mailbox = ${cfg.lmtpSaveToDetailMailbox} + protocol imap { + mail_max_userip_connections = ${toString cfg.maxConnectionsPerUser} + mail_plugins = $mail_plugins imap_sieve + } - protocol lmtp { - mail_plugins = $mail_plugins sieve + service imap { + vsz_limit = ${builtins.toString cfg.imapMemoryLimit} MB + } + + protocol pop3 { + mail_max_userip_connections = ${toString cfg.maxConnectionsPerUser} + } + + mail_access_groups = ${cfg.vmailGroupName} + + # https://ssl-config.mozilla.org/#server=dovecot&version=2.3.21&config=intermediate&openssl=3.4.1&guideline=5.7 + ssl = required + ssl_min_protocol = TLSv1.2 + ssl_prefer_server_ciphers = no + ssl_curve_list = X25519:prime256v1:secp384r1 + + service lmtp { + unix_listener dovecot-lmtp { + group = ${postfixCfg.group} + mode = 0600 + user = ${postfixCfg.user} } + vsz_limit = ${builtins.toString cfg.lmtpMemoryLimit} MB + } + service quota-status { + inet_listener { + port = 0 + } + unix_listener quota-status { + user = postfix + } + vsz_limit = ${builtins.toString cfg.quotaStatusMemoryLimit} MB + } + + recipient_delimiter = ${cfg.recipientDelimiter} + lmtp_save_to_detail_mailbox = ${cfg.lmtpSaveToDetailMailbox} + + protocol lmtp { + mail_plugins = $mail_plugins sieve + } + + passdb { + driver = passwd-file + args = ${passwdFile} + } + + userdb { + driver = passwd-file + args = ${userdbFile} + default_fields = uid=${builtins.toString cfg.vmailUID} gid=${builtins.toString cfg.vmailUID} home=${cfg.mailDirectory} + } + + ${lib.optionalString cfg.ldap.enable '' passdb { - driver = passwd-file - args = ${passwdFile} + driver = ldap + args = ${ldapConfFile} } userdb { - driver = passwd-file - args = ${userdbFile} - default_fields = uid=${builtins.toString cfg.vmailUID} gid=${builtins.toString cfg.vmailUID} home=${cfg.mailDirectory} + driver = ldap + args = ${ldapConfFile} + default_fields = home=${cfg.mailDirectory}/ldap/%{user} uid=${toString cfg.vmailUID} gid=${toString cfg.vmailUID} } + ''} - ${lib.optionalString cfg.ldap.enable '' - passdb { - driver = ldap - args = ${ldapConfFile} - } - - userdb { - driver = ldap - args = ${ldapConfFile} - default_fields = home=${cfg.mailDirectory}/ldap/%{user} uid=${toString cfg.vmailUID} gid=${toString cfg.vmailUID} - } - ''} - - service auth { - unix_listener auth { - mode = 0660 - user = ${postfixCfg.user} - group = ${postfixCfg.group} - } + service auth { + unix_listener auth { + mode = 0660 + user = ${postfixCfg.user} + group = ${postfixCfg.group} } + } - auth_mechanisms = plain login + auth_mechanisms = plain login - namespace inbox { - separator = ${cfg.hierarchySeparator} - inbox = yes - } + namespace inbox { + separator = ${cfg.hierarchySeparator} + inbox = yes + } - service indexer-worker { - ${lib.optionalString (cfg.fullTextSearch.memoryLimit != null) '' - vsz_limit = ${toString (cfg.fullTextSearch.memoryLimit * 1024 * 1024)} - ''} - } + service indexer-worker { + ${lib.optionalString (cfg.fullTextSearch.memoryLimit != null) '' + vsz_limit = ${toString (cfg.fullTextSearch.memoryLimit * 1024 * 1024)} + ''} + } - lda_mailbox_autosubscribe = yes - lda_mailbox_autocreate = yes - ''; - }; - - systemd.services.dovecot2 = { - preStart = - '' - ${genPasswdScript} - '' - + (lib.optionalString cfg.ldap.enable setPwdInLdapConfFile); - }; - - systemd.services.postfix.restartTriggers = [ - genPasswdScript - ] ++ (lib.optional cfg.ldap.enable [ setPwdInLdapConfFile ]); + lda_mailbox_autosubscribe = yes + lda_mailbox_autocreate = yes + ''; }; + + systemd.services.dovecot2 = { + preStart = + '' + ${genPasswdScript} + '' + + (lib.optionalString cfg.ldap.enable setPwdInLdapConfFile); + }; + + systemd.services.postfix.restartTriggers = [ + genPasswdScript + ] ++ (lib.optional cfg.ldap.enable [ setPwdInLdapConfFile ]); + }; } diff --git a/mail-server/environment.nix b/mail-server/environment.nix index b853211..462cb05 100644 --- a/mail-server/environment.nix +++ b/mail-server/environment.nix @@ -25,17 +25,15 @@ let cfg = config.mailserver; in { - config = - with cfg; - lib.mkIf enable { - environment.systemPackages = - with pkgs; - [ - dovecot - openssh - postfix - rspamd - ] - ++ (if certificateScheme == "selfsigned" then [ openssl ] else [ ]); - }; + config = lib.mkIf cfg.enable { + environment.systemPackages = + with pkgs; + [ + dovecot + openssh + postfix + rspamd + ] + ++ (if cfg.certificateScheme == "selfsigned" then [ openssl ] else [ ]); + }; } diff --git a/mail-server/networking.nix b/mail-server/networking.nix index 587a8ae..f560ec0 100644 --- a/mail-server/networking.nix +++ b/mail-server/networking.nix @@ -20,21 +20,19 @@ let cfg = config.mailserver; in { - config = - with cfg; - lib.mkIf (enable && openFirewall) { + config = lib.mkIf (cfg.enable && cfg.openFirewall) { - networking.firewall = { - allowedTCPPorts = - [ 25 ] - ++ lib.optional enableSubmission 587 - ++ lib.optional enableSubmissionSsl 465 - ++ lib.optional enableImap 143 - ++ lib.optional enableImapSsl 993 - ++ lib.optional enablePop3 110 - ++ lib.optional enablePop3Ssl 995 - ++ lib.optional enableManageSieve 4190 - ++ lib.optional (certificateScheme == "acme-nginx") 80; - }; + networking.firewall = { + allowedTCPPorts = + [ 25 ] + ++ lib.optional cfg.enableSubmission 587 + ++ lib.optional cfg.enableSubmissionSsl 465 + ++ lib.optional cfg.enableImap 143 + ++ lib.optional cfg.enableImapSsl 993 + ++ lib.optional cfg.enablePop3 110 + ++ lib.optional cfg.enablePop3Ssl 995 + ++ lib.optional cfg.enableManageSieve 4190 + ++ lib.optional (cfg.certificateScheme == "acme-nginx") 80; }; + }; } diff --git a/mail-server/postfix.nix b/mail-server/postfix.nix index 4237efc..680077d 100644 --- a/mail-server/postfix.nix +++ b/mail-server/postfix.nix @@ -233,183 +233,181 @@ let }; in { - config = - with cfg; - lib.mkIf enable { + config = lib.mkIf cfg.enable { - systemd.services.postfix-setup = lib.mkIf cfg.ldap.enable { - preStart = '' - ${appendPwdInVirtualMailboxMap} - ${appendPwdInSenderLoginMap} - ''; - restartTriggers = [ - appendPwdInVirtualMailboxMap - appendPwdInSenderLoginMap + systemd.services.postfix-setup = lib.mkIf cfg.ldap.enable { + preStart = '' + ${appendPwdInVirtualMailboxMap} + ${appendPwdInSenderLoginMap} + ''; + restartTriggers = [ + appendPwdInVirtualMailboxMap + appendPwdInSenderLoginMap + ]; + }; + + services.postfix = { + enable = true; + hostname = "${cfg.sendingFqdn}"; + networksStyle = "host"; + mapFiles."valias" = valiases_file; + mapFiles."regex_valias" = regex_valiases_file; + mapFiles."vaccounts" = vaccounts_file; + mapFiles."regex_vaccounts" = regex_vaccounts_file; + mapFiles."denied_recipients" = denied_recipients_file; + mapFiles."reject_senders" = reject_senders_file; + mapFiles."reject_recipients" = reject_recipients_file; + enableSubmission = cfg.enableSubmission; + enableSubmissions = cfg.enableSubmissionSsl; + virtual = lookupTableToString (mergeLookupTables [ + all_valiases_postfix + catchAllPostfix + forwards + ]); + + config = { + smtpd_tls_chain_files = [ + "${keyPath}" + "${certificatePath}" ]; + + # Extra Config + mydestination = ""; + recipient_delimiter = cfg.recipientDelimiter; + smtpd_banner = "${cfg.fqdn} ESMTP NO UCE"; + disable_vrfy_command = true; + message_size_limit = toString cfg.messageSizeLimit; + + # virtual mail system + virtual_uid_maps = "static:5000"; + virtual_gid_maps = "static:5000"; + virtual_mailbox_base = cfg.mailDirectory; + virtual_mailbox_domains = vhosts_file; + virtual_mailbox_maps = + [ + (mappedFile "valias") + ] + ++ lib.optionals cfg.ldap.enable [ + "ldap:${ldapVirtualMailboxMapFile}" + ] + ++ lib.optionals (regex_valiases_postfix != { }) [ + (mappedRegexFile "regex_valias") + ]; + virtual_alias_maps = lib.mkAfter ( + lib.optionals (regex_valiases_postfix != { }) [ + (mappedRegexFile "regex_valias") + ] + ); + virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp"; + # Avoid leakage of X-Original-To, X-Delivered-To headers between recipients + lmtp_destination_recipient_limit = "1"; + + # sasl with dovecot + smtpd_sasl_type = "dovecot"; + smtpd_sasl_path = "/run/dovecot2/auth"; + smtpd_sasl_auth_enable = true; + smtpd_relay_restrictions = [ + "permit_mynetworks" + "permit_sasl_authenticated" + "reject_unauth_destination" + ]; + + # reject selected senders + smtpd_sender_restrictions = [ + "check_sender_access ${mappedFile "reject_senders"}" + ]; + + smtpd_recipient_restrictions = [ + # reject selected recipients + "check_recipient_access ${mappedFile "denied_recipients"}" + "check_recipient_access ${mappedFile "reject_recipients"}" + # quota checking + "check_policy_service unix:/run/dovecot2/quota-status" + ]; + + # TLS for incoming mail is optional + smtpd_tls_security_level = "may"; + + # But required for authentication attempts + smtpd_tls_auth_only = true; + + # TLS versions supported for the SMTP server + smtpd_tls_protocols = ">=TLSv1.2"; + smtpd_tls_mandatory_protocols = ">=TLSv1.2"; + + # Require ciphersuites that OpenSSL classifies as "High" + smtpd_tls_ciphers = "high"; + smtpd_tls_mandatory_ciphers = "high"; + + # Exclude cipher suites with undesirable properties + smtpd_tls_exclude_ciphers = "eNULL, aNULL"; + smtpd_tls_mandatory_exclude_ciphers = "eNULL, aNULL"; + + # Opportunistic DANE support when delivering mail to other servers + # https://www.postfix.org/postconf.5.html#smtp_tls_security_level + smtp_dns_support_level = "dnssec"; + smtp_tls_security_level = "dane"; + + # TLS versions supported for the SMTP client + smtp_tls_protocols = ">=TLSv1.2"; + smtp_tls_mandatory_protocols = ">=TLSv1.2"; + + # Require ciphersuites that OpenSSL classifies as "High" + smtp_tls_ciphers = "high"; + smtp_tls_mandatory_ciphers = "high"; + + # Exclude ciphersuites with undesirable properties + smtp_tls_exclude_ciphers = "eNULL, aNULL"; + smtp_tls_mandatory_exclude_ciphers = "eNULL, aNULL"; + + # Restrict and prioritize the following curves in the given order + # Excludes curves that have no widespread support, so we don't bloat the handshake needlessly. + # https://www.postfix.org/postconf.5.html#tls_eecdh_auto_curves + # https://ssl-config.mozilla.org/#server=postfix&version=3.10&config=intermediate&openssl=3.4.1&guideline=5.7 + tls_eecdh_auto_curves = [ + "X25519" + "prime256v1" + "secp384r1" + ]; + + # Disable FFDHE on TLSv1.3 because it is slower than elliptic curves + # https://www.postfix.org/postconf.5.html#tls_ffdhe_auto_groups + tls_ffdhe_auto_groups = [ ]; + + # As long as all cipher suites are considered safe, let the client use its preferred cipher + tls_preempt_cipherlist = false; + + # Log only a summary message on TLS handshake completion + smtp_tls_loglevel = "1"; + smtpd_tls_loglevel = "1"; + + smtpd_milters = smtpdMilters; + non_smtpd_milters = lib.mkIf cfg.dkimSigning [ "unix:/run/rspamd/rspamd-milter.sock" ]; + milter_protocol = "6"; + milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_authen}"; }; - services.postfix = { - enable = true; - hostname = "${sendingFqdn}"; - networksStyle = "host"; - mapFiles."valias" = valiases_file; - mapFiles."regex_valias" = regex_valiases_file; - mapFiles."vaccounts" = vaccounts_file; - mapFiles."regex_vaccounts" = regex_vaccounts_file; - mapFiles."denied_recipients" = denied_recipients_file; - mapFiles."reject_senders" = reject_senders_file; - mapFiles."reject_recipients" = reject_recipients_file; - enableSubmission = cfg.enableSubmission; - enableSubmissions = cfg.enableSubmissionSsl; - virtual = lookupTableToString (mergeLookupTables [ - all_valiases_postfix - catchAllPostfix - forwards - ]); + submissionOptions = submissionOptions; + submissionsOptions = submissionOptions; - config = { - smtpd_tls_chain_files = [ - "${keyPath}" - "${certificatePath}" - ]; - - # Extra Config - mydestination = ""; - recipient_delimiter = cfg.recipientDelimiter; - smtpd_banner = "${fqdn} ESMTP NO UCE"; - disable_vrfy_command = true; - message_size_limit = toString cfg.messageSizeLimit; - - # virtual mail system - virtual_uid_maps = "static:5000"; - virtual_gid_maps = "static:5000"; - virtual_mailbox_base = mailDirectory; - virtual_mailbox_domains = vhosts_file; - virtual_mailbox_maps = - [ - (mappedFile "valias") - ] - ++ lib.optionals cfg.ldap.enable [ - "ldap:${ldapVirtualMailboxMapFile}" - ] - ++ lib.optionals (regex_valiases_postfix != { }) [ - (mappedRegexFile "regex_valias") - ]; - virtual_alias_maps = lib.mkAfter ( - lib.optionals (regex_valiases_postfix != { }) [ - (mappedRegexFile "regex_valias") - ] - ); - virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp"; - # Avoid leakage of X-Original-To, X-Delivered-To headers between recipients - lmtp_destination_recipient_limit = "1"; - - # sasl with dovecot - smtpd_sasl_type = "dovecot"; - smtpd_sasl_path = "/run/dovecot2/auth"; - smtpd_sasl_auth_enable = true; - smtpd_relay_restrictions = [ - "permit_mynetworks" - "permit_sasl_authenticated" - "reject_unauth_destination" - ]; - - # reject selected senders - smtpd_sender_restrictions = [ - "check_sender_access ${mappedFile "reject_senders"}" - ]; - - smtpd_recipient_restrictions = [ - # reject selected recipients - "check_recipient_access ${mappedFile "denied_recipients"}" - "check_recipient_access ${mappedFile "reject_recipients"}" - # quota checking - "check_policy_service unix:/run/dovecot2/quota-status" - ]; - - # TLS for incoming mail is optional - smtpd_tls_security_level = "may"; - - # But required for authentication attempts - smtpd_tls_auth_only = true; - - # TLS versions supported for the SMTP server - smtpd_tls_protocols = ">=TLSv1.2"; - smtpd_tls_mandatory_protocols = ">=TLSv1.2"; - - # Require ciphersuites that OpenSSL classifies as "High" - smtpd_tls_ciphers = "high"; - smtpd_tls_mandatory_ciphers = "high"; - - # Exclude cipher suites with undesirable properties - smtpd_tls_exclude_ciphers = "eNULL, aNULL"; - smtpd_tls_mandatory_exclude_ciphers = "eNULL, aNULL"; - - # Opportunistic DANE support when delivering mail to other servers - # https://www.postfix.org/postconf.5.html#smtp_tls_security_level - smtp_dns_support_level = "dnssec"; - smtp_tls_security_level = "dane"; - - # TLS versions supported for the SMTP client - smtp_tls_protocols = ">=TLSv1.2"; - smtp_tls_mandatory_protocols = ">=TLSv1.2"; - - # Require ciphersuites that OpenSSL classifies as "High" - smtp_tls_ciphers = "high"; - smtp_tls_mandatory_ciphers = "high"; - - # Exclude ciphersuites with undesirable properties - smtp_tls_exclude_ciphers = "eNULL, aNULL"; - smtp_tls_mandatory_exclude_ciphers = "eNULL, aNULL"; - - # Restrict and prioritize the following curves in the given order - # Excludes curves that have no widespread support, so we don't bloat the handshake needlessly. - # https://www.postfix.org/postconf.5.html#tls_eecdh_auto_curves - # https://ssl-config.mozilla.org/#server=postfix&version=3.10&config=intermediate&openssl=3.4.1&guideline=5.7 - tls_eecdh_auto_curves = [ - "X25519" - "prime256v1" - "secp384r1" - ]; - - # Disable FFDHE on TLSv1.3 because it is slower than elliptic curves - # https://www.postfix.org/postconf.5.html#tls_ffdhe_auto_groups - tls_ffdhe_auto_groups = [ ]; - - # As long as all cipher suites are considered safe, let the client use its preferred cipher - tls_preempt_cipherlist = false; - - # Log only a summary message on TLS handshake completion - smtp_tls_loglevel = "1"; - smtpd_tls_loglevel = "1"; - - smtpd_milters = smtpdMilters; - non_smtpd_milters = lib.mkIf cfg.dkimSigning [ "unix:/run/rspamd/rspamd-milter.sock" ]; - milter_protocol = "6"; - milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_authen}"; + masterConfig = { + "lmtp" = { + # Add headers when delivering, see http://www.postfix.org/smtp.8.html + # D => Delivered-To, O => X-Original-To, R => Return-Path + args = [ "flags=O" ]; }; - - submissionOptions = submissionOptions; - submissionsOptions = submissionOptions; - - masterConfig = { - "lmtp" = { - # Add headers when delivering, see http://www.postfix.org/smtp.8.html - # D => Delivered-To, O => X-Original-To, R => Return-Path - args = [ "flags=O" ]; - }; - "submission-header-cleanup" = { - type = "unix"; - private = false; - chroot = false; - maxproc = 0; - command = "cleanup"; - args = [ - "-o" - "header_checks=pcre:${submissionHeaderCleanupRules}" - ]; - }; + "submission-header-cleanup" = { + type = "unix"; + private = false; + chroot = false; + maxproc = 0; + command = "cleanup"; + args = [ + "-o" + "header_checks=pcre:${submissionHeaderCleanupRules}" + ]; }; }; }; + }; } diff --git a/mail-server/rspamd.nix b/mail-server/rspamd.nix index a4fcdce..7ed2a0e 100644 --- a/mail-server/rspamd.nix +++ b/mail-server/rspamd.nix @@ -52,223 +52,221 @@ let ''; in { - config = - with cfg; - lib.mkIf enable { - environment.systemPackages = lib.mkBefore [ - (pkgs.runCommand "rspamc-wrapped" - { - nativeBuildInputs = with pkgs; [ makeWrapper ]; - } - '' - makeWrapper ${pkgs.rspamd}/bin/rspamc $out/bin/rspamc \ - --add-flags "-h /run/rspamd/worker-controller.sock" - '' - ) - ]; + config = lib.mkIf cfg.enable { + environment.systemPackages = lib.mkBefore [ + (pkgs.runCommand "rspamc-wrapped" + { + nativeBuildInputs = with pkgs; [ makeWrapper ]; + } + '' + makeWrapper ${pkgs.rspamd}/bin/rspamc $out/bin/rspamc \ + --add-flags "-h /run/rspamd/worker-controller.sock" + '' + ) + ]; - services.rspamd = { - enable = true; - inherit debug; - locals = { - "milter_headers.conf" = { - text = '' - extended_spam_headers = true; - ''; - }; - "redis.conf" = { - text = - '' - servers = "${ - if cfg.redis.port == null then - cfg.redis.address - else - "${cfg.redis.address}:${toString cfg.redis.port}" - }"; - '' - + (lib.optionalString (cfg.redis.password != null) '' - password = "${cfg.redis.password}"; - ''); - }; - "classifier-bayes.conf" = { - text = '' - cache { - backend = "redis"; - } - ''; - }; - "antivirus.conf" = lib.mkIf cfg.virusScanning { - text = '' - clamav { - action = "reject"; - symbol = "CLAM_VIRUS"; - type = "clamav"; - log_clean = true; - servers = "/run/clamav/clamd.ctl"; - scan_mime_parts = false; # scan mail as a whole unit, not parts. seems to be needed to work at all - } - ''; - }; - "dkim_signing.conf" = { - text = '' - enabled = ${lib.boolToString cfg.dkimSigning}; - path = "${cfg.dkimKeyDirectory}/$domain.$selector.key"; - selector = "${cfg.dkimSelector}"; - # Allow for usernames w/o domain part - allow_username_mismatch = true - ''; - }; - "dmarc.conf" = { - text = '' - ${lib.optionalString cfg.dmarcReporting.enable '' - reporting { - enabled = true; - email = "${cfg.dmarcReporting.email}"; - domain = "${cfg.dmarcReporting.domain}"; - org_name = "${cfg.dmarcReporting.organizationName}"; - from_name = "${cfg.dmarcReporting.fromName}"; - msgid_from = "${cfg.dmarcReporting.domain}"; - ${lib.optionalString (cfg.dmarcReporting.excludeDomains != [ ]) '' - exclude_domains = ${builtins.toJSON cfg.dmarcReporting.excludeDomains}; - ''} - }''} - ''; - }; + services.rspamd = { + enable = true; + inherit (cfg) debug; + locals = { + "milter_headers.conf" = { + text = '' + extended_spam_headers = true; + ''; }; - - workers.rspamd_proxy = { - type = "rspamd_proxy"; - bindSockets = [ - { - socket = "/run/rspamd/rspamd-milter.sock"; - mode = "0664"; - } - ]; - count = 1; # Do not spawn too many processes of this type - extraConfig = '' - milter = yes; # Enable milter mode - timeout = 120s; # Needed for Milter usually - - upstream "local" { - default = yes; # Self-scan upstreams are always default - self_scan = yes; # Enable self-scan + "redis.conf" = { + text = + '' + servers = "${ + if cfg.redis.port == null then + cfg.redis.address + else + "${cfg.redis.address}:${toString cfg.redis.port}" + }"; + '' + + (lib.optionalString (cfg.redis.password != null) '' + password = "${cfg.redis.password}"; + ''); + }; + "classifier-bayes.conf" = { + text = '' + cache { + backend = "redis"; } ''; }; - workers.controller = { - type = "controller"; - count = 1; - bindSockets = [ - { - socket = "/run/rspamd/worker-controller.sock"; - mode = "0666"; + "antivirus.conf" = lib.mkIf cfg.virusScanning { + text = '' + clamav { + action = "reject"; + symbol = "CLAM_VIRUS"; + type = "clamav"; + log_clean = true; + servers = "/run/clamav/clamd.ctl"; + scan_mime_parts = false; # scan mail as a whole unit, not parts. seems to be needed to work at all } - ]; - includes = [ ]; - extraConfig = '' - static_dir = "''${WWWDIR}"; # Serve the web UI static assets ''; }; - - }; - - services.redis.servers.rspamd.enable = lib.mkDefault true; - - systemd.tmpfiles.settings."10-rspamd.conf" = { - "${cfg.dkimKeyDirectory}" = { - d = { - # Create /var/dkim owned by rspamd user/group - user = rspamdUser; - group = rspamdGroup; - }; - Z = { - # Recursively adjust permissions in /var/dkim - user = rspamdUser; - group = rspamdGroup; - }; + "dkim_signing.conf" = { + text = '' + enabled = ${lib.boolToString cfg.dkimSigning}; + path = "${cfg.dkimKeyDirectory}/$domain.$selector.key"; + selector = "${cfg.dkimSelector}"; + # Allow for usernames w/o domain part + allow_username_mismatch = true + ''; + }; + "dmarc.conf" = { + text = '' + ${lib.optionalString cfg.dmarcReporting.enable '' + reporting { + enabled = true; + email = "${cfg.dmarcReporting.email}"; + domain = "${cfg.dmarcReporting.domain}"; + org_name = "${cfg.dmarcReporting.organizationName}"; + from_name = "${cfg.dmarcReporting.fromName}"; + msgid_from = "${cfg.dmarcReporting.domain}"; + ${lib.optionalString (cfg.dmarcReporting.excludeDomains != [ ]) '' + exclude_domains = ${builtins.toJSON cfg.dmarcReporting.excludeDomains}; + ''} + }''} + ''; }; }; - systemd.services.rspamd = { - requires = [ "redis-rspamd.service" ] ++ (lib.optional cfg.virusScanning "clamav-daemon.service"); - after = [ "redis-rspamd.service" ] ++ (lib.optional cfg.virusScanning "clamav-daemon.service"); - serviceConfig = lib.mkMerge [ + workers.rspamd_proxy = { + type = "rspamd_proxy"; + bindSockets = [ { - SupplementaryGroups = [ config.services.redis.servers.rspamd.group ]; + socket = "/run/rspamd/rspamd-milter.sock"; + mode = "0664"; } - (lib.optionalAttrs cfg.dkimSigning { - ExecStartPre = map createDkimKeypair cfg.domains; - ReadWritePaths = [ cfg.dkimKeyDirectory ]; - }) ]; - }; + count = 1; # Do not spawn too many processes of this type + extraConfig = '' + milter = yes; # Enable milter mode + timeout = 120s; # Needed for Milter usually - systemd.services.rspamd-dmarc-reporter = lib.optionalAttrs cfg.dmarcReporting.enable { - # Explicitly select yesterday's date to work around broken - # default behaviour when called without a date. - # https://github.com/rspamd/rspamd/issues/4062 - script = '' - ${pkgs.rspamd}/bin/rspamadm dmarc_report $(date -d "yesterday" "+%Y%m%d") + upstream "local" { + default = yes; # Self-scan upstreams are always default + self_scan = yes; # Enable self-scan + } ''; - serviceConfig = { - User = "${config.services.rspamd.user}"; - Group = "${config.services.rspamd.group}"; - - AmbientCapabilities = [ ]; - CapabilityBoundingSet = ""; - DevicePolicy = "closed"; - IPAddressAllow = "localhost"; - LockPersonality = true; - NoNewPrivileges = true; - PrivateDevices = true; - PrivateMounts = true; - PrivateTmp = true; - PrivateUsers = true; - ProtectClock = true; - ProtectControlGroups = true; - ProtectHome = true; - ProtectHostname = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectProc = "invisible"; - ProcSubset = "pid"; - ProtectSystem = "strict"; - RemoveIPC = true; - RestrictAddressFamilies = [ - "AF_INET" - "AF_INET6" - ]; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - SystemCallArchitectures = "native"; - SystemCallFilter = [ - "@system-service" - "~@privileged" - ]; - UMask = "0077"; - }; }; - - systemd.timers.rspamd-dmarc-reporter = lib.optionalAttrs cfg.dmarcReporting.enable { - description = "Daily delivery of aggregated DMARC reports"; - wantedBy = [ - "timers.target" + workers.controller = { + type = "controller"; + count = 1; + bindSockets = [ + { + socket = "/run/rspamd/worker-controller.sock"; + mode = "0666"; + } ]; - timerConfig = { - OnCalendar = "daily"; - Persistent = true; - RandomizedDelaySec = 86400; - FixedRandomDelay = true; + includes = [ ]; + extraConfig = '' + static_dir = "''${WWWDIR}"; # Serve the web UI static assets + ''; + }; + + }; + + services.redis.servers.rspamd.enable = lib.mkDefault true; + + systemd.tmpfiles.settings."10-rspamd.conf" = { + "${cfg.dkimKeyDirectory}" = { + d = { + # Create /var/dkim owned by rspamd user/group + user = rspamdUser; + group = rspamdGroup; + }; + Z = { + # Recursively adjust permissions in /var/dkim + user = rspamdUser; + group = rspamdGroup; }; }; - - systemd.services.postfix = { - after = [ rspamdSocket ]; - requires = [ rspamdSocket ]; - }; - - users.extraUsers.${postfixCfg.user}.extraGroups = [ rspamdCfg.group ]; }; + + systemd.services.rspamd = { + requires = [ "redis-rspamd.service" ] ++ (lib.optional cfg.virusScanning "clamav-daemon.service"); + after = [ "redis-rspamd.service" ] ++ (lib.optional cfg.virusScanning "clamav-daemon.service"); + serviceConfig = lib.mkMerge [ + { + SupplementaryGroups = [ config.services.redis.servers.rspamd.group ]; + } + (lib.optionalAttrs cfg.dkimSigning { + ExecStartPre = map createDkimKeypair cfg.domains; + ReadWritePaths = [ cfg.dkimKeyDirectory ]; + }) + ]; + }; + + systemd.services.rspamd-dmarc-reporter = lib.optionalAttrs cfg.dmarcReporting.enable { + # Explicitly select yesterday's date to work around broken + # default behaviour when called without a date. + # https://github.com/rspamd/rspamd/issues/4062 + script = '' + ${pkgs.rspamd}/bin/rspamadm dmarc_report $(date -d "yesterday" "+%Y%m%d") + ''; + serviceConfig = { + User = "${config.services.rspamd.user}"; + Group = "${config.services.rspamd.group}"; + + AmbientCapabilities = [ ]; + CapabilityBoundingSet = ""; + DevicePolicy = "closed"; + IPAddressAllow = "localhost"; + LockPersonality = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateMounts = true; + PrivateTmp = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProcSubset = "pid"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; + UMask = "0077"; + }; + }; + + systemd.timers.rspamd-dmarc-reporter = lib.optionalAttrs cfg.dmarcReporting.enable { + description = "Daily delivery of aggregated DMARC reports"; + wantedBy = [ + "timers.target" + ]; + timerConfig = { + OnCalendar = "daily"; + Persistent = true; + RandomizedDelaySec = 86400; + FixedRandomDelay = true; + }; + }; + + systemd.services.postfix = { + after = [ rspamdSocket ]; + requires = [ rspamdSocket ]; + }; + + users.extraUsers.${postfixCfg.user}.extraGroups = [ rspamdCfg.group ]; + }; } diff --git a/mail-server/systemd.nix b/mail-server/systemd.nix index dd4bd63..8fb0da7 100644 --- a/mail-server/systemd.nix +++ b/mail-server/systemd.nix @@ -32,61 +32,59 @@ let [ "acme-finished-${cfg.fqdn}.target" ]; in { - config = - with cfg; - lib.mkIf enable { - # Create self signed certificate - systemd.services.mailserver-selfsigned-certificate = - lib.mkIf (cfg.certificateScheme == "selfsigned") - { - after = [ "local-fs.target" ]; - script = '' - # Create certificates if they do not exist yet - dir="${cfg.certificateDirectory}" - fqdn="${cfg.fqdn}" - [[ $fqdn == /* ]] && fqdn=$(< "$fqdn") - key="$dir/key-${cfg.fqdn}.pem"; - cert="$dir/cert-${cfg.fqdn}.pem"; + config = lib.mkIf cfg.enable { + # Create self signed certificate + systemd.services.mailserver-selfsigned-certificate = + lib.mkIf (cfg.certificateScheme == "selfsigned") + { + after = [ "local-fs.target" ]; + script = '' + # Create certificates if they do not exist yet + dir="${cfg.certificateDirectory}" + fqdn="${cfg.fqdn}" + [[ $fqdn == /* ]] && fqdn=$(< "$fqdn") + key="$dir/key-${cfg.fqdn}.pem"; + cert="$dir/cert-${cfg.fqdn}.pem"; - if [[ ! -f $key || ! -f $cert ]]; then - mkdir -p "${cfg.certificateDirectory}" - (umask 077; "${pkgs.openssl}/bin/openssl" genrsa -out "$key" 2048) && - "${pkgs.openssl}/bin/openssl" req -new -key "$key" -x509 -subj "/CN=$fqdn" \ - -days 3650 -out "$cert" - fi - ''; - serviceConfig = { - Type = "oneshot"; - PrivateTmp = true; - }; - }; - - # Create maildir folder before dovecot startup - systemd.services.dovecot2 = { - wants = certificatesDeps; - after = certificatesDeps; - preStart = - let - directories = lib.strings.escapeShellArgs ( - [ mailDirectory ] ++ lib.optional (cfg.indexDir != null) cfg.indexDir - ); - in - '' - # Create mail directory and set permissions. See - # . - # Prevent world-readable paths, even temporarily. - umask 007 - mkdir -p ${directories} - chgrp "${vmailGroupName}" ${directories} - chmod 02770 ${directories} + if [[ ! -f $key || ! -f $cert ]]; then + mkdir -p "${cfg.certificateDirectory}" + (umask 077; "${pkgs.openssl}/bin/openssl" genrsa -out "$key" 2048) && + "${pkgs.openssl}/bin/openssl" req -new -key "$key" -x509 -subj "/CN=$fqdn" \ + -days 3650 -out "$cert" + fi ''; - }; + serviceConfig = { + Type = "oneshot"; + PrivateTmp = true; + }; + }; - # Postfix requires dovecot lmtp socket, dovecot auth socket and certificate to work - systemd.services.postfix = { - wants = certificatesDeps; - after = [ "dovecot2.service" ] ++ lib.optional cfg.dkimSigning "rspamd.service" ++ certificatesDeps; - requires = [ "dovecot2.service" ] ++ lib.optional cfg.dkimSigning "rspamd.service"; - }; + # Create maildir folder before dovecot startup + systemd.services.dovecot2 = { + wants = certificatesDeps; + after = certificatesDeps; + preStart = + let + directories = lib.strings.escapeShellArgs ( + [ cfg.mailDirectory ] ++ lib.optional (cfg.indexDir != null) cfg.indexDir + ); + in + '' + # Create mail directory and set permissions. See + # . + # Prevent world-readable paths, even temporarily. + umask 007 + mkdir -p ${directories} + chgrp "${cfg.vmailGroupName}" ${directories} + chmod 02770 ${directories} + ''; }; + + # Postfix requires dovecot lmtp socket, dovecot auth socket and certificate to work + systemd.services.postfix = { + wants = certificatesDeps; + after = [ "dovecot2.service" ] ++ lib.optional cfg.dkimSigning "rspamd.service" ++ certificatesDeps; + requires = [ "dovecot2.service" ] ++ lib.optional cfg.dkimSigning "rspamd.service"; + }; + }; }