The composer's proof that the manifest composes, and that a port is the placement's, is the node-engine's now (ADR 0593 §7). Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
138 lines
6.0 KiB
Go
138 lines
6.0 KiB
Go
package main
|
|
|
|
import (
|
|
"path/filepath"
|
|
"slices"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
)
|
|
|
|
// Moved from mesh-controller's internal/ownmanifest (issue 1126), which is archived with the controller: the two tests
|
|
// that held route-proxy's manifest to what keeps its public account and runs its program, now beside the module.
|
|
|
|
// 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 (cmd/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"
|
|
|
|
// ownManifest is route-proxy's manifest as its tree holds it: this repository's root and the module's directory.
|
|
func ownManifest(t *testing.T) catalogue.Manifest {
|
|
t.Helper()
|
|
m, err := catalogue.ReadTreeManifest(filepath.Join("..", "..", "..", ".."), filepath.Join("..", ".."))
|
|
if err != nil {
|
|
t.Fatalf("route-proxy's manifest does not parse:\n%v", err)
|
|
}
|
|
return m
|
|
}
|
|
|
|
func resourceNamed(out []map[string]any, id string) map[string]any {
|
|
for _, r := range out {
|
|
if r["id"] == id {
|
|
return r
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestRouteProxyKeepsItsPublicAccountDirectory(t *testing.T) {
|
|
m := ownManifest(t)
|
|
if slices.Contains(m.Requires, "acme-ca") {
|
|
t.Fatal("route-proxy still asks for acme-ca; the public issuer is its own fact (ADR 0226)")
|
|
}
|
|
// The tree read inlines the template the manifest names, a literal with nothing to fill, so this is what it writes.
|
|
env := resourceNamed(m.Resources, "acme-env")
|
|
if env == nil {
|
|
t.Fatalf("nothing writes the public issuer's environment: %v", m.Resources)
|
|
}
|
|
if got, _ := env["content"].(string); got != renderedBeforeTheFold {
|
|
t.Fatalf("acme.env changed, which moves the proxy's account and reorders every certificate:\ngot %q\nwant %q",
|
|
got, renderedBeforeTheFold)
|
|
}
|
|
if resourceNamed(m.Resources, "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 proxy's program is mounted into the pinned trust image, not baked into an image (novox/hq ADR 0480 clauses 1, 2
|
|
// and 4): a Go bundle of the module's own Go module stamped for alpine, unpacked by an archive resource, mounted
|
|
// read-only at /opt/mesh, called through args, and named under restart-on so a new program never leaves the old one
|
|
// running. The container's name, network and logging are what the jail and the routes rely on. And its routes are the
|
|
// seat mesh-provisioner's plans alone (issue 774, research 072 row 34): it reads them, and receives no file.
|
|
func TestRouteProxyRunsItsMountedProgramOnTheTrustImage(t *testing.T) {
|
|
m := ownManifest(t)
|
|
if !slices.Contains(m.Reads, "mesh-provisioner.plans") {
|
|
t.Errorf("route-proxy does not read mesh-provisioner.plans, where its routes are: %v", m.Reads)
|
|
}
|
|
if len(m.Receives) > 0 {
|
|
t.Errorf("route-proxy receives %v: a route is served from the seat's plans, never a received file", m.Receives)
|
|
}
|
|
var bundle *catalogue.Artifact
|
|
for i, a := range m.Build.Artifacts {
|
|
if a.Name == "server" {
|
|
bundle = &m.Build.Artifacts[i]
|
|
}
|
|
if a.Kind == "image" {
|
|
t.Errorf("artifact %s is an image built from a Dockerfile (ADR 0452 §1)", a.Name)
|
|
}
|
|
}
|
|
if bundle == nil || bundle.Kind != "bundle" || bundle.Language != "go" || bundle.System != "alpine" ||
|
|
bundle.From != "cmd/route-proxy" || bundle.Binary != "route-proxy" {
|
|
t.Fatalf("the server artifact is not a Go bundle of cmd/route-proxy stamped for alpine: %+v", bundle)
|
|
}
|
|
var archive, server map[string]any
|
|
for _, r := range m.Resources {
|
|
switch {
|
|
case r["type"] == "archive" && r["artifact"] == "server":
|
|
archive = r
|
|
case r["id"] == "server":
|
|
server = r
|
|
}
|
|
}
|
|
if archive == nil || server == nil {
|
|
t.Fatalf("archive %v or server container %v is missing", archive, server)
|
|
}
|
|
if server["artifact"] != "trust" || server["name"] != "route-proxy" || server["network"] != "host" || server["logging"] != "journald" {
|
|
t.Errorf("the server container moved: %v", server)
|
|
}
|
|
mount := archive["path"].(string) + ":/opt/mesh:ro"
|
|
volumes, _ := server["volumes"].([]any)
|
|
if !slices.Contains(volumes, any(mount)) {
|
|
t.Errorf("the archive is not mounted read-only at /opt/mesh (%s): %v", mount, server["volumes"])
|
|
}
|
|
if args, _ := server["args"].([]any); len(args) != 1 || args[0] != "/opt/mesh/route-proxy" {
|
|
t.Errorf("args are %v, want the mounted program", server["args"])
|
|
}
|
|
restartOn, _ := server["restart-on"].([]any)
|
|
if !slices.Contains(restartOn, archive["id"]) {
|
|
t.Errorf("restart-on %v does not name the archive %v", server["restart-on"], archive["id"])
|
|
}
|
|
}
|