rspamd: allow configuring dmarc reporting

Enabling collects DMARC results in Redis and sends out aggregated
reports (RUA) on a daily basis.
This commit is contained in:
Martin Weinelt 2021-10-03 14:31:43 +02:00 committed by lewo
parent 3f0b7a1b5c
commit fe36e7ae0d
5 changed files with 212 additions and 2 deletions

View File

@ -627,6 +627,63 @@ in
''; '';
}; };
dmarcReporting = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Whether to send out aggregated, daily DMARC reports in response to incoming
mail, when the sender domain defines a DMARC policy including the RUA tag.
This is helpful for the mail ecosystem, because it allows third parties to
get notified about SPF/DKIM violations originating from their sender domains.
See https://rspamd.com/doc/modules/dmarc.html#reporting
'';
};
localpart = mkOption {
type = types.str;
default = "dmarc-noreply";
example = "dmarc-report";
description = ''
The local part of the email address used for outgoing DMARC reports.
'';
};
domain = mkOption {
type = types.enum (cfg.domains);
example = "example.com";
description = ''
The domain from which outgoing DMARC reports are served.
'';
};
email = mkOption {
type = types.str;
default = with cfg.dmarcReporting; "${localpart}@${domain}";
example = "dmarc-noreply@example.com";
readOnly = true;
};
organizationName = mkOption {
type = types.str;
example = "ACME Corp.";
description = ''
The name of your organization used in the <literal>org_name</literal> attribute in
DMARC reports.
'';
};
fromName = mkOption {
type = types.str;
default = cfg.dmarcReporting.organizationName;
description = ''
The sender name for DMARC reports. Defaults to the organization name.
'';
};
};
debug = mkOption { debug = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;

View File

@ -19,6 +19,72 @@ to enable this unless you're hacking on nixos-mailserver.
- Default: ``False`` - Default: ``False``
mailserver.dmarcReporting.domain
--------------------------------
The domain from which outgoing DMARC reports are served.
- Type: ``value "example.com" (singular enum)``
mailserver.dmarcReporting.email
-------------------------------
None
- Type: ``string``
- Default: ``dmarc-noreply@example.com``
mailserver.dmarcReporting.enable
--------------------------------
Whether to send out aggregated, daily DMARC reports in response to incoming
mail, when the sender domain defines a DMARC policy including the RUA tag.
This is helpful for the mail ecosystem, because it allows third parties to
get notified about SPF/DKIM violations originating from their sender domains.
See https://rspamd.com/doc/modules/dmarc.html#reporting
- Type: ``boolean``
- Default: ``False``
mailserver.dmarcReporting.fromName
----------------------------------
The sender name for DMARC reports. Defaults to the organization name.
- Type: ``string``
- Default: ``Example Corp``
mailserver.dmarcReporting.localpart
-----------------------------------
The local part of the email address used for outgoing DMARC reports.
- Type: ``string``
- Default: ``dmarc-noreply``
mailserver.dmarcReporting.organizationName
------------------------------------------
The name of your organization used in the <literal>org_name</literal> attribute in
DMARC reports.
- Type: ``string``
mailserver.domains mailserver.domains
------------------ ------------------
@ -489,7 +555,7 @@ For which domains should this account act as a catch all?
Note: Does not allow sending from all addresses of these domains. Note: Does not allow sending from all addresses of these domains.
- Type: ``list of impossible (empty enum)s`` - Type: ``list of value "example.com" (singular enum)s``
- Default: ``[]`` - Default: ``[]``

View File

@ -59,7 +59,16 @@
# don't care about this package but it is part of the # don't care about this package but it is part of the
# NixOS module evaluation) # NixOS module evaluation)
nixpkgs.config.allowBroken = true; nixpkgs.config.allowBroken = true;
mailserver.fqdn = "mx.example.com"; mailserver = {
fqdn = "mx.example.com";
domains = [
"example.com"
];
dmarcReporting = {
organizationName = "Example Corp";
domain = "example.com";
};
};
} }
]; ];

View File

@ -56,6 +56,17 @@ in
# Disable outbound email signing, we use opendkim for this # Disable outbound email signing, we use opendkim for this
enabled = false; enabled = false;
''; }; ''; };
"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 = "dmarc-rua";
}''}
''; };
}; };
overrides = { overrides = {
@ -108,6 +119,64 @@ in
after = [ "redis-rspamd.service" ] ++ (lib.optional cfg.virusScanning "clamav-daemon.service"); after = [ "redis-rspamd.service" ] ++ (lib.optional cfg.virusScanning "clamav-daemon.service");
}; };
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 = { systemd.services.postfix = {
after = [ rspamdSocket ]; after = [ rspamdSocket ];
requires = [ rspamdSocket ]; requires = [ rspamdSocket ];

View File

@ -43,6 +43,11 @@ pkgs.nixosTest {
domains = [ "example.com" "example2.com" ]; domains = [ "example.com" "example2.com" ];
rewriteMessageId = true; rewriteMessageId = true;
dkimKeyBits = 1535; dkimKeyBits = 1535;
dmarcReporting = {
enable = true;
domain = "example.com";
organizationName = "ACME Corp";
};
loginAccounts = { loginAccounts = {
"user1@example.com" = { "user1@example.com" = {
@ -494,6 +499,10 @@ pkgs.nixosTest {
# check that Junk is not indexed # check that Junk is not indexed
server.fail("journalctl -u dovecot2 | grep 'indexer-worker' | grep -i 'JUNK' >&2") server.fail("journalctl -u dovecot2 | grep 'indexer-worker' | grep -i 'JUNK' >&2")
with subtest("dmarc reporting"):
server.systemctl("start rspamd-dmarc-reporter.service")
server.wait_until_succeeds("journalctl -eu rspamd-dmarc-reporter.service -o cat | grep -q 'No reports for '")
with subtest("no warnings or errors"): with subtest("no warnings or errors"):
server.fail("journalctl -u postfix | grep -i error >&2") server.fail("journalctl -u postfix | grep -i error >&2")
server.fail("journalctl -u postfix | grep -i warning >&2") server.fail("journalctl -u postfix | grep -i warning >&2")