docs: FINDING — public DNS publishes internal RFC1918 addresses, contradicting R-DNS-03
gate / gate (push) Has been cancelled
gate / conformance (push) Has been cancelled

Operator-reported, verified. external-dns runs provider cloudflare with
gateway-httproute + service sources and publishes the Cilium Gateway VIP
192.168.2.20, so ~16 hostnames in a public authoritative zone answer with
RFC1918 space. The spec says the opposite: external resolves to the public IP,
Pi-hole serves the internal split-horizon answers, internal services are
unreachable from the internet.

Provenance stated plainly: the pattern predates me — external-dns always
published the Gateway address, and dev did the same under owner=default. My
apex migration changed which names carry it, propagating an existing flaw onto
the apex rather than inventing it.

Four consequences recorded: topology and service-inventory disclosure; a
DNS-rebind primitive, which is why the SOHO router strips these answers and
LAN clients need per-client overrides; the five services declaring exposure
public are not actually publicly reachable; and seven internal services are
publicly advertised.

Also records why deleting the records is not a safe first step: the
*.home.keithjackson.dev wildcard is gone, so removal yields NXDOMAIN rather
than a fallback. Remediation sequenced, nothing started.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
jakman85andClaude Opus 5 committed 2026-09-14 00:11:30 -05:00
1 parent 3de79f81a5
commit ce7819a7da
1 file changed
+62
+62
View File
@@ -3858,3 +3858,65 @@ Two genuine discoveries from that re-measurement:
queueing, which is also why its queue time stays near zero. This is an independent signal that
prod-1 sustains far more concurrent NFS work than the other two nodes, and it is consistent with
three of the four NFS-heavy pods being scheduled there.
---
## 2026-09-14 — FINDING (operator): public DNS carries internal RFC1918 addresses
The operator pointed out that public DNS records resolve to internal, non-routable IPs. Verified, and
it is worse than disclosure alone.
**What is published.** `platform/externaldns/helmrelease.yaml` runs `provider: cloudflare` with
`sources: [gateway-httproute, service]` and `policy: sync`. external-dns faithfully publishes each
source's address, and that address is the Cilium Gateway's LoadBalancer VIP `192.168.2.20` (plus the
L4 service IPs `.21` unifi and `.22` crafty-mc). So ~16 hostnames in a **public authoritative zone**
answer with RFC1918 space.
**This contradicts the spec.** `docs/09-dns-and-ingress.md` R-DNS-03 states: *"**External**
(`*.home.keithjackson.dev` from the internet): resolves to the **public IP**"*, *"**PiHole** …
provides internal LAN DNS and the split-horizon answers"*, and *"`internal` services are unreachable
from the public internet"*. The deployed arrangement inverts it — the internal VIP is what the world
is told, and there is no internal authority at all (Pi-hole serves no DNS; see the entry above).
**Provenance, plainly.** The pattern is architectural and predates me: external-dns has always
published the Gateway's address to Cloudflare, and on dev it did the same with dev's internal VIP
under `owner=default`. What MY apex migration changed is *which* names carry it — prod moved from
`prod.home.keithjackson.dev` to the apex, so `home.keithjackson.dev` and its subdomains now publish
internal addresses where previously they were dev's records or the wildcard. I propagated an existing
flaw onto the apex; I did not invent it. I also widened prod's `EXTERNALDNS_DOMAIN_FILTER` from its
own subtree to the zone apex `keithjackson.dev` per finding F-DNS1 (a strict-subdomain filter drops
the parent zone), which is the documented fallback but leaves `txtOwnerId` as the sole ownership guard.
**Four consequences:**
1. **Information disclosure.** Anyone can enumerate the internal subnet, the Gateway VIP, both L4
service addresses, and the full service inventory — sonarr, radarr, qbittorrent, jackett, gitea,
nextcloud, keycloak, semaphore, pihole, plex, seerr, crafty, headlamp, unifi, runbooks, grafana.
No credentials leak, but topology and a target list do.
2. **A DNS-rebind primitive.** A public name answering with a private address is exactly the rebind
shape browsers and resolvers defend against — which is *why* the SOHO router at 192.168.2.1 strips
these answers, and therefore why LAN clients need per-client dnsmasq overrides. The security
control is fighting the misconfiguration.
3. **The five `public` services are not actually publicly reachable.** plex, gitea, seerr, keycloak and
nextcloud declare `exposure: public`, yet their records point at 192.168.2.20, which no internet
client can route to. The declaration is unsatisfied.
4. **Seven `internal` services are advertised publicly** — pihole, radarr, sonarr, jackett, vm-demo,
semaphore, qbittorrent — which the spec says must be unreachable from the internet. They are
unreachable, but they should not be *listed*.
**Removing the records is NOT a safe first step.** The `*.home.keithjackson.dev` wildcard that used to
answer for this subtree is **gone** — `nothing-here.home.keithjackson.dev` returns NXDOMAIN — so
deleting the specific records yields no answer at all rather than falling back. Only
`*.keithjackson.dev` still resolves, to the legacy public IP `136.51.115.252` (ports 80/443 open,
serving a placeholder certificate `CN=%D`, so the legacy stack's TLS is not configured for these
names either).
**Sequenced remediation — NOT started, needs the operator's decisions:**
1. **Stand up internal DNS first.** Expose Pi-hole's DNS on port 53 via a LoadBalancer IP and make it
authoritative for the zone internally. Until this exists, removing public records breaks LAN
resolution outright, because the router strips RFC1918 and nothing else answers.
2. **Then stop publishing internal names publicly.** Either scope external-dns to only the services
declared `public`, or point it at an internal provider, or drop the Cloudflare provider for this
subtree entirely. This is the step that closes the disclosure.
3. **Then decide what `public` means for the five.** If they are genuinely meant to be
internet-reachable, their records must target the public IP with edge port-forwards and real
certificates; if not, they should be re-declared `internal`. Today they are neither.