Retire the networking bundle and what the control plane stops shipping (hq ADR 0226)

networking required mesh-wireguard and nothing else; machines are assigned the network directly.
module forget refuses a provided module, so a retired one is removed at start once no machine has it.
Guard route-proxy's public account directory against a reissue.
This commit is contained in:
jochen
2026-10-06 02:21:18 +02:00
parent e096b4595a
commit 64bc138692
10 changed files with 381 additions and 52 deletions
+48 -23
View File
@@ -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.DomainManifest(),
overlay.Manifest(),
} {
b, err := json.Marshal(raw)
if err != nil {
@@ -34,20 +34,16 @@ func provided(t *testing.T) map[string]catalogue.Manifest {
return out
}
func TestTheShippedNetworkingModulesResolveOnTheirOwn(t *testing.T) {
got, err := catalogue.Resolve(provided(t), []string{overlay.Domain}, catalogue.Node{Name: "workstation", Site: "house"}, catalogue.World{})
func TestTheShippedPrivateNetworkResolvesOnItsOwn(t *testing.T) {
// **Assigned directly** (novox/hq ADR 0226): the `networking` bundle that required it is
// retired, so the private network's own module is what a machine is given, and it must need
// nothing the control plane does not ship beside it.
got, err := catalogue.Resolve(provided(t), []string{overlay.Name}, catalogue.Node{Name: "workstation", Site: "house"}, catalogue.World{})
if err != nil {
t.Fatalf("assigning %s does not work out of the box: %v", overlay.Domain, err)
t.Fatalf("assigning %s does not work out of the box: %v", overlay.Name, err)
}
var have []string
for _, m := range got.Modules {
have = append(have, m.Module)
}
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)
}
if len(got.Modules) != 1 || got.Modules[0].Module != overlay.Name {
t.Fatalf("assigning %s brought %v", overlay.Name, got.Modules)
}
// **The network writes no names** (novox/hq ADR 0199): /etc/hosts is the hosts seat holder's
@@ -55,23 +51,52 @@ func TestTheShippedNetworkingModulesResolveOnTheirOwn(t *testing.T) {
// may name that file.
for _, m := range got.Modules {
for name, f := range m.Facts {
if m.Module == overlay.Name && f.Path == "/etc/hosts" {
if f.Path == "/etc/hosts" {
t.Fatalf("the network's provider still writes /etc/hosts, as its %q fact", name)
}
}
}
}
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.
func TestTheControlPlaneShipsNoNetworkingBundle(t *testing.T) {
// ADR 0226: one module for the one private network. A bundle shipped beside it again would be
// a second name every machine carries for the same thing.
shipped := provided(t)
got, err := catalogue.Resolve(
withTailscale(shipped),
[]string{overlay.Domain, "tailscale"},
if len(shipped) != 1 {
t.Fatalf("the control plane ships %d modules, want only %s", len(shipped), overlay.Name)
}
if _, ok := shipped["networking"]; ok {
t.Fatal("the retired networking bundle is shipped again")
}
}
func TestARefusalForWantOfThePrivateNetworkNamesItsModule(t *testing.T) {
// The hint in a refusal is a string in the resolver rather than an import of this package. It
// named `networking` until ADR 0226; a hint naming a module nobody ships sends a person to
// assign something that does not exist.
shelf := map[string]catalogue.Manifest{
"app": {Module: "app", Version: "1", Requires: []string{"postgres-database"}},
"postgres": {Module: "postgres", Version: "1", Provides: catalogue.FromAnywhere("postgres-database")},
}
_, err := catalogue.Resolve(shelf, []string{"app"}, catalogue.Node{Name: "workstation"},
catalogue.World{Offered: map[string][]catalogue.Provider{
"postgres-database": {{Node: "anchor", At: "anchor.internal"}}}})
if err == nil {
t.Fatal("an app off the private network was pointed at a database on it")
}
if !strings.Contains(err.Error(), "assign "+overlay.Name) {
t.Fatalf("the refusal does not name the private network's module %s: %v", overlay.Name, err)
}
}
func TestAnotherVPNIsChosenByAssigningItInstead(t *testing.T) {
// What the bundle was for, kept without it: a machine given another VPN answers
// private-network from that VPN, and WireGuard is not dragged in by anything.
shipped := provided(t)
shelf := withTailscale(shipped)
shelf["needs-network"] = catalogue.Manifest{Module: "needs-network", Version: "1",
Requires: []string{overlay.Requirement}}
got, err := catalogue.Resolve(shelf, []string{"needs-network", "tailscale"},
catalogue.Node{Name: "workstation", Site: "house"}, catalogue.World{})
if err != nil {
t.Fatalf("choosing another VPN was refused: %v", err)
+119
View File
@@ -0,0 +1,119 @@
package catalogue
import (
"os"
"testing"
)
// The public issuer is the proxy's own fact (novox/hq ADR 0226), and the folding of it must not
// move the proxy's account.
//
// route-proxy keeps each ACME authority's account and certificates in a directory named after a
// digest of the directory URL and of the root bundle its `trust` container copies in
// (examples/route-proxy, forThisAuthority). Until ADR 0226 the URL was rendered from a binding to
// `public-acme`'s `acme-ca`, spelled with the port. A URL spelled any other way — even the same
// authority without `:443` — or a root bundle from another image is a new authority to the proxy:
// a new account, and every routed name ordered again, on two machines at once, against Let's
// Encrypt's rate limits. So what the module now states is held to exactly what the binding rendered.
// renderedBeforeTheFold is route-proxy's acme.env as the binding to public-acme rendered it on every
// machine running the proxy, read from the controller's plan on 2026-10-06.
const renderedBeforeTheFold = "ACME_DIRECTORY=https://acme-v02.api.letsencrypt.org:443/directory\n" +
"ACME_ROOTS=https://acme-v02.api.letsencrypt.org:443\n" +
"ACME_ROOTS_PATH=\n"
// trustImage is the image whose system bundle becomes the public root the account directory is
// named after. Moving it renames that directory.
const trustImage = "alpine@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b"
func TestRouteProxyKeepsItsPublicAccountDirectory(t *testing.T) {
m := catalogueManifest(t, "route-proxy")
for _, r := range m.Requires {
if r == "acme-ca" {
t.Fatal("route-proxy still asks for acme-ca; the public issuer is its own fact (ADR 0226)")
}
}
// As a build would leave it: each artifact a container names resolved to an image. Which image
// does not matter to the file checked here.
for _, res := range m.Resources {
if artifact, ok := res["artifact"].(string); ok {
delete(res, "artifact")
res["image"] = "registry.invalid/route-proxy/" + artifact +
"@sha256:1111111111111111111111111111111111111111111111111111111111111111"
}
}
r := Resolution{
Node: "anchor",
Modules: []Manifest{m},
Needs: []Needed{{
Name: "internal-acme-ca", From: "home", At: "home.internal", For: "route-proxy",
Serves: map[string]any{
"port": float64(9000), "path": "/acme/acme/directory", "roots": "/roots.pem",
},
}},
}
// Its own broker credential, sealed as the mesh would; what it is does not matter here.
out, err := r.Declaration(Rendering{Needed: map[string]map[string]string{
"route-proxy": {"broker": "sealed"}}})
if err != nil {
t.Fatalf("route-proxy could not be composed for a machine: %v", err)
}
env := fileNamed(out, "route-proxy.acme-env")
if env == nil {
t.Fatalf("nothing writes the public issuer's environment: %v", out)
}
if got, _ := env["content"].(string); got != renderedBeforeTheFold {
t.Fatalf("acme.env changed, which moves the proxy's account and reorders every certificate:\n"+
"got %q\nwant %q", got, renderedBeforeTheFold)
}
if fileNamed(out, "route-proxy.bound-acme-ca") != nil {
t.Error("a binding to acme-ca is still written")
}
var trust string
if m.Build != nil {
for _, a := range m.Build.Artifacts {
if a.Name == "trust" {
trust = a.From
}
}
}
if trust != trustImage {
t.Errorf("the trust image is %q, not %q: its system bundle is the public root the account "+
"directory is named after, so moving it reorders every certificate. Move it only with a "+
"plan for the account (ADR 0226)", trust, trustImage)
}
}
// The modules ADR 0226 retired stay retired, and nothing asks for what they provided.
func TestTheRetiredNetworkingModulesAreNotInTheCatalogue(t *testing.T) {
if _, err := os.Stat("../../../mesh-catalog/modules"); err != nil {
t.Skipf("the catalogue is not beside this checkout: %v", err)
}
for _, gone := range []string{"public-acme", "dhcpcd", "cloudflare-dns"} {
if _, err := os.Stat("../../../mesh-catalog/modules/" + gone); err == nil {
t.Errorf("%s is in the catalogue again; ADR 0226 retired it", gone)
}
}
entries, err := os.ReadDir("../../../mesh-catalog/modules")
if err != nil {
t.Fatal(err)
}
for _, e := range entries {
raw, err := os.ReadFile("../../../mesh-catalog/modules/" + e.Name() + "/module.json")
if err != nil {
continue
}
m, err := ParseManifest(raw)
if err != nil {
continue // judged by the catalogue's own tests
}
for _, r := range m.Requires {
if r == "acme-ca" || r == "public-dns" {
t.Errorf("%s requires %q, which nothing in the catalogue provides since ADR 0226",
m.Module, r)
}
}
}
}
+4 -2
View File
@@ -1019,8 +1019,10 @@ func FromAnywhere(names ...string) []Offer {
//
// A string here rather than an import, because the private network is a module the control plane
// ships and this package must not depend on the thing it resolves. The name being wrong would
// show up as a refusal naming a module nobody can assign, which a test checks.
const meshNetwork = "networking"
// show up as a refusal naming a module nobody can assign, which a test checks
// (provided_test.go). The private network's own module since novox/hq ADR 0226 retired the
// `networking` bundle that required it.
const meshNetwork = "mesh-wireguard"
// providersFirst orders a node's modules so that what answers a requirement comes before what
// asked for it.
+2 -1
View File
@@ -24,7 +24,8 @@ var bothResolvers = []Held{
// uplinks is every module in the catalogue holding node-uplink, and so writing the machine's resolver
// file (novox/hq ADR 0223 part 2): the program that would otherwise rewrite it is the one that writes it.
var uplinks = []string{"dhcpcd", "networkmanager", "systemd-networkd"}
// dhcpcd's left the catalogue with ADR 0226, held by no machine.
var uplinks = []string{"networkmanager", "systemd-networkd"}
// managing is a machine as each uplink module needs it: able to install, run a service and run the
// manager that module is for.