Files
mesh-controller/internal/catalogue/co_located_test.go
T
jochen 1b502a37e0
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check fail: its merge-check.sh failed: --- FAIL: TestAnUnreadableStoreClearsNothing (0.01s)
Let only the authority's root hold lines, refuse every line end, and keep places off the runtime's data and any .ssh
The review of hq issue 339 found the PEM exception too wide (any module, any
key, any label, anything base64), \v, \f, NEL and the Unicode separators
still let a value end a line in some readers, and the spool, /opt, the
container runtimes' data and an account's .ssh still placeable. Lines are now
taken only in step-ca's root setting, as certificates encoding/pem decodes and
x509 parses; every line end is refused; and those paths are the machine's own.
The test certificate is a real one, made for the tests with its key thrown away.
2026-10-09 00:28:08 +02:00

307 lines
12 KiB
Go

package catalogue
import (
"encoding/json"
"strings"
"testing"
)
// A provider and its consumer on ONE machine, which is what ADR 0066's anchor is.
//
// **Every fault in this file is the same shape: the same-node path diverging from the cross-node
// one.** A provider on another machine is walked by the control plane — its served facts are
// re-derived from its manifest with that machine's port assignments and settled with that node's
// settings layers — and only then handed to the consumer. A provider on the consumer's OWN machine
// never passes through that walk, so every step of it had to be repeated here, and each step that
// was not is a promise the co-located arrangement quietly breaks.
//
// 04-ISSUES/038 was the first of them (the port). These are the rest.
// A root certificate, as a certificate authority serves one: a real, self-signed one made for these tests (its key
// thrown away), because the one setting that may hold lines must parse as a certificate (novox/hq issue 339).
const servedRoot = `-----BEGIN CERTIFICATE-----
MIIBPjCB8aADAgECAhRZG3p93hUUB5bz00uxhYo/nJnTQjAFBgMrZXAwFDESMBAG
A1UEAwwJdGVzdCByb290MCAXDTI2MTAwODIyMjE0NloYDzIxMjYwOTE0MjIyMTQ2
WjAUMRIwEAYDVQQDDAl0ZXN0IHJvb3QwKjAFBgMrZXADIQA0pt/ld+W0MXwBhPfO
cuAt56kIW6Qcn+4vqWpuvHTiqaNTMFEwHQYDVR0OBBYEFIjgaLk4OVGIOeZQiqnN
p9vhciFjMB8GA1UdIwQYMBaAFIjgaLk4OVGIOeZQiqnNp9vhciFjMA8GA1UdEwEB
/wQFMAMBAf8wBQYDK2VwA0EAl9uWeSM2XAV8u0reyV3BLRxNVik+4FCRO1QKPs2k
IlB1rK9oAOYManH+VFuBMI/JJ31ajSti81q4E0CSw8r5DQ==
-----END CERTIFICATE-----
`
// stepCA is an internal ACME authority: it answers `acme-ca` from anywhere in the mesh, and what a
// consumer must know is the directory URL and the root to trust. **The root is not knowable when
// the module is written** — it exists only once the CA has been initialised — so the manifest
// declares the key and the operator supplies the value as a setting, which is exactly the shape the
// cross-node path settles and the same-node path did not.
func stepCA() Manifest {
return Manifest{
Module: "step-ca", Version: "1",
Provides: FromAnywhere("acme-ca"),
Listens: []Listening{{Port: 9000, Protocol: "tcp", From: FromMesh}},
Serves: map[string]map[string]any{"acme-ca": {
"directory": "https://anchor.internal/acme/acme/directory",
// Declared empty: a manifest is the same on every mesh, and this mesh's root is not.
"root": "",
}},
Resources: []map[string]any{{
"id": "server", "type": "container", "name": "step-ca", "ports": []any{"9000"},
}},
}
}
// routeProxy issues from that authority, so it must be told the root to verify it with.
func routeProxy() Manifest {
return Manifest{
Module: "route-proxy", Version: "1",
Requires: []string{"acme-ca"},
Provides: FromAnywhere("route"),
Binds: map[string]string{"acme-ca": "/var/lib/route-proxy/acme-ca.json"},
Receives: map[string]string{"route": "/var/lib/route-proxy/routes.json"},
Resources: []map[string]any{{
"id": "bundle", "type": "file", "path": "/var/lib/route-proxy/ca.pem", "mode": "0644",
"content": "${bound:acme-ca:root}",
}},
}
}
// A co-located provider's served VALUES reach its consumer, not just its served keys.
//
// The mesh walks a provider on ANOTHER machine and settles what it serves with that node's settings
// layers before offering it (cmd/mesh-controller plan.go, theRestOfTheMesh). A provider on the
// consumer's own machine was never settled at all: resolve.go's servedHere and declaration.go's
// here() both read the manifest and stop there. So a served value the operator supplied — the one
// kind of value a manifest cannot carry, because it is different on every mesh — arrived as the
// manifest's empty default.
//
// The consequence is silent and total: route-proxy wrote an empty CA bundle, fell back to the
// system trust store, could not verify the internal authority, and no certificate was ever issued.
func TestACoLocatedProvidersServedValuesReachItsConsumer(t *testing.T) {
r, err := Resolve(shelf(stepCA(), routeProxy()),
[]string{"step-ca", "route-proxy"}, reachable(), World{})
if err != nil {
t.Fatal(err)
}
// What the operator set on the provider, on this node — the CA's root, which only exists once
// the CA has been initialised.
out, err := r.Declaration(Rendering{Settings: SettingsBy{
"step-ca": {{From: "the operator", Values: map[string]any{"root": servedRoot}}},
}})
if err != nil {
t.Fatal(err)
}
bundle := fileNamed(out, "route-proxy.bundle")
if bundle == nil {
t.Fatalf("the consumer was given no bundle at all: %v", out)
}
if got := bundle["content"]; got != servedRoot {
t.Errorf("the co-located consumer was not told the root it must trust:\n got %q\nwant %q",
got, servedRoot)
}
}
// And the binding file says the same thing, for a consumer that reads the binding rather than a
// substituted placeholder. The two are one fact and must not be able to disagree.
func TestACoLocatedConsumersBindingCarriesTheSettledValues(t *testing.T) {
r, err := Resolve(shelf(stepCA(), routeProxy()),
[]string{"step-ca", "route-proxy"}, reachable(), World{})
if err != nil {
t.Fatal(err)
}
out, err := r.Declaration(Rendering{
Settings: SettingsBy{
"step-ca": {{From: "the operator", Values: map[string]any{"root": servedRoot}}},
},
// And the machine moved the provider's port while it was at it, so both halves of the
// cross-node derivation are exercised at once.
Ports: map[string]map[int]int{"step-ca": {9000: 19000}},
})
if err != nil {
t.Fatal(err)
}
binding := fileNamed(out, "route-proxy.bound-acme-ca")
if binding == nil {
t.Fatalf("the consumer was given no binding at all: %v", out)
}
var said struct {
Serves map[string]any `json:"serves"`
}
if err := json.Unmarshal([]byte(binding["content"].(string)), &said); err != nil {
t.Fatal(err)
}
if said.Serves["root"] != servedRoot {
t.Errorf("the binding does not carry the settled root: %q", said.Serves["root"])
}
if port, _ := asPort(said.Serves["port"]); port != 19000 {
t.Errorf("the binding does not carry the port the machine publishes: %v", said.Serves["port"])
}
}
// A provider PULLED IN rather than assigned is settled the same way.
//
// The resolver's same-node need is built during the walk, from whichever modules had been chosen by
// the time the requirement came up — so which path a co-located binding took depended on the order
// a person happened to assign things in. Settling after the closure is known removes that: both
// orders now produce the same file.
func TestACoLocatedProviderIsSettledWhicheverWayItWasPulledIn(t *testing.T) {
for _, assigned := range [][]string{
{"step-ca", "route-proxy"},
{"route-proxy", "step-ca"},
} {
r, err := Resolve(shelf(stepCA(), routeProxy()), assigned, reachable(), World{})
if err != nil {
t.Fatal(err)
}
out, err := r.Declaration(Rendering{Settings: SettingsBy{
"step-ca": {{From: "the operator", Values: map[string]any{"root": servedRoot}}},
}})
if err != nil {
t.Fatal(err)
}
bundle := fileNamed(out, "route-proxy.bundle")
if bundle == nil || bundle["content"] != servedRoot {
t.Errorf("assigned %v: the consumer was not told the root", assigned)
}
}
}
// A same-node contribution names the port the MACHINE publishes, not the one the module declared.
//
// 04-ISSUES/038 fixed this for what a consumer is TOLD about a provider. This is the other
// direction: what a workload TELLS the provider about itself. gitea declares a bare container port
// 3000, the mesh publishes it as 20000:3000 — and gitea's route contribution still said 3000, so
// the proxy beside it dialled a port nothing listens on and answered 502 for every request.
//
// The redirect uses the CONTRIBUTING module's assignment: the port belongs to the workload, and
// using the provider's map would move it to wherever the proxy happens to be published.
func TestASameNodeContributionNamesThePortTheMachinePublishes(t *testing.T) {
gitea := Manifest{
Module: "gitea", Version: "1",
Listens: []Listening{{Port: 3000, Protocol: "tcp", From: FromMesh}},
Contributes: map[string]map[string]any{"route": {"label": "git", "port": 3000}},
Resources: []map[string]any{{
"id": "server", "type": "container", "name": "gitea", "ports": []any{"3000"},
}},
}
r, err := Resolve(shelf(gitea, routeProxy(), stepCA()),
[]string{"gitea", "route-proxy", "step-ca"}, reachable(), World{})
if err != nil {
t.Fatal(err)
}
out, err := r.Declaration(Rendering{
// The machine put gitea's 3000 on 20000, and published it that way.
Ports: map[string]map[int]int{"gitea": {3000: 20000}},
})
if err != nil {
t.Fatal(err)
}
// Where the workload is actually published.
server := fileNamed(out, "gitea.server")
if published := strings.Join(asStrings(server["ports"]), ","); published != "20000:3000" {
t.Fatalf("the workload was not published on the assigned port: %v", server["ports"])
}
// What the proxy beside it was told to dial: the SAME port.
routes := fileNamed(out, "route-proxy.received-route")
if routes == nil {
t.Fatalf("the proxy was given no routes file at all: %v", out)
}
var given struct {
Given []Contribution `json:"given"`
}
if err := json.Unmarshal([]byte(routes["content"].(string)), &given); err != nil {
t.Fatal(err)
}
if len(given.Given) != 1 {
t.Fatalf("expected one route, got %v", given.Given)
}
port, ok := asPort(given.Given[0].Values["port"])
if !ok || port != 20000 {
t.Errorf("the proxy was told to dial a port nothing listens on: %v",
given.Given[0].Values["port"])
}
}
// A contribution from ANOTHER machine is left exactly as it was.
//
// Its port belongs to that machine's assignment, which this node's map knows nothing about —
// applying this node's map to it would move a remote workload's port to wherever a local module of
// the same name happens to be published, which is worse than the fault being fixed.
func TestAContributionFromAnotherMachineKeepsItsOwnPort(t *testing.T) {
r, err := Resolve(shelf(routeProxy(), stepCA()),
[]string{"route-proxy", "step-ca"}, reachable(), World{})
if err != nil {
t.Fatal(err)
}
out, err := r.Declaration(Rendering{
// A same-named module on this machine, moved. The grant below is a laptop's, and must not
// be dragged along with it.
Ports: map[string]map[int]int{"gitea": {3000: 20000}},
Grants: []Grant{{
Provision: "route", Consumer: "laptop", From: "gitea", At: "laptop.internal",
Values: map[string]any{"name": "git.example", "port": 3000},
}},
})
if err != nil {
t.Fatal(err)
}
routes := fileNamed(out, "route-proxy.received-route")
var given struct {
Given []Contribution `json:"given"`
}
if err := json.Unmarshal([]byte(routes["content"].(string)), &given); err != nil {
t.Fatal(err)
}
if len(given.Given) != 1 {
t.Fatalf("expected one route, got %v", given.Given)
}
if port, _ := asPort(given.Given[0].Values["port"]); port != 3000 {
t.Errorf("another machine's contribution was rewritten with this machine's ports: %v",
given.Given[0].Values["port"])
}
}
// The cross-node half of the same fault: a grant assembled on the providing machine carries the
// consumer's declared port until the CONSUMER's machine's assignments are applied to it. The
// declaration layer cannot do it — those assignments are not this machine's, which is the line the
// test above holds — so the grant layer is handed them and does it there.
func TestAContributionIsRedirectedToWhereItsOwnMachinePublishedIt(t *testing.T) {
published := map[int]int{3000: 20000}
got := AtPublishedPort(map[string]any{"name": "git.example.tld", "port": 3000}, "forge", published)
if port, _ := asPort(got["port"]); port != 20000 {
t.Errorf("the proxy would dial a port that machine never published: %v", got["port"])
}
if got["name"] != "git.example.tld" {
t.Errorf("redirecting the port disturbed the rest of the contribution: %v", got)
}
// Idempotent, and silent about a port nobody moved: a module that pinned its own mapping has no
// assignment, and must come back exactly as it was written.
again := AtPublishedPort(got, "forge", published)
if port, _ := asPort(again["port"]); port != 20000 {
t.Errorf("applying it twice moved the port again: %v", again["port"])
}
pinned := AtPublishedPort(map[string]any{"port": 9070}, "office", published)
if port, _ := asPort(pinned["port"]); port != 9070 {
t.Errorf("a port the mesh never assigned was rewritten: %v", pinned["port"])
}
}
func asStrings(v any) []string {
listed, ok := v.([]any)
if !ok {
return nil
}
out := make([]string, 0, len(listed))
for _, one := range listed {
out = append(out, strings.TrimSpace(plainly(one)))
}
return out
}