Skip to content

DNS Hardening Runbook — DMARC enforcement + DNSSEC

Date: 2026-06-25 · Companion to: compliance-roadmap.md (findings CF-3/CF-4/CF-5)

Live state probed 2026-06-25:

  • ordermatic.co — SPF ~all (google + hubspotemail), DMARC p=quarantine, MX Google Workspace, DNSSEC OFF
  • erpunlocked.com — SPF cloudflare, DMARC p=none, MX Cloudflare Email Routing, DNSSEC OFF

Both zones are on Cloudflare. Legend per step:

  • [CF-API] — applicable via the Cloudflare DNS API (TXT record edits)
  • [CF-DASH] — Cloudflare dashboard only (out-of-band)
  • [REGISTRAR] — registrar control panel only (out-of-band)

Roll DMARC forward only after a monitoring window with clean aggregate (rua) reports. Set rua= to a mailbox you actually read before tightening anything.


(a) erpunlocked.com DMARC: none → quarantine → reject

Section titled “(a) erpunlocked.com DMARC: none → quarantine → reject”

a1. Confirm reporting is on, start the clock — [CF-API] _dmarc.erpunlocked.com TXT (keep p=none, ensure rua present):

v=DMARC1; p=none; rua=mailto:dmarc@ordermatic.co; fo=1; adkim=s; aspf=s

Wait 2–4 weeks. Read aggregate reports; confirm every legitimate sender passes SPF and/or DKIM under alignment. Do not proceed while any real sender fails.

a2. none → quarantine — [CF-API]

v=DMARC1; p=quarantine; rua=mailto:dmarc@ordermatic.co; fo=1; adkim=s; aspf=s

Optional ramp: add pct=25 → 50 → 100 across a week if volume is high. Wait 1–2 weeks of clean reports.

a3. quarantine → reject — [CF-API]

v=DMARC1; p=reject; rua=mailto:dmarc@ordermatic.co; fo=1; adkim=s; aspf=s

(b) ordermatic.co DMARC: quarantine → reject

Section titled “(b) ordermatic.co DMARC: quarantine → reject”

b1. Verify reporting, confirm clean — [CF-API] Ensure _dmarc.ordermatic.co TXT has a monitored rua:

v=DMARC1; p=quarantine; rua=mailto:dmarc@ordermatic.co; fo=1

Wait until ≥2 weeks of aggregate reports show no legit sender failing (check Google Workspace, HubSpot, any other SPF-included sender aligns).

b2. quarantine → reject — [CF-API]

v=DMARC1; p=reject; rua=mailto:dmarc@ordermatic.co; fo=1

Per zone: Cloudflare first, then publish DS at the registrar. Never publish DS before Cloudflare is signing — that hard-fails the zone.

c1. Turn on signing in Cloudflare — [CF-DASH] Dashboard → zone → DNS → Settings → Enable DNSSEC. Cloudflare displays the DS record set: Key Tag, Algorithm (13 / ECDSAP256SHA256), Digest Type (2 / SHA-256), Digest. (Zone-setting action — not the DNS-record API.)

c2. Publish DS at the registrar — [REGISTRAR] In the domain registrar’s control panel, add the DS record exactly as Cloudflare shows it (Key Tag, Algorithm 13, Digest Type 2, Digest). Wait for propagation (minutes to a few hours).

c3. Verify — out-of-band check

delv ordermatic.co # expect "fully validated"
delv erpunlocked.com
dig +dnssec DS ordermatic.co

Or https://dnssec-analyzer.verisignlabs.com/ → all green.

Repeat c1–c3 for ordermatic.co and erpunlocked.com independently.


Tighten SPF fail mode only after the matching zone’s DMARC is at p=reject and reports are clean — at that point DMARC already rejects unaligned mail, so -all (hard fail) is low-risk. Verify every legitimate sender is in the include chain first; a missing include + -all hard-bounces real mail.

  • ordermatic.co — [CF-API] TXT ordermatic.co (preserve exact existing includes; flip ~all → -all):
    v=spf1 include:_spf.google.com include:<...hubspotemail.net> -all
  • erpunlocked.com — [CF-API] flip its SPF ~all → -all likewise once its DMARC is at reject.

Ready-to-run commands (real record IDs, verified 2026-06-25)

Section titled “Ready-to-run commands (real record IDs, verified 2026-06-25)”

Live _dmarc record IDs (looked up via the IaC token):

  • erpunlocked.com — zone 78e9ab3c33160d203e67f88fa46e1722, record c8998ea0aee07f9f384d5b4ffa3c080f (currently p=none)
  • ordermatic.co — zone 65d4cad6d15e15d45b92546005bd9bc0, record bb17b4f0505110eef15003117e2ba048 (currently p=quarantine)

Set the token once (never echoed):

Terminal window
export CF_TOKEN="$(op read 'op://ERP-Unlocked/Cloudflare/api_token')"

DMARC — run each step ONLY after a clean monitoring window; do not batch. (rua reporting is already on for both, so the clock is running.)

Terminal window
# erpunlocked.com: p=none -> p=quarantine
curl -X PATCH -H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json" \
"https://api.cloudflare.com/client/v4/zones/78e9ab3c33160d203e67f88fa46e1722/dns_records/c8998ea0aee07f9f384d5b4ffa3c080f" \
--data '{"type":"TXT","name":"_dmarc.erpunlocked.com","content":"v=DMARC1; p=quarantine; rua=mailto:5bed5509ee51485e963fc123525593a2@dmarc-reports.cloudflare.net,mailto:dmarc@ordermatic.co","ttl":1}'
# erpunlocked.com: p=quarantine -> p=reject
curl -X PATCH -H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json" \
"https://api.cloudflare.com/client/v4/zones/78e9ab3c33160d203e67f88fa46e1722/dns_records/c8998ea0aee07f9f384d5b4ffa3c080f" \
--data '{"type":"TXT","name":"_dmarc.erpunlocked.com","content":"v=DMARC1; p=reject; rua=mailto:5bed5509ee51485e963fc123525593a2@dmarc-reports.cloudflare.net,mailto:dmarc@ordermatic.co","ttl":1}'
# ordermatic.co: p=quarantine -> p=reject
curl -X PATCH -H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json" \
"https://api.cloudflare.com/client/v4/zones/65d4cad6d15e15d45b92546005bd9bc0/dns_records/bb17b4f0505110eef15003117e2ba048" \
--data '{"type":"TXT","name":"_dmarc.ordermatic.co","content":"v=DMARC1; p=reject; rua=mailto:a7392566b654427f86ae9d247441bc04@dmarc-reports.cloudflare.net","ttl":1}'

DNSSEC — the IaC token has the permission (GET .../dnssec returned 200, not 403). Enable signing via API, then publish the returned DS at the registrar (manual):

Terminal window
curl -X PATCH -H "Authorization: Bearer $CF_TOKEN" "https://api.cloudflare.com/client/v4/zones/65d4cad6d15e15d45b92546005bd9bc0/dnssec" --data '{"status":"active"}' # ordermatic.co
curl -X PATCH -H "Authorization: Bearer $CF_TOKEN" "https://api.cloudflare.com/client/v4/zones/78e9ab3c33160d203e67f88fa46e1722/dnssec" --data '{"status":"active"}' # erpunlocked.com