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, in the shape a certificate authority serves one. const servedRoot = `-----BEGIN CERTIFICATE----- MIIBeDCCAR2gAwIBAgIQfake0000000000000000000000 -----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 }