Skip to content

Use dnsmasq local= for an authoritative internal fzymgc.house zone

Date: 2026-06-18 Status: Superseded by hl-qra1 Decision: hl-2t4o Deciders: sean

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.

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.

  • 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-hosts role, gated by router_hosts_enable_dnsmasq_cutover.
  • Pre-flight audit confirmed the PublicNames − InternalOverrides gap is empty for all known fzymgc.house services — NXDOMAIN risk is empirically near-zero.
  • Immune to upstream/ctrld configuration changes (the likely trigger for the latent leak going active).
  • 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-AAAA targeted suppression (rejected): no NXDOMAIN risk, but only patches the AAAA symptom; leaves TXT/HTTPS/SVCB and future types still leak-by-default.
  • Full --auth-zone SOA/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.tf proxied=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.
  • Positive: AAAA for A-only internal names returns NODATA instead of leaking Cloudflare anycast IPv6; kubectl oidc-login / kubernetes-mcp-server no 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.house name in public DNS must also have an explicit internal hostsdir entry or it NXDOMAINs internally; the router-hosts-dnsmasq Ansible tag (currently a no-op due to an include_tasks tag-propagation gap) must be fixed before the directive can be applied selectively.
  • Neutral: fzymgc.net has the same latent leak pattern but is out of scope (follow-up bead); the live-only 00-bootstrap-vault.conf is unaffected; auth.fzymgc.house remains publicly reachable — only the internal view changes.
  • Superseded by: hl-qra1