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) } } } }