diff --git a/README.md b/README.md index 3a1ccde..5c079c6 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ SNM branch corresponding to your NixOS version. * Virus Scanning - [x] Via ClamAV * DKIM Signing - - [x] Via OpenDKIM + - [x] Via Rspamd * User Management - [x] Declarative user management - [x] Declarative password management diff --git a/default.nix b/default.nix index 1828c5f..3f46610 100644 --- a/default.nix +++ b/default.nix @@ -802,6 +802,19 @@ in ''; }; + dkimKeyType = mkOption { + type = types.enum [ "rsa" "ed25519" ]; + default = "rsa"; + description = '' + The key type used for generating DKIM keys. ED25519 was introduced in RFC6376 (2018). + + If you have already deployed a key with a different type than specified + here, then you should use a different selector ({option}`mailserver.dkimSelector`). In order to get + this package to generate a key with the new type, you will either have to + change the selector or delete the old key file. + ''; + }; + dkimKeyBits = mkOption { type = types.int; default = 1024; @@ -815,26 +828,6 @@ in ''; }; - dkimHeaderCanonicalization = mkOption { - type = types.enum ["relaxed" "simple"]; - default = "relaxed"; - description = '' - DKIM canonicalization algorithm for message headers. - - See https://datatracker.ietf.org/doc/html/rfc6376/#section-3.4 for details. - ''; - }; - - dkimBodyCanonicalization = mkOption { - type = types.enum ["relaxed" "simple"]; - default = "relaxed"; - description = '' - DKIM canonicalization algorithm for message bodies. - - See https://datatracker.ietf.org/doc/html/rfc6376/#section-3.4 for details. - ''; - }; - dmarcReporting = { enable = mkOption { type = types.bool; @@ -1299,7 +1292,6 @@ in ./mail-server/networking.nix ./mail-server/systemd.nix ./mail-server/dovecot.nix - ./mail-server/opendkim.nix ./mail-server/postfix.nix ./mail-server/rspamd.nix ./mail-server/nginx.nix @@ -1308,5 +1300,11 @@ in 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" ] '' + DKIM signing has been migrated to Rspamd, which always uses relaxed canonicalization. + '') + (lib.mkRemovedOptionModule [ "mailserver" "dkimBodyCanonicalization" ] '' + DKIM signing has been migrated to Rspamd, which always uses relaxed canonicalization. + '') ]; } diff --git a/docs/release-notes.rst b/docs/release-notes.rst index f1ab80d..556de5f 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -4,6 +4,8 @@ Release Notes NixOS 25.05 ----------- +- OpenDKIM has been removed and DKIM signing is now handled by Rspamd, which only supports ``relaxed`` canoncalizaliaton. + (`merge request `__) - Rspamd now connects to Redis over its Unix Domain Socket by default (`merge request `__) diff --git a/docs/setup-guide.rst b/docs/setup-guide.rst index f359893..5f6f903 100644 --- a/docs/setup-guide.rst +++ b/docs/setup-guide.rst @@ -173,7 +173,7 @@ Note that it can take a while until a DNS entry is propagated. Set ``DKIM`` signature ^^^^^^^^^^^^^^^^^^^^^^ -On your server, the ``opendkim`` systemd service generated a file +On your server, the ``rspamd`` systemd service generated a file containing your DKIM public key in the file ``/var/dkim/example.com.mail.txt``. The content of this file looks like diff --git a/mail-server/environment.nix b/mail-server/environment.nix index e509ea6..b4326a1 100644 --- a/mail-server/environment.nix +++ b/mail-server/environment.nix @@ -22,7 +22,7 @@ in { config = with cfg; lib.mkIf enable { environment.systemPackages = with pkgs; [ - dovecot opendkim openssh postfix rspamd + dovecot openssh postfix rspamd ] ++ (if certificateScheme == "selfsigned" then [ openssl ] else []); }; } diff --git a/mail-server/opendkim.nix b/mail-server/opendkim.nix deleted file mode 100644 index cdb283c..0000000 --- a/mail-server/opendkim.nix +++ /dev/null @@ -1,89 +0,0 @@ -# nixos-mailserver: a simple mail server -# Copyright (C) 2017 Brian Olsen -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see -{ config, lib, pkgs, ... }: - -with lib; - -let - cfg = config.mailserver; - - dkimUser = config.services.opendkim.user; - dkimGroup = config.services.opendkim.group; - - createDomainDkimCert = dom: - let - dkim_key = "${cfg.dkimKeyDirectory}/${dom}.${cfg.dkimSelector}.key"; - dkim_txt = "${cfg.dkimKeyDirectory}/${dom}.${cfg.dkimSelector}.txt"; - in - '' - if [ ! -f "${dkim_key}" ] - then - ${pkgs.opendkim}/bin/opendkim-genkey -s "${cfg.dkimSelector}" \ - -d "${dom}" \ - --bits="${toString cfg.dkimKeyBits}" \ - --directory="${cfg.dkimKeyDirectory}" - mv "${cfg.dkimKeyDirectory}/${cfg.dkimSelector}.private" "${dkim_key}" - mv "${cfg.dkimKeyDirectory}/${cfg.dkimSelector}.txt" "${dkim_txt}" - chmod 644 "${dkim_txt}" - echo "Generated key for domain ${dom} selector ${cfg.dkimSelector}" - fi - ''; - createAllCerts = lib.concatStringsSep "\n" (map createDomainDkimCert cfg.domains); - - keyTable = pkgs.writeText "opendkim-KeyTable" - (lib.concatStringsSep "\n" (lib.flip map cfg.domains - (dom: "${dom} ${dom}:${cfg.dkimSelector}:${cfg.dkimKeyDirectory}/${dom}.${cfg.dkimSelector}.key"))); - signingTable = pkgs.writeText "opendkim-SigningTable" - (lib.concatStringsSep "\n" (lib.flip map cfg.domains (dom: "${dom} ${dom}"))); - - dkim = config.services.opendkim; - args = [ "-f" "-l" ] ++ lib.optionals (dkim.configFile != null) [ "-x" dkim.configFile ]; -in -{ - config = mkIf (cfg.dkimSigning && cfg.enable) { - services.opendkim = { - enable = true; - selector = cfg.dkimSelector; - keyPath = cfg.dkimKeyDirectory; - domains = "csl:${builtins.concatStringsSep "," cfg.domains}"; - configFile = pkgs.writeText "opendkim.conf" ('' - Canonicalization ${cfg.dkimHeaderCanonicalization}/${cfg.dkimBodyCanonicalization} - UMask 0002 - Socket ${dkim.socket} - KeyTable file:${keyTable} - SigningTable file:${signingTable} - '' + (lib.optionalString cfg.debug '' - Syslog yes - SyslogSuccess yes - LogWhy yes - '')); - }; - - users.users = optionalAttrs (config.services.postfix.user == "postfix") { - postfix.extraGroups = [ "${dkimGroup}" ]; - }; - systemd.services.opendkim = { - preStart = lib.mkForce createAllCerts; - serviceConfig = { - ExecStart = lib.mkForce "${pkgs.opendkim}/bin/opendkim ${escapeShellArgs args}"; - PermissionsStartOnly = lib.mkForce false; - }; - }; - systemd.tmpfiles.rules = [ - "d '${cfg.dkimKeyDirectory}' - ${dkimUser} ${dkimGroup} - -" - ]; - }; -} diff --git a/mail-server/postfix.nix b/mail-server/postfix.nix index c0bd2fb..da06111 100644 --- a/mail-server/postfix.nix +++ b/mail-server/postfix.nix @@ -126,9 +126,7 @@ let inetSocket = addr: port: "inet:[${toString port}@${addr}]"; unixSocket = sock: "unix:${sock}"; - smtpdMilters = - (lib.optional cfg.dkimSigning "unix:/run/opendkim/opendkim.sock") - ++ [ "unix:/run/rspamd/rspamd-milter.sock" ]; + smtpdMilters = [ "unix:/run/rspamd/rspamd-milter.sock" ]; policyd-spf = pkgs.writeText "policyd-spf.conf" cfg.policydSPFExtraConfig; @@ -300,9 +298,9 @@ in tls_random_source = "dev:/dev/urandom"; smtpd_milters = smtpdMilters; - non_smtpd_milters = lib.mkIf cfg.dkimSigning ["unix:/run/opendkim/opendkim.sock"]; + 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_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}"; + milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_authen}"; # Fix for https://www.postfix.org/smtp-smuggling.html smtpd_forbid_bare_newline = cfg.smtpdForbidBareNewline; diff --git a/mail-server/rspamd.nix b/mail-server/rspamd.nix index ec919c2..fd94c84 100644 --- a/mail-server/rspamd.nix +++ b/mail-server/rspamd.nix @@ -22,6 +22,26 @@ let postfixCfg = config.services.postfix; rspamdCfg = config.services.rspamd; rspamdSocket = "rspamd.service"; + + rspamdUser = config.services.rspamd.user; + rspamdGroup = config.services.rspamd.group; + + createDkimKeypair = domain: let + privateKey = "${cfg.dkimKeyDirectory}/${domain}.${cfg.dkimSelector}.key"; + publicKey = "${cfg.dkimKeyDirectory}/${domain}.${cfg.dkimSelector}.txt"; + in pkgs.writeShellScript "dkim-keygen-${domain}" '' + if [ ! -f "${privateKey}" ] + then + ${lib.getExe' pkgs.rspamd "rspamadm"} dkim_keygen \ + --domain "${domain}" \ + --selector "${cfg.dkimSelector}" \ + --type "${cfg.dkimKeyType}" \ + --bits ${toString cfg.dkimKeyBits} \ + --privkey "${privateKey}" > "${publicKey}" + chmod 0644 "${publicKey}" + echo "Generated key for domain ${domain} and selector ${cfg.dkimSelector}" + fi + ''; in { config = with cfg; lib.mkIf enable { @@ -66,8 +86,11 @@ in } ''; }; "dkim_signing.conf" = { text = '' - # Disable outbound email signing, we use opendkim for this - enabled = false; + 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 '' @@ -119,10 +142,33 @@ in 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.rspamd = { requires = [ "redis-rspamd.service" ] ++ (lib.optional cfg.virusScanning "clamav-daemon.service"); after = [ "redis-rspamd.service" ] ++ (lib.optional cfg.virusScanning "clamav-daemon.service"); - serviceConfig.SupplementaryGroups = [ config.services.redis.servers.rspamd.group ]; + 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) { diff --git a/mail-server/systemd.nix b/mail-server/systemd.nix index 121abfe..c411441 100644 --- a/mail-server/systemd.nix +++ b/mail-server/systemd.nix @@ -76,10 +76,10 @@ in systemd.services.postfix = { wants = certificatesDeps; after = [ "dovecot2.service" ] - ++ lib.optional cfg.dkimSigning "opendkim.service" + ++ lib.optional cfg.dkimSigning "rspamd.service" ++ certificatesDeps; requires = [ "dovecot2.service" ] - ++ lib.optional cfg.dkimSigning "opendkim.service"; + ++ lib.optional cfg.dkimSigning "rspamd.service"; }; }; }