Merge branch 'srs' into 'master'
Add support for sender rewriting for forwards using postsrsd See merge request simple-nixos-mailserver/nixos-mailserver!431
This commit is contained in:
@@ -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
|
||||
|
||||
22
default.nix
22
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;
|
||||
|
||||
@@ -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
|
||||
==================
|
||||
|
||||
@@ -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
|
||||
|
||||
102
docs/srs.rst
Normal file
102
docs/srs.rst
Normal file
@@ -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=<value>` 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=<really-long-key>``
|
||||
=============================== ===== ==== ========================================
|
||||
|
||||
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.
|
||||
@@ -84,6 +84,7 @@
|
||||
_module.check = false;
|
||||
mailserver = {
|
||||
fqdn = "mx.example.com";
|
||||
systemDomain = "example.com";
|
||||
domains = [
|
||||
"example.com"
|
||||
];
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 {
|
||||
@@ -202,7 +204,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 ];
|
||||
})
|
||||
];
|
||||
|
||||
@@ -27,6 +27,7 @@ groups = [
|
||||
"mailserver.loginAccounts",
|
||||
"mailserver.certificate",
|
||||
"mailserver.dkim",
|
||||
"mailserver.srs",
|
||||
"mailserver.dmarcReporting",
|
||||
"mailserver.fullTextSearch",
|
||||
"mailserver.redis",
|
||||
@@ -90,7 +91,9 @@ def print_option(option):
|
||||
key=option["name"],
|
||||
description=description or "",
|
||||
type=f"- type: {md_literal(option['type'])}",
|
||||
default=render_option_value(option, "default"),
|
||||
default=render_option_value(option, "defaultText")
|
||||
if "defaultText" in option
|
||||
else render_option_value(option, "default"),
|
||||
example=render_option_value(option, "example"),
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user