From 946fddd62200fce86cbcf9f60821c64d91fbf0df Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 10 Sep 2026 21:12:04 +0200 Subject: [PATCH] catalogue: a module may name the machine it was assigned to MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An authority inside the mesh is reached at .internal, so its own certificate must be issued for that name — and it is the one module that cannot be told its name by a binding, because it provides rather than requires. Written as a literal it would be one deployment's machine name in a manifest, which is what ADR 0056 exists to remove. ${machine:name} and ${machine:at}, beside the bound values and refused the same way. An address the machine does not have is named here rather than discovered later as a certificate nobody can verify. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF --- internal/catalogue/declaration.go | 9 ++ internal/catalogue/machine_into_files.go | 94 +++++++++++++++++++ internal/catalogue/machine_into_files_test.go | 72 ++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 internal/catalogue/machine_into_files.go create mode 100644 internal/catalogue/machine_into_files_test.go diff --git a/internal/catalogue/declaration.go b/internal/catalogue/declaration.go index a029d65..3eb0df4 100644 --- a/internal/catalogue/declaration.go +++ b/internal/catalogue/declaration.go @@ -421,6 +421,8 @@ func (r Resolution) Declaration(with Rendering) ([]map[string]any, error) { } // And what its bindings say, for the half of a connection that is not secret. known := knownFor(m, r.Needs, r.Node) + // And the machine underneath, which no binding of its own can tell it. + thisMachine := machineFacts(r) for _, unsettled := range resources { resource, err := ApplySettings(unsettled, with.Settings[m.Module]) @@ -444,6 +446,13 @@ func (r Resolution) Declaration(with Rendering) ([]map[string]any, error) { if err := boundInto(copied, known, m.Module); err != nil { return nil, err } + // And what the module could not have written: the machine it turned out to be + // assigned to. Beside the bound values because it is the same kind of fact — the + // mesh's own, held in the clear — and because a module that must name itself to + // something else has no binding to learn it from (novox/hq ADR 0056). + if err := machineInto(copied, thisMachine, m.Module); err != nil { + return nil, err + } if err := pinned(copied, m.Module); err != nil { return nil, err } diff --git a/internal/catalogue/machine_into_files.go b/internal/catalogue/machine_into_files.go new file mode 100644 index 0000000..302f392 --- /dev/null +++ b/internal/catalogue/machine_into_files.go @@ -0,0 +1,94 @@ +package catalogue + +import ( + "fmt" + "regexp" + "sort" + "strings" +) + +// What a module may say about the machine it is running on. +// +// **A module cannot know where it will be assigned, and sometimes it must say so anyway.** Every +// other name in a declaration is either the module's own — which it wrote — or something it +// requires, which arrives as a binding. The machine underneath is neither: it is chosen when the +// module is assigned, long after the manifest was written, and until now nothing carried it into a +// file. +// +// The case that found this is a certificate authority inside the mesh (novox/hq ADR 0056). A proxy +// reaches it at the address the mesh handed over, `.internal` — so the authority's own +// certificate has to be issued for that name, or the first thing that happens is the proxy refusing +// to talk to it. The authority is the one thing that cannot be told its name by a binding: it +// provides, it does not require. Written as a literal it would be a manifest carrying one +// deployment's machine name, which is the shape [ADR 0056] exists to remove. +// +// Two facts, both the mesh's own vocabulary — the same `node` and `at` a contribution already +// carries. Nothing about what a machine is *for*: that would be the mesh learning what a module +// means, which it does not do. + +// ofMachine is where a module says a fact about the machine underneath it belongs: +// ${machine:}. +var ofMachine = regexp.MustCompile(`\$\{machine:([a-z0-9][a-z0-9_-]*)\}`) + +// machineUsed are the keys a file's content asks for, first appearance first. +func machineUsed(content string) []string { + var used []string + seen := map[string]bool{} + for _, m := range ofMachine.FindAllStringSubmatch(content, -1) { + if key := m[1]; !seen[key] { + seen[key] = true + used = append(used, key) + } + } + return used +} + +// machineFacts is what a module may name about the machine it was assigned to. +// +// `at` is absent rather than empty when the machine is not on the private network. A module asking +// to be reached at an address that does not exist is a misconfiguration, and it is said here — +// where the module and the machine are both named — rather than discovered later as a certificate +// nobody can verify. +func machineFacts(r Resolution) map[string]string { + out := map[string]string{"name": r.Node} + if r.At != "" { + out["at"] = r.At + } + return out +} + +// machineInto replaces a file's ${machine:…} placeholders with what the mesh knows about the +// machine the module was assigned to. +// +// A key the mesh does not hold is refused, for the same reason a binding's is: left alone, the +// literal would be written into a configuration file and read as a value. +func machineInto(resource map[string]any, facts map[string]string, module string) error { + if fmt.Sprint(resource["type"]) != "file" { + return nil + } + content, ok := resource["content"].(string) + if !ok { + return nil + } + for _, key := range machineUsed(content) { + value, has := facts[key] + if !has { + return fmt.Errorf( + "%s has a file that says ${machine:%s}, and this machine says %s", + module, key, orNothing(namesOfFacts(facts))) + } + resource["content"] = strings.ReplaceAll( + content, fmt.Sprintf("${machine:%s}", key), value) + content = resource["content"].(string) + } + return nil +} + +func namesOfFacts(facts map[string]string) []string { + out := make([]string, 0, len(facts)) + for k := range facts { + out = append(out, k) + } + sort.Strings(out) + return out +} diff --git a/internal/catalogue/machine_into_files_test.go b/internal/catalogue/machine_into_files_test.go new file mode 100644 index 0000000..1749fef --- /dev/null +++ b/internal/catalogue/machine_into_files_test.go @@ -0,0 +1,72 @@ +package catalogue + +import ( + "strings" + "testing" +) + +// novox/hq ADR 0056 — a module that must name ITSELF to something else. +// +// The authority inside the mesh is the case. A proxy reaches it at the address the mesh handed +// over, so its certificate has to be issued for that name — and it has no binding to learn the name +// from, because it provides rather than requires. Written as a literal it would be one deployment's +// machine name living in a manifest, which is the shape the decision exists to remove. + +// anchored is a machine on the private network, which is where a name worth certifying comes from. +func anchored() Node { + n := workstation() + n.At = "workstation.internal" + return n +} + +// authority is a module that must put its own machine's name into a file it writes. +func authority() Manifest { + return Manifest{ + Module: "authority", Version: "1", + Provides: FromAnywhere("acme-ca"), + Serves: map[string]map[string]any{"acme-ca": {}}, + Resources: []map[string]any{{ + "id": "init", "type": "file", "path": "/var/lib/authority/init.env", + "content": "NAMES=${machine:at},${machine:name},localhost\n", + }}, + } +} + +func TestAModuleNamesTheMachineItWasAssignedTo(t *testing.T) { + got, err := Resolve(shelf(authority()), []string{"authority"}, anchored(), World{}) + if err != nil { + t.Fatal(err) + } + + content := "" + for _, r := range mustDeclare(t, got) { + if strings.HasSuffix(plainly(r["id"]), "init") { + content = plainly(r["content"]) + } + } + if content == "" { + t.Fatal("the authority's file was not declared") + } + if strings.Contains(content, "${machine:") { + t.Fatalf("the placeholder was written into the file as a value: %q", content) + } + // The machine's own name on the private network — the one a consumer is handed as `at`, and so + // the one a certificate has to carry. + if !strings.Contains(content, "workstation.internal") { + t.Errorf("the file does not name the address consumers reach it at: %q", content) + } +} + +// A machine not on the private network has no such address, and a module asking to be reached at +// one is named here rather than left to fail later as a certificate nobody can verify. +func TestAnAddressAMachineDoesNotHaveIsRefused(t *testing.T) { + got, err := Resolve(shelf(authority()), []string{"authority"}, workstation(), World{}) + if err != nil { + t.Fatal(err) + } + if _, err := got.Declaration(Rendering{}); err == nil { + t.Fatal("an address the machine does not have was substituted rather than refused") + } else if !strings.Contains(err.Error(), "at") { + t.Errorf("the refusal does not name what was asked for: %v", err) + } +}