- Go 98.8%
- Dockerfile 1.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
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. |
||
| .forgejo/workflows | ||
| cmd/cli-dmarc-validate | ||
| internal/validate | ||
| .gitignore | ||
| catalog-info.yaml | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| README.md | ||
| renovate.json | ||
| sample-domains.json | ||
cli-dmarc-validate
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_selectorsis optional; each selector is looked up as<selector>._domainkey.<domain>(TXT or CNAME).spf_testsis optional.ehlodefaults tomail.<domain>.expectis optional ("pass"or"fail"); when omitted, the outcome is reported but not asserted.dnsis optional and overrides-dnsfor this domain only. The rootspfcheck 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 localservice-dmarc-nsinstance (-dns 127.0.0.1:5533, pre- delegation), setdnsto 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.zoneis optional and, when set, is used directly for this domain's per-IPspf-ip:*checks instead of one parsed from a live root SPF TXT lookup. Use this for purely local testing against aservice-dmarc-nsinstance whose zone (e.g."hosted.spf.com.au", matching that instance'sNS_ZONE) the domain's real SPF record either doesn't reference yet or will never be published for at all (a dev-only domain). Thespfroot check is reported asskiprather than attempted in that case.dnsandzoneare 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) |