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:
parent
3f0b7a1b5c
commit
fe36e7ae0d
57
default.nix
57
default.nix
|
@ -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;
|
||||||
|
|
|
@ -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: ``[]``
|
||||||
|
|
||||||
|
|
||||||
|
|
11
flake.nix
11
flake.nix
|
@ -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";
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -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 ];
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in New Issue