From c421629f6e254fd056d584a16641e94f73a9a40d Mon Sep 17 00:00:00 2001 From: jochen Date: Fri, 30 Oct 2026 18:19:26 +0100 Subject: [PATCH] Ask the vault for a bus process's account only once it is issued, so the vault never makes a random value under an account's name (issue 774) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At 16:36:51 the vault handed own/novox/nats/broker-nats-tools to the controller as a plain random own secret, and owes the bus-account hashes of it and of the controller's broker-address and broker-management for good ("holds no account the controller hands (not JSON)"). The controller decided that: its send asks the vault (`get`) for every own secret of a core module, a bus process's account among them, before the account is issued; the vault, holding none, makes one. The send now leaves out of that ask a process's account nobody issued (the same rule as its warning line); the machine keeps the controller's stand-in until the account is issued and handed to the vault. The vault side (not minting an account-shaped name, and replacing the random value already held) is mesh-catalog's mesh-vault, with build-catalog. Quick fix: this filter on the ask. Thorough fix: process accounts minted by the vault itself (ADR 0113 ยง6, research 072 milestone 6), so no part asks for one before it exists. Chose quick: milestone 6 is not started, and the filter removes the cause on the controller's side now. Research 072 piece: milestone 6 (identity and bus accounts to the vault): until then the controller asks the vault only for what exists. --- cmd/mesh-controller/compose_perform.go | 2 +- cmd/mesh-controller/core_box.go | 15 +++++++++---- cmd/mesh-controller/core_box_test.go | 29 +++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/cmd/mesh-controller/compose_perform.go b/cmd/mesh-controller/compose_perform.go index 6e05f2bd..b07f6181 100644 --- a/cmd/mesh-controller/compose_perform.go +++ b/cmd/mesh-controller/compose_perform.go @@ -250,7 +250,7 @@ func prepareSend(ctx context.Context, open *stores, node string) ([]string, erro if n.Stated && n.Contract >= compose.VaultBoxContract { boxes, err := coreBoxesFor(ctx, inv, node, resolved.Plan.Modules, func(m catalogue.Manifest) bool { return handing.Handing(m) - }, true) + }, true, in.BusMinted) if err != nil { return nil, err } diff --git a/cmd/mesh-controller/core_box.go b/cmd/mesh-controller/core_box.go index a86d1f68..f165c470 100644 --- a/cmd/mesh-controller/core_box.go +++ b/cmd/mesh-controller/core_box.go @@ -69,7 +69,14 @@ type coreBoxes struct { // coreModulesOf is every module of the plan on the core's list whose own secrets this machine's declaration carries, // with their plain own secret names, sorted: one handed to the provisioner has its secrets from the vault through the // client, and one issued outside the mesh is never the vault's to seal here. -func coreModulesOf(modules []catalogue.Manifest, handed func(catalogue.Manifest) bool) map[string][]string { +// +// **Nor a process's bus account nobody issued yet** (sweep 2026-10-30, issue 774). Asked with `get`, the vault holds +// none and makes a random value under the account's name: own/novox/nats/broker-nats-tools at 16:36:51, and the +// controller's own broker-address and broker-management, which the vault then finds holding no account ("not JSON") +// and owes their hashes for good. Until the controller issues the account and hands it to the vault, the machine has +// the controller's stand-in (prepareSend), as every module below the vault's contract does. +func coreModulesOf(modules []catalogue.Manifest, handed func(catalogue.Manifest) bool, node string, + minted []string) map[string][]string { out := map[string][]string{} for _, m := range modules { if !catalogue.CarriesVaultSealed(m.Module) || (handed != nil && handed(m)) { @@ -77,7 +84,7 @@ func coreModulesOf(modules []catalogue.Manifest, handed func(catalogue.Manifest) } var names []string for name, own := range m.OwnSecrets.Plain() { - if own.IssuedBy != catalogue.IssuedOutside { + if own.IssuedBy != catalogue.IssuedOutside && processAccountWarning(m, node, name, minted) == "" { names = append(names, name) } } @@ -208,8 +215,8 @@ var coreVault = func(ctx context.Context) coreAsk { // coreBoxesFor is coreBoxesOf for one machine, read from and kept in the store, and said on a send. func coreBoxesFor(ctx context.Context, inv *inventory.Inventory, node string, modules []catalogue.Manifest, - handed func(catalogue.Manifest) bool, allocating bool) (coreBoxes, error) { - core := coreModulesOf(modules, handed) + handed func(catalogue.Manifest) bool, allocating bool, minted []string) (coreBoxes, error) { + core := coreModulesOf(modules, handed, node, minted) if len(core) == 0 { if allocating { settleVaultSilent(ctx, node) diff --git a/cmd/mesh-controller/core_box_test.go b/cmd/mesh-controller/core_box_test.go index 1148d0a1..f9f4da81 100644 --- a/cmd/mesh-controller/core_box_test.go +++ b/cmd/mesh-controller/core_box_test.go @@ -124,8 +124,35 @@ func TestTheCoresModulesAreTheListsWithTheirOwnSecrets(t *testing.T) { {Module: "postgres", OwnSecrets: catalogue.OwnSecrets{"superuser": {Path: "/c"}}}, {Module: "mesh-provisioner", OwnSecrets: catalogue.OwnSecrets{"broker": {Path: "/d"}}}, } - got := coreModulesOf(modules, func(m catalogue.Manifest) bool { return m.Module == "mesh-provisioner" }) + got := coreModulesOf(modules, func(m catalogue.Manifest) bool { return m.Module == "mesh-provisioner" }, "anchor", nil) if want := map[string][]string{"nats": {"broker"}}; !reflect.DeepEqual(got, want) { t.Fatalf("got %v, want %v", got, want) } } + +// Sweep 2026-10-30 (issue 774): a bus process's account is asked of the vault only once it is issued; before, the vault +// would make a random value under the account's name. +func TestAnUnissuedProcessAccountIsNotAskedOfTheVault(t *testing.T) { + nats, err := catalogue.ParseManifest([]byte(`{"module":"nats","version":"1","emits":["x"], + "own-secrets":{"broker":{"path":"/a"},"broker-nats-tools":{"path":"/b","owner":"nats-tools"}}, + "resources":[{"id":"account","type":"user","name":"nats-tools"}, + {"id":"nats-tools","type":"process","run":["./nats-tools"],"user":"nats-tools","bus":{"emits":["x"]}}]}`)) + if err != nil { + t.Fatal(err) + } + if len(nats.ProcessBuses()) == 0 { + t.Fatal("the fixture declares no bus process") + } + for name, c := range map[string]struct { + minted []string + want []string + }{ + "not issued": {nil, []string{"broker"}}, + "issued": {[]string{processUser("anchor", "nats", "nats-tools")}, []string{"broker", "broker-nats-tools"}}, + } { + got := coreModulesOf([]catalogue.Manifest{nats}, nil, "anchor", c.minted) + if !reflect.DeepEqual(got["nats"], c.want) { + t.Errorf("%s: asks %v, want %v", name, got["nats"], c.want) + } + } +}