Fix missing SPF result for mail relayed through the wg-box edge #18

Open
opened 2026-07-09 12:55:20 +00:00 by claude-bot · 1 comment
Collaborator

Mailcow's WHITELISTED_FWD_HOST exemption for 172.30.0.1 (added to fix RDNS_NONE/HFILTER_HOSTNAME_UNKNOWN false positives on relayed inbound mail) suppresses rspamd's whole 'policies' rule group, which includes SPF -- so inbound mail via the wg-box edge relay no longer shows an spf= result in Authentication-Results at all, rather than evaluating SPF correctly against the real original sender IP.

Root cause: Postfix's XFORWARD command (sent by the wg-box to mailcow to preserve the real client IP) updates the Received: header trail correctly, but rspamd's SPF module still evaluates against the literal TCP peer (172.30.0.1, the wg-box itself) rather than the XFORWARD-updated address -- confirmed via header inspection during the notify.coppercore.net real-world test (see ccnet-mailcow-vm-runbook and the mail migration plan for full context).

Not a security hole -- DKIM/DMARC (the stronger modern signals) still evaluate and score correctly, and this doesn't create a bypass since WHITELISTED_FWD_HOST intentionally leaves KEEP_SPAM unset (spam filtering stays active). Just an accuracy gap worth closing eventually: find the correct Postfix/rspamd config to have the milter macros rspamd reads for its SPF check reflect the XFORWARD-updated client address, rather than suppressing the check entirely.

Mailcow's WHITELISTED_FWD_HOST exemption for 172.30.0.1 (added to fix RDNS_NONE/HFILTER_HOSTNAME_UNKNOWN false positives on relayed inbound mail) suppresses rspamd's whole 'policies' rule group, which includes SPF -- so inbound mail via the wg-box edge relay no longer shows an spf= result in Authentication-Results at all, rather than evaluating SPF correctly against the real original sender IP. Root cause: Postfix's XFORWARD command (sent by the wg-box to mailcow to preserve the real client IP) updates the Received: header trail correctly, but rspamd's SPF module still evaluates against the literal TCP peer (172.30.0.1, the wg-box itself) rather than the XFORWARD-updated address -- confirmed via header inspection during the notify.coppercore.net real-world test (see ccnet-mailcow-vm-runbook and the mail migration plan for full context). Not a security hole -- DKIM/DMARC (the stronger modern signals) still evaluate and score correctly, and this doesn't create a bypass since WHITELISTED_FWD_HOST intentionally leaves KEEP_SPAM unset (spam filtering stays active). Just an accuracy gap worth closing eventually: find the correct Postfix/rspamd config to have the milter macros rspamd reads for its SPF check reflect the XFORWARD-updated client address, rather than suppressing the check entirely.
Author
Collaborator

Investigated 2026-07-14 — root cause confirmed, real fix identified, still deferred (p:low, not a security issue).

Confirmed live via rspamd logs on ccnet-wv-mailcow (mailcowdockerized-rspamd-mailcow-1): for mail relayed through the wg-box (172.30.0.1), rspamd's task-level ip is 172.30.0.1 and no R_SPF_* symbol is produced at all, e.g.:

ip: 172.30.0.1, from: <adamualhajimodu@gmail.com> ... [WHITELISTED_FWD_HOST(0.00){172.30.0.1;},WL_FWD_HOST(0.00){} ...]

Why the "obvious" fixes are already in place and still don't help:

  • mailcow's Postfix already trusts XFORWARD from 172.30.0.1 (smtpd_authorized_xforward_hosts = 172.30.0.1 in both main.cf and extra.cf)
  • it already re-sends the refreshed {client_addr} macro to rspamd at MAIL FROM stage (milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen})
  • despite both being configured correctly, rspamd fixes the task's ip at the milter CONNECT callback — before XFORWARD is even processed — and never re-derives it from the later MAIL-stage macro refresh. This is an rspamd behavior/limitation, not a config gap on our side.

Why a narrower composite tweak would make things worse, not better: WHITELISTED_FWD_HOST/WL_FWD_HOST in mailcow's stock composites.conf blanket-disables the whole policies symbol group (SPF included) for known forwarders. That's deliberate: since rspamd genuinely can't see the real sender IP at that hop, letting SPF run un-suppressed would evaluate against 172.30.0.1 and produce a false spf=fail for every legitimate sender — strictly worse than the current "no result" state.

The real fix, when we pick this back up: ARC-seal at the wg-box.
The wg-box's own local rspamd (smtpd_milters = inet:127.0.0.1:11332 on ccnet-wireguard-ub-caddy-2gb-ash-1, currently a vanilla/stock install — local.d/ is empty except a greylist override) is the actual first-touch MX listener and does see the true client IP, so its own SPF/DKIM/DMARC evaluation there is already correct. Mailcow's rspamd already validates incoming ARC chains (ARC_SIGNED/ARC_NA symbols observed scoring on real mail today), so the standards-based fix is:

  1. Generate an ARC/DKIM-style keypair on the wg-box (own selector, e.g. relay._domainkey.coppercore.net or similar — not mailcow's existing s=dkim selector).
  2. Publish the selector's DNS TXT record.
  3. Add local.d/arc.conf on the wg-box enabling ARC sealing for mail passing through that relay hop (keyed by trusted network/relay identity, not by the original sender's envelope-from domain — each ARC hop seals as itself, per spec).
  4. Verify mailcow's rspamd surfaces a valid ARC-Authentication-Results carrying the correct spf=/dkim=/dmarc= from the wg-box hop, and (optionally) narrow WL_FWD_HOST to only suppress the native (wrong) policies recompute when there's no valid ARC chain backing it (i.e. add !ARC_ALLOW or equivalent to the composite condition).

This mirrors the pattern mailcow itself already uses for its own outbound DKIM signing — same idea, one hop earlier, on a box with no existing DNS/key material for it yet.

Not picking this up right now (still p:low, still not a security issue — DKIM/DMARC score correctly and KEEP_SPAM stays active either way). Leaving this comment so the investigation doesn't need to be redone next time this is revisited.

**Investigated 2026-07-14 — root cause confirmed, real fix identified, still deferred (p:low, not a security issue).** Confirmed live via rspamd logs on `ccnet-wv-mailcow` (`mailcowdockerized-rspamd-mailcow-1`): for mail relayed through the wg-box (`172.30.0.1`), rspamd's task-level `ip` is `172.30.0.1` and no `R_SPF_*` symbol is produced at all, e.g.: ``` ip: 172.30.0.1, from: <adamualhajimodu@gmail.com> ... [WHITELISTED_FWD_HOST(0.00){172.30.0.1;},WL_FWD_HOST(0.00){} ...] ``` **Why the "obvious" fixes are already in place and still don't help:** - mailcow's Postfix already trusts XFORWARD from `172.30.0.1` (`smtpd_authorized_xforward_hosts = 172.30.0.1` in both `main.cf` and `extra.cf`) - it already re-sends the refreshed `{client_addr}` macro to rspamd at MAIL FROM stage (`milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}`) - despite both being configured correctly, rspamd fixes the task's `ip` at the milter **CONNECT** callback — before XFORWARD is even processed — and never re-derives it from the later MAIL-stage macro refresh. This is an rspamd behavior/limitation, not a config gap on our side. **Why a narrower composite tweak would make things *worse*, not better:** `WHITELISTED_FWD_HOST`/`WL_FWD_HOST` in mailcow's stock `composites.conf` blanket-disables the whole `policies` symbol group (SPF included) for known forwarders. That's deliberate: since rspamd genuinely can't see the real sender IP at that hop, letting SPF run un-suppressed would evaluate against `172.30.0.1` and produce a false `spf=fail` for every legitimate sender — strictly worse than the current "no result" state. **The real fix, when we pick this back up: ARC-seal at the wg-box.** The wg-box's own local rspamd (`smtpd_milters = inet:127.0.0.1:11332` on `ccnet-wireguard-ub-caddy-2gb-ash-1`, currently a vanilla/stock install — `local.d/` is empty except a greylist override) is the actual first-touch MX listener and *does* see the true client IP, so its own SPF/DKIM/DMARC evaluation there is already correct. Mailcow's rspamd already validates incoming ARC chains (`ARC_SIGNED`/`ARC_NA` symbols observed scoring on real mail today), so the standards-based fix is: 1. Generate an ARC/DKIM-style keypair on the wg-box (own selector, e.g. `relay._domainkey.coppercore.net` or similar — not mailcow's existing `s=dkim` selector). 2. Publish the selector's DNS TXT record. 3. Add `local.d/arc.conf` on the wg-box enabling ARC sealing for mail passing through that relay hop (keyed by trusted network/relay identity, not by the original sender's envelope-from domain — each ARC hop seals as itself, per spec). 4. Verify mailcow's rspamd surfaces a valid `ARC-Authentication-Results` carrying the correct `spf=`/`dkim=`/`dmarc=` from the wg-box hop, and (optionally) narrow `WL_FWD_HOST` to only suppress the native (wrong) policies recompute when there's *no* valid ARC chain backing it (i.e. add `!ARC_ALLOW` or equivalent to the composite condition). This mirrors the pattern mailcow itself already uses for its own outbound DKIM signing — same idea, one hop earlier, on a box with no existing DNS/key material for it yet. Not picking this up right now (still `p:low`, still not a security issue — DKIM/DMARC score correctly and `KEEP_SPAM` stays active either way). Leaving this comment so the investigation doesn't need to be redone next time this is revisited.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
copper/ccnet-prod-devops#18
No description provided.