feat(dns): carry a second Google token, and add SPF and DMARC (#911) #913

Merged
coilyco-ops merged 1 commit from ops/911-dns-mail-auth into main 2026-08-25 01:10:59 +00:00
Member

Closes #911 items 1 and 2. DKIM and Bing verification stay blocked on Kai and are not in this branch.

What changed

  • aws_route53_record.txt - for_each values become lists, since Route53 stores every TXT value for one name in a single record set. The apex now carries both google-site-verification tokens plus v=spf1 include:_spf.google.com ~all.
  • aws_route53_record.dmarc - new, _dmarc.coilysiren.me, TTL 300, v=DMARC1; p=none; rua=mailto:kai@coilysiren.me.
  • _atproto and _discord are byte-identical. The plan shows no change on either.
  • README and the import helper follow: mail-authentication section, _dmarc in ROUTE53_RECORDS, and the stale "17 records" count replaced by the live counts output.

Verification so far

Before-state confirmed against 8.8.8.8: apex TXT carried one token only, _dmarc empty, google._domainkey empty, MX is 1 smtp.google.com..

terraform validate passes, terraform fmt -check is clean, pre-commit run --all-files is green.

Targeted plan on the two changed addresses:

Plan: 1 to add, 1 to change, 0 to destroy.

Not applied yet

The live apply is blocked pending operator approval. The exact command, which must keep -target:

terraform -chdir=terraform/aws-inventory plan \
  -target='aws_route53_record.txt["@"]' \
  -target='aws_route53_record.dmarc' \
  -out=aws-inv.tfplan
terraform -chdir=terraform/aws-inventory apply aws-inv.tfplan

Then verify against a public resolver rather than the apply output:

dig +short @8.8.8.8 coilysiren.me TXT
dig +short @8.8.8.8 _dmarc.coilysiren.me TXT
dig +short @8.8.8.8 _atproto.coilysiren.me TXT
dig +short @8.8.8.8 _discord.coilysiren.me TXT

Do not run an untargeted apply

The module carries pre-existing drift this branch did not introduce and does not fix. An untargeted just terraform-aws-inventory apply plans 2 to add, 1 to change, 3 to destroy:

  • home_a["eco-jobs-tracker"], home_a["eco-mcp"], home_a["grafana"] would be destroyed. All three resolve live to the home IP right now. They were dropped from the home_a map during the ExternalDNS migration (#339) without a matching terraform state rm, and no extdns-a-* ownership TXT exists for any of them, so ExternalDNS never adopted them either.
  • comfyui would be created. The CNAME is in code but absent from state, and comfyui.coilysiren.me does not resolve today.

Filed separately so it gets a deliberate operator decision rather than riding along with a DNS mail change.

🤖 Generated with Claude Code

Closes #911 items 1 and 2. DKIM and Bing verification stay blocked on Kai and are not in this branch. ## What changed * `aws_route53_record.txt` - `for_each` values become lists, since Route53 stores every TXT value for one name in a single record set. The apex now carries both `google-site-verification` tokens plus `v=spf1 include:_spf.google.com ~all`. * `aws_route53_record.dmarc` - new, `_dmarc.coilysiren.me`, TTL 300, `v=DMARC1; p=none; rua=mailto:kai@coilysiren.me`. * `_atproto` and `_discord` are byte-identical. The plan shows no change on either. * README and the import helper follow: mail-authentication section, `_dmarc` in `ROUTE53_RECORDS`, and the stale "17 records" count replaced by the live `counts` output. ## Verification so far Before-state confirmed against `8.8.8.8`: apex TXT carried one token only, `_dmarc` empty, `google._domainkey` empty, `MX` is `1 smtp.google.com.`. `terraform validate` passes, `terraform fmt -check` is clean, `pre-commit run --all-files` is green. Targeted plan on the two changed addresses: ``` Plan: 1 to add, 1 to change, 0 to destroy. ``` ## Not applied yet The live apply is blocked pending operator approval. The exact command, which must keep `-target`: ``` terraform -chdir=terraform/aws-inventory plan \ -target='aws_route53_record.txt["@"]' \ -target='aws_route53_record.dmarc' \ -out=aws-inv.tfplan terraform -chdir=terraform/aws-inventory apply aws-inv.tfplan ``` Then verify against a public resolver rather than the apply output: ``` dig +short @8.8.8.8 coilysiren.me TXT dig +short @8.8.8.8 _dmarc.coilysiren.me TXT dig +short @8.8.8.8 _atproto.coilysiren.me TXT dig +short @8.8.8.8 _discord.coilysiren.me TXT ``` ## Do not run an untargeted apply The module carries pre-existing drift this branch did not introduce and does not fix. An untargeted `just terraform-aws-inventory apply` plans `2 to add, 1 to change, 3 to destroy`: * `home_a["eco-jobs-tracker"]`, `home_a["eco-mcp"]`, `home_a["grafana"]` would be **destroyed**. All three resolve live to the home IP right now. They were dropped from the `home_a` map during the ExternalDNS migration (#339) without a matching `terraform state rm`, and no `extdns-a-*` ownership TXT exists for any of them, so ExternalDNS never adopted them either. * `comfyui` would be created. The CNAME is in code but absent from state, and `comfyui.coilysiren.me` does not resolve today. Filed separately so it gets a deliberate operator decision rather than riding along with a DNS mail change. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(dns): carry a second Google token, and add SPF and DMARC (#911)
All checks were successful
CI / lint (pull_request) Successful in 3m51s
TruffleHog / Scan for secrets (pull_request) Successful in 4s
58dccdabd2
coilysiren.me routes mail through Google Workspace with no SPF, no DKIM,
and no DMARC, so the domain is trivially spoofable and its mail is easy
to filter as spam. Add SPF and a monitor-only DMARC policy, and carry the
newly issued Google site-verification token alongside the existing one.

Route53 stores every TXT value for one name in a single record set, so
the apex for_each value becomes a list. _atproto and _discord are
byte-identical - both are load-bearing, for Bluesky handle resolution and
Discord domain ownership.

Both verification tokens stay. Search Console and Workspace issue the
same format, DNS cannot say which product owns which token, and dropping
the wrong one can un-verify the domain and suspend Workspace mail.

Softfail ~all and p=none are deliberate. Harden either only after DMARC
rua reports confirm SPF and DKIM pass on real mail. DKIM stays absent -
its public key exists only in the Google Workspace admin console.

Not yet applied. A targeted plan is clean at 1 to add, 1 to change, 0 to
destroy, but the apply is blocked pending operator approval. An
untargeted apply must not be run: the module carries unrelated
pre-existing drift, where three home_a records dropped from the map
during the ExternalDNS migration are still in tfstate and would be
deleted from live DNS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Agent-Role: sysadmin
Sign in to join this conversation.
No reviewers
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
coilyco-flight-deck/infrastructure!913
No description provided.