CLI tool that validates DMARC, SPF, DKIM, MTA-STS, and TLS-RPT DNS configuration, including per-IP SPF checks against the platform's macro-based SPF answers.
  • Go 98.8%
  • Dockerfile 1.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Nyaaan 0bab901069
All checks were successful
Bump Tag on Main / bump-tag (push) Successful in 11s
Go lint / lint (push) Successful in 1m59s
cli-dmarc-validate CI / build-and-push (push) Successful in 1m17s
fix: push tags via a credential-injected remote, not a reconstructed URL
GITHUB_SERVER_URL on this runner is an internal cluster address using
http:// (e.g. http://forgejo-http.forgejo.svc.cluster.local:3000), not
the public https:// hostname -- stripping only "https://" left it
untouched and produced "https://http://...". Instead of guessing at
scheme/host, read the remote URL checkout already resolved (proven to
work, since fetch/clone succeeded) and inject tag-bot credentials into
it directly. Also set persist-credentials: false on checkout so its
own token credentials cannot conflict with ours.
2026-08-28 04:26:40 +10:00
.forgejo/workflows fix: push tags via a credential-injected remote, not a reconstructed URL 2026-08-28 04:26:40 +10:00
cmd/cli-dmarc-validate Initial version of cli-dmarc-validate 2026-08-24 02:29:25 +10:00
internal/validate Initial version of cli-dmarc-validate 2026-08-24 02:29:25 +10:00
.gitignore Initial version of cli-dmarc-validate 2026-08-24 02:29:25 +10:00
catalog-info.yaml Initial version of cli-dmarc-validate 2026-08-24 02:29:25 +10:00
Dockerfile fix: fully-qualify base images in Dockerfile 2026-08-27 18:03:03 +10:00
go.mod Initial version of cli-dmarc-validate 2026-08-24 02:29:25 +10:00
go.sum Initial version of cli-dmarc-validate 2026-08-24 02:29:25 +10:00
README.md docs: add Forgejo Actions status badges to README 2026-08-28 02:28:06 +10:00
renovate.json feat: add renovate.json 2026-08-28 04:06:14 +10:00
sample-domains.json Initial version of cli-dmarc-validate 2026-08-24 02:29:25 +10:00

cli-dmarc-validate

build-and-push lint

A CLI tool that validates a batch of domains' DMARC, SPF, DKIM, MTA-STS, and TLS-RPT DNS configuration, including per-IP SPF checks against the dmarc.ing platform's macro-based SPF answers.

Unlike a plain DNS lookup tool, this exercises the platform's actual SPF mechanism: it expands %{i}._ip.%{h}._ehlo.%{d}._spf.<zone> for each configured sending IP and checks whether the synthesised answer matches the expected pass/fail outcome, the same lookup a receiving mail server performs.

Build

cd cli-dmarc-validate
go build ./cmd/cli-dmarc-validate

Usage

./cli-dmarc-validate -file sample-domains.json -dns 127.0.0.1:5533
# JSON output, useful in CI
./cli-dmarc-validate -file sample-domains.json -dns 127.0.0.1:5533 -format json

Exit code is 0 if every domain's checks pass, 1 if any check reports fail or error, and 2 for a usage/config error (e.g. a malformed input file).

Flags

Flag Default Purpose
-file (required) JSON file describing domains to validate
-dns 127.0.0.1:53 DNS server to query directly (no recursive resolution)
-timeout 5s Per-query timeout
-parallel 8 Domains validated concurrently
-format table table or json

Point -dns at a public resolver to validate what real mail receivers see, or directly at a service-dmarc-ns instance (e.g. 127.0.0.1:5533 in the root docker-compose.yml dev stack) to test before delegation is live.

Input format

{
  "domains": [
    {
      "domain": "example.com",
      "dkim_selectors": ["default", "google"],
      "spf_tests": [
        { "ip": "203.0.113.5", "ehlo": "mail.example.com", "expect": "pass" },
        { "ip": "198.51.100.9", "ehlo": "mail.example.com", "expect": "fail" }
      ]
    },
    {
      "domain": "already-delegated.com",
      "dns": "8.8.8.8:53",
      "spf_tests": [
        { "ip": "203.0.113.5", "expect": "pass" }
      ]
    },
    {
      "domain": "not-published-yet.com",
      "zone": "hosted.spf.com.au",
      "spf_tests": [
        { "ip": "203.0.113.5", "expect": "pass" }
      ]
    }
  ]
}
  • dkim_selectors is optional; each selector is looked up as <selector>._domainkey.<domain> (TXT or CNAME).
  • spf_tests is optional. ehlo defaults to mail.<domain>. expect is optional ("pass" or "fail"); when omitted, the outcome is reported but not asserted.
  • dns is optional and overrides -dns for this domain only. The root spf check reads the domain's real SPF TXT record, which lives on the domain's own DNS, not on the platform NS — so if you're running most domains against a local service-dmarc-ns instance (-dns 127.0.0.1:5533, pre- delegation), set dns to a public resolver (e.g. "8.8.8.8:53") for any domain whose SPF record is already live, so the root check and per-IP macro checks (which depend on it) can actually run instead of getting REFUSED.
  • zone is optional and, when set, is used directly for this domain's per-IP spf-ip:* checks instead of one parsed from a live root SPF TXT lookup. Use this for purely local testing against a service-dmarc-ns instance whose zone (e.g. "hosted.spf.com.au", matching that instance's NS_ZONE) the domain's real SPF record either doesn't reference yet or will never be published for at all (a dev-only domain). The spf root check is reported as skip rather than attempted in that case. dns and zone are independent — set either, both, or neither per domain.

See sample-domains.json for a complete example.

Checks performed per domain

Check Query Notes
DMARC _dmarc.<domain> TXT fails if missing
SPF <domain> TXT fails if missing; warns if it doesn't use the platform's macro include
SPF (per IP) <ip>._ip.<ehlo>._ehlo.<domain>._spf.<zone> TXT zone comes from zone if configured, otherwise from the root SPF record; skipped if neither yields one
DKIM <selector>._domainkey.<domain> TXT/CNAME one check per configured selector; fails if missing
MTA-STS _mta-sts.<domain> TXT, mta-sts.<domain> CNAME warns if missing (optional record)
TLS-RPT _smtp._tls.<domain> TXT warns if missing (optional record)