carlos / platform Public

edge: KnownHost — exact membership of the mirrored home map

The three cert-adjacent gates need to ask "does some box in this
deployment home this name, spelled exactly?" (#277). Home() cannot
answer it: its one-label wildcard fallback is what makes an invented
subdomain under a remote catch-all dialable, and a cert gate that
admitted such a label would let it drive an ACME order and spend a
read-through budget — PR #180's rule, verbatim.

So KnownHost reads the same fold and refuses both the fallback and the
"*." key itself, inheriting the fold's own-box and no-EIP exclusions
(both documented, both unreachable-for-validation). Nil-receiver-safe,
like Home. Unused until the gates are widened in the next commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev b069537f51f06ac19de7ea6056abb6c642f40d72 parent 3af777e
2 files changed, +97 −0
  • internal/edge/homemap.go +37 −0
  • internal/edge/homemap_test.go +60 −0
diff --git a/internal/edge/homemap.go b/internal/edge/homemap.go
index 044709a88..841d4e92a 100644
--- a/internal/edge/homemap.go
+++ b/internal/edge/homemap.go
@@ -413,6 +413,43 @@ func (m *homeMap) Home(host string) (eip, box string, ok bool) {
return "", "", false
}
+// KnownHost reports whether host is an EXACT key in the mirrored home map — a
+// name some OTHER box in this deployment homes, spelled precisely.
+//
+// Deliberately NOT Home()'s one-label wildcard fallback, and refusing "*."
+// outright: this predicate widens cert issuance and challenge admission
+// (certsync.go's three gates, through Edge.deploymentHost), where PR #180's
+// rule stands — attacker-chosen labels under a catch-all must never drive an
+// ACME order or a bucket read, and their TLS coverage is the wildcard cert's
+// job (Allowed's own doc says the same thing for this box's own rows). The
+// widening loses nothing by staying exact: an order only ever exists for a
+// name that passed the exact gate, so a challenge only ever needs answering
+// for an exact name.
+//
+// Unlike Home it needs no EIP and no dial target — membership is the whole
+// question — but it reads the same fold, so it inherits both of the fold's
+// exclusions. Own-box hosts are excluded and that is harmless: they pass
+// Allowed on the other half of deploymentHost. Boxes with EIP=="" are
+// excluded and that is accepted: a box with no public IP offers no backhaul
+// target and appears in no DNS answer, so a validation for its hosts has
+// nowhere legitimate to land anyway.
+//
+// There is deliberately no m.self gate, unlike Home: Home fail-closes without
+// a self label because it could otherwise offer this box itself as a dial
+// target, and membership carries no such hazard — an unlabelled box's fold
+// keeps its own file, whose hosts are its own registry rows and so pass
+// Allowed regardless.
+//
+// Nil-receiver-safe, like Home: the map may not be armed yet (or at all, on a
+// box with no deployment store).
+func (m *homeMap) KnownHost(host string) bool {
+ if m == nil || strings.HasPrefix(host, "*.") {
+ return false
+ }
+ _, ok := m.snapshot().hosts[host]
+ return ok
+}
+
// refreshKeys re-reads the mesh key pair. loadMeshKeys is already tolerant of
// a malformed .next and fail-closed on the current key (mesh.go); this only
// adds the mirror's own posture on top — the pair is replaced ONLY on a clean
diff --git a/internal/edge/homemap_test.go b/internal/edge/homemap_test.go
index aa87a7df5..ce605928c 100644
--- a/internal/edge/homemap_test.go
+++ b/internal/edge/homemap_test.go
@@ -235,11 +235,71 @@ func TestNilHomeMapIsInert(t *testing.T) {
if _, _, ok := m.Home("b.test"); ok {
t.Fatal("a nil home map must offer nothing")
}
+ if m.KnownHost("b.test") {
+ t.Fatal("a nil home map knows no host")
+ }
if cur, next := m.meshKeys(); cur != nil || next != nil {
t.Fatal("a nil home map holds no mesh keys")
}
}
+// KnownHost is the membership question the three cert gates ask through
+// Edge.deploymentHost (#277), and it is EXACT-only where Home is not. Home's
+// one-label fallback is what makes an invented subdomain under a remote
+// catch-all DIALABLE; letting the same fallback into a cert gate would let
+// that invented label drive an ACME order and spend a read-through budget,
+// which is precisely what PR #180's Allowed rule refuses. The two answers
+// diverging on the same map, in the same test, is the pin.
+func TestKnownHostExactOnly(t *testing.T) {
+ s := meshTestStore(t)
+ putJSON(t, s, "control/edges/annie.json",
+ `{"v":1,"box":"annie","eip":"1.1.1.1","hosts":{"own.test":{"role":"home"}}}`)
+ putJSON(t, s, "control/edges/betty.json",
+ `{"v":1,"box":"betty","eip":"2.2.2.2","hosts":{"*.apps.test":{"role":"home"},"exact.apps.test":{"role":"home"}}}`)
+ m := newHomeMap(s, "annie")
+ refreshHomeMap(t, m)
+
+ if !m.KnownHost("exact.apps.test") {
+ t.Fatal("an exactly-named remote-homed host is the whole point of the predicate")
+ }
+ // Precondition: the fallback Home applies is live on this very map — so
+ // the refusal below is KnownHost's own, not an empty map's.
+ if _, _, ok := m.Home("invented.apps.test"); !ok {
+ t.Fatal("test bug: the one-label wildcard rule did not apply")
+ }
+ if m.KnownHost("invented.apps.test") {
+ t.Fatal("a label under a mirrored catch-all must never be admitted — that is #180's rule")
+ }
+ if m.KnownHost("*.apps.test") {
+ t.Fatal("the catch-all key itself is not a certifiable name")
+ }
+ if m.KnownHost("stranger.example") {
+ t.Fatal("a host no box in the deployment homes is a stranger")
+ }
+ // Own-box rows are excluded by the fold and stay excluded here. Harmless:
+ // deploymentHost's other half (Allowed) is what admits this box's routes.
+ if m.KnownHost("own.test") {
+ t.Fatal("the fold excludes this box's own file, so KnownHost inherits that exclusion")
+ }
+ // Nothing polled yet: no membership, and no panic on the gate's path.
+ if newHomeMap(s, "annie").KnownHost("exact.apps.test") {
+ t.Fatal("a map that has never polled knows no host")
+ }
+}
+
+// A box publishing eip:"" is in the fold's no-dial exclusion, and KnownHost
+// inherits it: there is no DNS answer pointing at such a box, so a validation
+// for its hosts has nowhere legitimate to land.
+func TestKnownHostSkipsBoxesWithNoEIP(t *testing.T) {
+ s := meshTestStore(t)
+ putJSON(t, s, "control/edges/betty.json", `{"v":1,"box":"betty","eip":"","hosts":{"b.test":{"role":"home"}}}`)
+ m := newHomeMap(s, "annie")
+ refreshHomeMap(t, m)
+ if m.KnownHost("b.test") {
+ t.Fatal("a box with no public IP offers no validation target")
+ }
+}
+
// ---- hop acceptance ---------------------------------------------------------
// forwardEcho is an upstream that reports back exactly the trust-bearing