The mesh's knowledge is a fact a module asks for, not three modules
mesh-names, mesh-resolver and the names half of the overlay generators are gone.
They ran no software and could not be swapped for anything, which is the test of
whether something is a module at all — they existed because computed output
needed somewhere to live, and the control plane's only shape for output was a
module.
Now a module says where it wants what the mesh knows:
facts: { node-zones: /etc/mesh-resolver/nodes.conf }
and is given a file, under its own name, applied and removed like anything else
it declares. Two facts exist: node-names (a hosts file — exact names) and
node-zones (every machine as a wildcard, *.homer.internal is homer). Asking for
a fact the mesh does not compute is refused naming what would have worked,
because a daemon that starts and reads a file nobody wrote is a worse way to
find out.
The names ride with the network now: wireguard's manifest asks for node-names
into /etc/hosts, because being on the private network is what gives a machine a
name. networking no longer requires name-resolution — names are not a provision,
and the module that answered it ran nothing.
One behaviour inverted, deliberately: choosing another VPN used to drag
WireGuard in anyway, because only WireGuard provided the addressing the names
module required — the node-scope claim existed to at least make that loud. With
names as a fact there is nothing to drag in: tailscale assigned means tailscale,
alone. The claim still catches two VPNs assigned explicitly.
And a machine the mesh cannot place is left out of both files rather than named
at nothing: a name resolving to nothing hangs a connection, where an unknown
name fails at once and says so. In practice that is only ever a token issued and
not yet used — a machine that has announced itself has an address.
Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -19,7 +19,7 @@ func provided(t *testing.T) map[string]catalogue.Manifest {
|
||||
t.Helper()
|
||||
out := map[string]catalogue.Manifest{}
|
||||
for _, raw := range []map[string]any{
|
||||
overlay.Manifest(), overlay.NamesManifest(), overlay.DomainManifest(),
|
||||
overlay.Manifest(), overlay.DomainManifest(),
|
||||
} {
|
||||
b, err := json.Marshal(raw)
|
||||
if err != nil {
|
||||
@@ -44,22 +44,51 @@ func TestTheShippedNetworkingModulesResolveOnTheirOwn(t *testing.T) {
|
||||
for _, m := range got.Modules {
|
||||
have = append(have, m.Module)
|
||||
}
|
||||
for _, want := range []string{overlay.Domain, overlay.Name, overlay.Names} {
|
||||
for _, want := range []string{overlay.Domain, overlay.Name} {
|
||||
if !strings.Contains(strings.Join(have, " "), want) {
|
||||
t.Fatalf("%s did not bring in %s: %v", overlay.Domain, want, have)
|
||||
}
|
||||
}
|
||||
|
||||
// **The names come WITH the network now, not from a third module.** Being on the private
|
||||
// network is what gives a machine a name, so the provider asks for the node-names fact and
|
||||
// there is nothing else to bring in. A module that ran nothing used to be here.
|
||||
for _, m := range got.Modules {
|
||||
if m.Module == overlay.Name && m.Facts["node-names"] == "" {
|
||||
t.Fatalf("the network's provider does not ask for the names: %+v", m.Facts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheShippedWireGuardModuleClaimsBeingTheNetwork(t *testing.T) {
|
||||
// Without this, a person who chose another VPN gets WireGuard as well, dragged in by the
|
||||
// names, and is not told. The claim is the only thing that catches it.
|
||||
func TestAnotherVPNSatisfiesNetworkingWithoutDraggingWireGuardIn(t *testing.T) {
|
||||
// **This inverted, and the inversion is the improvement.** The names used to be a module that
|
||||
// required the mesh's own addressing, which only WireGuard provided — so choosing another VPN
|
||||
// dragged WireGuard in anyway, and the node-scoped claim existed to at least make that
|
||||
// collision loud. With the names a fact rather than a provision, a person who chose tailscale
|
||||
// gets tailscale, and there is nothing left to collide.
|
||||
shipped := provided(t)
|
||||
_, err := catalogue.Resolve(
|
||||
got, err := catalogue.Resolve(
|
||||
withTailscale(shipped),
|
||||
[]string{overlay.Domain, "tailscale"},
|
||||
catalogue.Node{Name: "workstation", Site: "house"}, catalogue.World{})
|
||||
if err != nil {
|
||||
t.Fatalf("choosing another VPN was refused: %v", err)
|
||||
}
|
||||
for _, m := range got.Modules {
|
||||
if m.Module == overlay.Name {
|
||||
t.Fatalf("the other VPN was chosen and WireGuard came anyway: %v", got.Modules)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTwoVPNsAssignedTogetherStillCollide(t *testing.T) {
|
||||
// The claim still guards the case it was always for: both assigned EXPLICITLY, which is a
|
||||
// machine with two private networks and a coin toss about which one a peer reaches it on.
|
||||
shipped := provided(t)
|
||||
_, err := catalogue.Resolve(
|
||||
withTailscale(shipped),
|
||||
[]string{overlay.Name, "tailscale"},
|
||||
catalogue.Node{Name: "workstation", Site: "house"}, catalogue.World{})
|
||||
if err == nil {
|
||||
t.Fatal("a machine was given two private networks and nobody was told")
|
||||
}
|
||||
@@ -68,20 +97,9 @@ func TestTheShippedWireGuardModuleClaimsBeingTheNetwork(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTheShippedNamesModuleNeedsTheMeshsOwnAddresses(t *testing.T) {
|
||||
// Over a VPN whose addresses the mesh does not hand out, it has no names to write. Refusing
|
||||
// is what stops a machine getting a hosts file that means nothing on it.
|
||||
shipped := provided(t)
|
||||
delete(shipped, overlay.Name)
|
||||
_, err := catalogue.Resolve(shipped, []string{overlay.Names}, catalogue.Node{Name: "workstation", Site: "house"}, catalogue.World{})
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("the mesh's names resolved with nothing handing out the mesh's addresses")
|
||||
}
|
||||
if !strings.Contains(err.Error(), overlay.Addressing) {
|
||||
t.Fatalf("the refusal does not name what is missing: %v", err)
|
||||
}
|
||||
}
|
||||
// The names-need-addressing test went with the names module: names are a fact now, and a machine
|
||||
// the mesh cannot place is simply left out of the file (facts_test.go) — which is the same
|
||||
// protection, enforced where the file is written rather than by a provision refusing.
|
||||
|
||||
func withTailscale(shelf map[string]catalogue.Manifest) map[string]catalogue.Manifest {
|
||||
out := map[string]catalogue.Manifest{}
|
||||
|
||||
Reference in New Issue
Block a user