Use dnsmasq local= for an authoritative internal fzymgc.house zone
Date: 2026-06-18 Status: Superseded by hl-qra1 Decision: hl-2t4o Deciders: sean
Context
Section titled “Context”Internal clients resolving *.fzymgc.house received correct internal A records but leaked public Cloudflare AAAA records, because the Firewalla’s dnsmasq overlay used hostsdir/expand-hosts/domain= with no authority directive — so any unoverridden record type forwarded to the public upstream (Control D → Cloudflare). The concrete failure: kubectl oidc-login (Go HTTP client) hung at OIDC discovery against auth.fzymgc.house because it attempted the leaked Cloudflare AAAA (2606:4700::/32) and blocked until context cancellation; kubernetes-mcp-server was stuck for the same reason. The headline choice — split-horizon with an authoritative internal zone — was made interactively over fully-internal-VPN-only and separate-public/private-domains; the directive-level choice was made over --filter-AAAA and full --auth-zone.
Decision
Section titled “Decision”Add local=/{{ router_hosts_domain }}/ to ansible/roles/router-hosts/templates/local-hosts.conf.j2, making dnsmasq authoritative for fzymgc.house: it answers the zone only from local hostsdir data and never forwards, returning NODATA for AAAA on A-only names and NXDOMAIN for unlisted names. auth.fzymgc.house stays publicly reachable via Cloudflare Tunnel; only the internal view changes.
Rationale
Section titled “Rationale”- Eliminates leak-by-default for all record types (AAAA, TXT, HTTPS/SVCB, future types), closing the structural split-incompleteness defect rather than patching the symptom.
- Minimal surface: one template line, deployed by the existing
router-hostsrole, gated byrouter_hosts_enable_dnsmasq_cutover. - Pre-flight audit confirmed the PublicNames − InternalOverrides gap is empty for all known
fzymgc.houseservices — NXDOMAIN risk is empirically near-zero. - Immune to upstream/ctrld configuration changes (the likely trigger for the latent leak going active).
Alternatives Considered
Section titled “Alternatives Considered”local=/fzymgc.house/authority directive (chosen): one-line, role-owned, reversible in seconds; kills leak-by-default for every record type; lighter than full auth-zone.--filter-AAAAtargeted suppression (rejected): no NXDOMAIN risk, but only patches the AAAA symptom; leaves TXT/HTTPS/SVCB and future types still leak-by-default.- Full
--auth-zoneSOA/zone-transfer mode (rejected): heavier than needed (SOA/NS/serial management); no zone-transfer consumers exist. - Fully-internal VPN-only, no public auth (rejected): breaks Cloudflare Access SSO — Authentik must stay publicly reachable (
tunnel.tfproxied=true). - Separate public/private domains (rejected): would rename 64+ internal hosts and break all IngressRoute annotations, Terraform, kubeconfigs, and docs — high blast radius for a one-line fix.
Consequences
Section titled “Consequences”- Positive:
AAAAfor A-only internal names returns NODATA instead of leaking Cloudflare anycast IPv6;kubectl oidc-login/kubernetes-mcp-serverno longer hang; future record types are also blocked from leaking; rollback is instantaneous (remove the line, re-run the role tag). - Negative: every new
fzymgc.housename in public DNS must also have an explicit internal hostsdir entry or it NXDOMAINs internally; therouter-hosts-dnsmasqAnsible tag (currently a no-op due to aninclude_taskstag-propagation gap) must be fixed before the directive can be applied selectively. - Neutral:
fzymgc.nethas the same latent leak pattern but is out of scope (follow-up bead); the live-only00-bootstrap-vault.confis unaffected;auth.fzymgc.houseremains publicly reachable — only the internal view changes.
References
Section titled “References”- Superseded by: hl-qra1