diff --git a/README.md b/README.md index 337163b..69d3182 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ can stay up to date with bug fixes and updates. * 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..9d56d07 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 `__) 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..d3b1dfc 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,29 @@ 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.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..46e0e7f 100644 --- a/mail-server/systemd.nix +++ b/mail-server/systemd.nix @@ -75,11 +75,8 @@ in # Postfix requires dovecot lmtp socket, dovecot auth socket and certificate to work systemd.services.postfix = { wants = certificatesDeps; - after = [ "dovecot2.service" ] - ++ lib.optional cfg.dkimSigning "opendkim.service" - ++ certificatesDeps; - requires = [ "dovecot2.service" ] - ++ lib.optional cfg.dkimSigning "opendkim.service"; + after = [ "dovecot2.service" ] ++ certificatesDeps; + requires = [ "dovecot2.service" ]; }; }; } diff --git a/tests/external.nix b/tests/external.nix index 7579b6d..c7e9a0d 100644 --- a/tests/external.nix +++ b/tests/external.nix @@ -402,7 +402,7 @@ pkgs.nixosTest { client.succeed("fetchmail --nosslcertck -v") client.succeed("cat ~/mail/* >&2") # make sure it is dkim signed - client.succeed("grep DKIM ~/mail/*") + client.succeed("grep DKIM-Signature: ~/mail/*") with subtest("aliases"): client.execute("rm ~/mail/*")