Gandi LiveDNS rejects the RFC-7505 null-MX value '0 .' ('invalid format for MX
record'), which failed the live apply. No MX + no apex A = no mail delivery, and
SPF -all + DMARC reject still prevent spoofing — so remove Gandi's seeded MX (add
@/MX to absent) rather than declare a null-MX present. Assert now requires an SPF
@/TXT record; tests + Molecule sample updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
55 lines
2 KiB
Python
55 lines
2 KiB
Python
import pathlib
|
|
|
|
import yaml
|
|
|
|
_DATA = (
|
|
pathlib.Path(__file__).resolve().parent.parent
|
|
/ "inventories" / "production" / "group_vars" / "all" / "public_dns.yml"
|
|
)
|
|
|
|
# Gandi auto-seeds these on a fresh .me zone; boma purges them (verified 2026-06-14).
|
|
# Includes the @ MX: Gandi rejects the RFC-7505 null-MX "0 .", so we remove the MX
|
|
# entirely (no MX + no apex A = no mail) rather than declare a null-MX present.
|
|
GANDI_DEFAULTS_ABSENT = {
|
|
("@", "A"), ("@", "MX"), ("www", "CNAME"), ("webmail", "CNAME"),
|
|
("gm1._domainkey", "CNAME"), ("gm2._domainkey", "CNAME"), ("gm3._domainkey", "CNAME"),
|
|
("_imap._tcp", "SRV"), ("_imaps._tcp", "SRV"), ("_pop3._tcp", "SRV"),
|
|
("_pop3s._tcp", "SRV"), ("_submission._tcp", "SRV"),
|
|
}
|
|
|
|
|
|
def _load():
|
|
return yaml.safe_load(_DATA.read_text())
|
|
|
|
|
|
def test_domain_is_wingu():
|
|
assert _load()["public_dns__domain"] == "wingu.me"
|
|
|
|
|
|
def test_present_records_well_formed():
|
|
for r in _load()["public_dns__records"]:
|
|
assert r["record"] and r["type"]
|
|
assert isinstance(r["values"], list) and r["values"]
|
|
|
|
|
|
def test_anti_spoof_baseline_present():
|
|
recs = {(r["record"], r["type"]): r["values"] for r in _load()["public_dns__records"]}
|
|
assert recs[("@", "TXT")] == ['"v=spf1 -all"'] # SPF deny-all
|
|
assert recs[("_dmarc", "TXT")] == ['"v=DMARC1; p=reject;"']
|
|
assert ("@", "MX") not in recs # no MX (Gandi rejects null-MX; removed instead)
|
|
|
|
|
|
def test_gandi_defaults_marked_absent():
|
|
absent = {(r["record"], r["type"]) for r in _load()["public_dns__absent"]}
|
|
assert GANDI_DEFAULTS_ABSENT <= absent
|
|
|
|
|
|
def test_no_record_both_present_and_absent():
|
|
present = {(r["record"], r["type"]) for r in _load()["public_dns__records"]}
|
|
absent = {(r["record"], r["type"]) for r in _load()["public_dns__absent"]}
|
|
assert present.isdisjoint(absent)
|
|
|
|
|
|
def test_no_duplicate_present_records():
|
|
keys = [(r["record"], r["type"]) for r in _load()["public_dns__records"]]
|
|
assert len(keys) == len(set(keys))
|