From e56f3aa1cb992cd515e1f72f1e00b11de1980569 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 3 Oct 2026 15:39:05 +0200 Subject: [PATCH] The roster publishes a route's internal name, never its public one (hq ADR 0191) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NamesServed read a route's public `name` and plan.go then filtered by suffix — telling the mesh's names from public ones by their spelling, when the mesh composed both itself. It now publishes the `internal-name` it composed under the serving node (ADR 0151); the suffix filter is gone. --- cmd/mesh-controller/mesh_own_names_test.go | 27 ---------------------- cmd/mesh-controller/plan.go | 27 ++++------------------ internal/catalogue/names_served.go | 15 +++++++++--- internal/catalogue/names_served_test.go | 26 +++++++++++++++++---- 4 files changed, 37 insertions(+), 58 deletions(-) delete mode 100644 cmd/mesh-controller/mesh_own_names_test.go diff --git a/cmd/mesh-controller/mesh_own_names_test.go b/cmd/mesh-controller/mesh_own_names_test.go deleted file mode 100644 index bfbb320..0000000 --- a/cmd/mesh-controller/mesh_own_names_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "reflect" - "testing" -) - -// The mesh's resolver holds only the mesh's own names (novox/hq ADR 0191): a routed public name is -// never given a private answer, and a route's internal name is. -func TestOnlyTheMeshsOwnNamesAreAnsweredPrivately(t *testing.T) { - routes := map[string]string{ - "git.example.tld": "10.77.0.1", - "example.tld": "10.77.0.1", - "media.home.example": "10.77.0.2", - "git.anchor.internal": "10.77.0.1", - "media.homeserver.internal": "10.77.0.2", - "internal.example.tld": "10.77.0.1", // the suffix as a label, not as the zone - } - got := meshOwnNames(routes, "internal") - want := map[string]string{ - "git.anchor.internal": "10.77.0.1", - "media.homeserver.internal": "10.77.0.2", - } - if !reflect.DeepEqual(got, want) { - t.Fatalf("names answered privately: %v\nwant only the mesh's own: %v", got, want) - } -} diff --git a/cmd/mesh-controller/plan.go b/cmd/mesh-controller/plan.go index 55a7a13..eb07049 100644 --- a/cmd/mesh-controller/plan.go +++ b/cmd/mesh-controller/plan.go @@ -645,9 +645,9 @@ func renderingFor(ctx context.Context, open *stores, node string, } } - // And every routed name under the mesh's own suffix → the node that serves it, alongside the - // `.internal` names above (novox/hq ADR 0066, narrowed by ADR 0191). A public name is not - // among them: the mesh gives no private answer for a name public DNS answers. + // And every route's internal name → the node that serves it, alongside the `.internal` + // names above (novox/hq ADR 0066, narrowed by ADR 0191). A route's public name is not among + // them: the mesh gives no private answer for a name public DNS answers. // Kept apart from the machines, because a fact about the machines must not be handed the names // the mesh merely serves (novox/hq 04-ISSUES/111). machines := make(map[string]string, len(names)) @@ -658,7 +658,7 @@ func renderingFor(ctx context.Context, open *stores, node string, if err != nil { return catalogue.Rendering{}, inventory.Node{}, err } - for name, at := range meshOwnNames(routes, overlay.Suffix()) { + for name, at := range routes { names[name] = at } @@ -739,25 +739,6 @@ func renderingFor(ctx context.Context, open *stores, node string, }, record, nil } -// meshOwnNames is the routed names the mesh may answer privately: those under its own suffix. -// -// **The mesh's resolver holds only the mesh's own names** (novox/hq ADR 0191). A routed public name -// was once published here at its serving node's private address, so an internal authority could -// reach it to certify it (ADR 0066). Every machine's resolver then answered public names with -// addresses only members can reach — and a resolver that also serves a LAN handed them to a phone -// on it, which could not reach the mail server while every check, run from a member, passed. A -// route is reached and certified inside the mesh by its internal name (ADR 0151); its public name -// resolves publicly, for members and everyone else alike. -func meshOwnNames(routes map[string]string, suffix string) map[string]string { - out := map[string]string{} - for name, at := range routes { - if strings.HasSuffix(name, "."+suffix) { - out[name] = at - } - } - return out -} - // routeNamesInTheMesh is every routed name and the address of the node that serves it (novox/hq // ADR 0066). // diff --git a/internal/catalogue/names_served.go b/internal/catalogue/names_served.go index 3880324..93e089c 100644 --- a/internal/catalogue/names_served.go +++ b/internal/catalogue/names_served.go @@ -22,8 +22,15 @@ import ( // modules declared. Deterministic: names, requirements and nodes are walked in order, so two // plans of one mesh yield one region. -// NamesServed is every routed name across the mesh and the node that serves it, from every node's -// resolution and settings. A name several termini claim goes to the first node in name order, so +// **The name published is the one the mesh composed for itself** (novox/hq ADR 0191). A route carries +// two: `name`, composed from the node's public domain, and `internal-name`, composed from the mesh's +// own domain under the serving node (ADR 0151). Only the second is the mesh's to answer. The public +// one is the operator's, answered by public DNS — publishing it here gave every machine's resolver a +// private answer for a public name, and a resolver that also serves a LAN handed it to a phone that +// could not reach it. Which is which is known from where each was composed, not read off its spelling. +// +// NamesServed is every routed internal name across the mesh and the node that serves it, from every +// node's resolution and settings. A name several termini claim goes to the first node in name order, so // the answer is stable; a name nothing terminal claims is left out. func NamesServed(plans map[string]Resolution, settings map[string]SettingsBy) (map[string]string, error) { nodes := make([]string, 0, len(plans)) @@ -56,7 +63,9 @@ func NamesServed(plans map[string]Resolution, settings map[string]SettingsBy) (m if _, labelled := given.Values["label"]; !labelled { continue } - name, _ := given.Values["name"].(string) + // A route that asked for no internal name — its reach is public only — has none to + // publish, and its public name is not the mesh's to answer. + name, _ := given.Values["internal-name"].(string) if name == "" { continue } diff --git a/internal/catalogue/names_served_test.go b/internal/catalogue/names_served_test.go index aa85c97..c2b2efc 100644 --- a/internal/catalogue/names_served_test.go +++ b/internal/catalogue/names_served_test.go @@ -55,14 +55,14 @@ func TestANameResolvesToTheNodeWhoseProxyServesIt(t *testing.T) { if err != nil { t.Fatal(err) } - if served["grafana.home.example"] != "home-server" { + if served["grafana.home-server.internal"] != "home-server" { t.Fatalf("run %d: the dashboard's name is served by %q, and its proxy is on the home server: %v", - i, served["grafana.home.example"], served) + i, served["grafana.home-server.internal"], served) } - if served["login.control.example"] != "anchor" { + if served["login.anchor.internal"] != "anchor" { t.Fatalf("run %d: the identity provider's own name is served by its proxy on the control node: %v", i, served) } - if _, leaked := served["grafana.control.example"]; leaked { + if _, leaked := served["grafana.anchor.internal"]; leaked { t.Fatalf("a name composed for the identity provider's benefit is not one it serves: %v", served) } } @@ -91,9 +91,25 @@ func TestEveryRouteOfAModuleWithSeveralIsANameServed(t *testing.T) { if err != nil { t.Fatal(err) } - for _, name := range []string{"photos.control.example", "photos-api.control.example"} { + for _, name := range []string{"photos.anchor.internal", "photos-api.anchor.internal"} { if served[name] != "anchor" { t.Fatalf("%s is not served by its proxy: %v", name, served) } } } + +// A route's public name is the operator's and answered by public DNS; the mesh publishes only the +// internal name it composed for the same route (novox/hq ADR 0191) — told apart by where each was +// composed, never by how it is spelled. +func TestAPublicNameIsNeverAMeshName(t *testing.T) { + plans, settings := twoNodesOneName(t) + served, err := NamesServed(plans, settings) + if err != nil { + t.Fatal(err) + } + for _, public := range []string{"grafana.home.example", "login.control.example"} { + if node, published := served[public]; published { + t.Fatalf("the public name %s is published at %s; public DNS answers it: %v", public, node, served) + } + } +}