diff --git a/README.md b/README.md index 79f8cd7..58929a9 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ SNM branch corresponding to your NixOS version. * User Aliases * [x] Regular aliases * [x] Catch all aliases +* Improve the Forwarding Experience + * [x] [Sender Rewriting Scheme](https://en.wikipedia.org/wiki/Sender_Rewriting_Scheme) ### In the future @@ -69,7 +71,6 @@ SNM branch corresponding to your NixOS version. * [ ] Allow passing DKIM signing keys * Improve the Forwarding Experience * [ ] Support [ARC](https://en.wikipedia.org/wiki/Authenticated_Received_Chain) signing with [Rspamd](https://rspamd.com/doc/modules/arc.html) - * [ ] Support [SRS](https://en.wikipedia.org/wiki/Sender_Rewriting_Scheme) with [postsrsd](https://github.com/roehling/postsrsd) * User management * [ ] Allow local and LDAP user to coexist * OpenID Connect diff --git a/default.nix b/default.nix index 3ca92d0..2f681d4 100644 --- a/default.nix +++ b/default.nix @@ -1101,6 +1101,28 @@ in ''; }; + srs = { + enable = mkEnableOption "Sender Rewrite Scheme"; + + domain = mkOption { + type = with types; nullOr str; + default = config.mailserver.systemDomain; + defaultText = literalExpression "config.mailserver.systemDomain"; + example = "srs.example.com"; + description = '' + Mail domain used for ephemeral SRS envelope addresses. + + :::{note} + This domain can only support relaxed SPF alignment. + ::: + + :::{important} + For privacy reasons you should use a dedicated domain when serving multiple unrelated domains. + ::: + ''; + }; + }; + redis = { configureLocally = mkOption { type = types.bool; diff --git a/docs/index.rst b/docs/index.rst index 0536c3c..2cb5339 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,16 +23,22 @@ Welcome to NixOS Mailserver's documentation! .. toctree:: :maxdepth: 1 + :caption: Features + + fts + ldap + srs + +.. toctree:: + :maxdepth: 0 :caption: How-to backup-guide add-radicale add-roundcube rspamd-tuning - fts flakes autodiscovery - ldap Indices and tables ================== diff --git a/docs/release-notes.rst b/docs/release-notes.rst index bd2e24a..b41110d 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -7,6 +7,8 @@ NixOS 25.11 - The ``systemName`` and ``systemDomain`` options have been introduced to have reusable configurations for automated reports (DMARC, TLSRPT). They come with reasonable defaults, but it is suggested to check and change them as needed. +- Support for the `Sender Rewriting Scheme`_ has been added, which allows + forwarding mail without breaking SPF by rewriting the envelope address. - The default key length for new DKIM RSA keys was increased to 2048 bits as recommended in `RFC 8301 3.2`_. We recommend rotating existing keys, as the RFC advises that signatures from @@ -29,6 +31,7 @@ NixOS 25.11 `tlsrpt-reporter`_. They can be enabled with the ``mailserver.tlsrpt.enable`` option. +.. _Sender Rewriting Scheme: srs.html .. _RFC 8301 3.2: https://www.rfc-editor.org/rfc/rfc8301#section-3.2 .. _RFC 8314 3.3: https://www.rfc-editor.org/rfc/rfc8314#section-3.3 .. _RFC 8314 4.1: https://www.rfc-editor.org/rfc/rfc8314#section-4.1 diff --git a/docs/srs.rst b/docs/srs.rst new file mode 100644 index 0000000..0995f92 --- /dev/null +++ b/docs/srs.rst @@ -0,0 +1,102 @@ +Sender Rewriting Scheme +======================= + +The Sender Rewriting Scheme (SRS) allows mail servers to forward emails without +breaking SPF checks. By rewriting the envelope sender to an address within the +forwarder’s domain, SRS ensures that forwarded messages pass SPF validation, +preventing them from being rejected as spoofed or unauthorized. + +How SRS works in practice +~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. ``alice@foo.example`` receives an E-Mail from ``bob@bar.example``. Both the +envelope sender as well as the ``From`` header show ``bob@bar.example``. This +results in strict SPF alignment, because ``bar.example`` is the domain used in +both the ``Return-Path`` and ``FROM`` headers. + +2. ``alice@foo.example`` forwards the mail to ``charlie@moo.example`` and +uses SRS to rewrite the envelope sender to originate from the local SRS domain +(e.g. `SRS0=HHH=TT=bar.example=alice@foo.example`). The ``FROM`` header remains +unchanged. This ensures that the forwarded mail succeeds SPF checks. + +3. The email reaches ``charlie@moo.example``. SPF passes because the sender +domain in the envelope has been rewritten. The mismatch between envelope sender +domain and ``FROM`` domain does however break strict SPF alignment. + +Enabling SRS +~~~~~~~~~~~~ + +In a simple setup just enabling SRS will use your ``mailserver.systemDomain`` +when rewriting the envelope sender domain. + +.. code:: nix + + { + mailserver = { + srs = { + enable = true; + #domain = "srs.example.com"; + }; + }; + }; +.. + +While you can reuse an existing email domain for SRS, it is recommended to +configure a dedicated SRS domain. This is particularly important under the +following conditions: + +* Multiple unrelated mail domains are hosted on the mailserver +* The mail domain requires strict SPF alignment in its DMARC policy + +Required DNS changes +~~~~~~~~~~~~~~~~~~~~ + +.. note:: + In the following example we assume that you want to set up a dedicated SRS + domain. If that is not the case you already have SPF and DKIM set up for the + system domain. If you have a DMARC record on the system domain, make sure it + uses a relaxed SPF alignment policy (``aspf=r``). + +First we set up an MX record. This is so that we can receive and route bounces +that can result from forwards. + +======================== ===== ==== ======== ===================== +Name (Subdomain) TTL Type Priority Value +======================== ===== ==== ======== ===================== +srs.example.com 10800 MX 10 ``mail.example.com`` +======================== ===== ==== ============================== + +Next up is the SPF record on the SRS domain to allow SPF authentication. + +======================== ===== ==== =================== +Name (Subdomain) TTL Type Value +======================== ===== ==== =================== +srs.example.com 10800 TXT ``v=spf1 mx -all`` +======================== ===== ==== =================== + +Then we deploy the DKIM record with the `p=` taken from +``/var/dkim/srs.example.com.mail.txt``, that appears after deploying with SRS +enabled. + +=============================== ===== ==== ======================================== +Name (Subdomain) TTL Type Value +=============================== ===== ==== ======================================== +mail._domainkey.srs.example.com 10800 TXT ``v=DKIM1; k=rsa; p=`` +=============================== ===== ==== ======================================== + +Finally we can tie this together in the DMARC record to require receivers to +verify the requested SPF/DKIM alignment. + +.. note:: + + The SRS domain can only support relaxed SPF alignment due to the envelope + sender and ``FROM`` header mismatch. + +======================== ===== ==== ========================================= +Name (Subdomain) TTL Type Value +======================== ===== ==== ========================================= +_dmarc.srs.example.com 10800 TXT ``v=DMARC1; p=reject; aspf=r; adkim=s;`` +======================== ===== ==== ========================================= + +We can safely configure a ``reject`` policy on the SRS domain, to enforce the +SPF and DKIM alignment as configured above. diff --git a/flake.nix b/flake.nix index 439c3d7..26e1670 100644 --- a/flake.nix +++ b/flake.nix @@ -84,6 +84,7 @@ _module.check = false; mailserver = { fqdn = "mx.example.com"; + systemDomain = "example.com"; domains = [ "example.com" ]; diff --git a/mail-server/postfix.nix b/mail-server/postfix.nix index 0793dae..2d0c56a 100644 --- a/mail-server/postfix.nix +++ b/mail-server/postfix.nix @@ -261,6 +261,24 @@ in configurePostfix = true; }; + # Sender Rewriting Scheme (https://www.libsrs2.net/srs/srs.pdf) + services.postsrsd = { + inherit (cfg.srs) enable; + configurePostfix = true; + settings = { + domains = lib.unique ( + [ + cfg.fqdn + cfg.sendingFqdn + cfg.systemDomain + ] + ++ cfg.domains + ); + separator = "="; + srs-domain = cfg.srs.domain; + }; + }; + systemd.services.postfix-setup = lib.mkIf cfg.ldap.enable { preStart = '' ${appendPwdInVirtualMailboxMap} diff --git a/mail-server/rspamd.nix b/mail-server/rspamd.nix index ab46750..1d0136f 100644 --- a/mail-server/rspamd.nix +++ b/mail-server/rspamd.nix @@ -50,6 +50,8 @@ let echo "Generated key for domain ${domain} and selector ${cfg.dkimSelector}" fi ''; + + dkimDomains = lib.unique (cfg.domains ++ (lib.optionals cfg.srs.enable [ cfg.srs.domain ])); in { config = lib.mkIf cfg.enable { @@ -201,7 +203,7 @@ in SupplementaryGroups = [ config.services.redis.servers.rspamd.group ]; } (lib.optionalAttrs cfg.dkimSigning { - ExecStartPre = map createDkimKeypair cfg.domains; + ExecStartPre = map createDkimKeypair dkimDomains; ReadWritePaths = [ cfg.dkimKeyDirectory ]; }) ]; diff --git a/scripts/generate-options.py b/scripts/generate-options.py index e78e262..b5ec939 100644 --- a/scripts/generate-options.py +++ b/scripts/generate-options.py @@ -27,6 +27,7 @@ groups = [ "mailserver.loginAccounts", "mailserver.certificate", "mailserver.dkim", + "mailserver.srs", "mailserver.dmarcReporting", "mailserver.fullTextSearch", "mailserver.redis",