From 8a428e3e8648a414b81bcb608bae2b889cc121cc Mon Sep 17 00:00:00 2001 From: mesh-admin Date: Wed, 14 Oct 2026 19:06:04 +0000 Subject: [PATCH] Send secret verbs to the vault for a module filled the new way (issue 675) ADR 0349 clauses 8 to 10 make accept, secret-ask, issued and rotate the vault's. A module still filled the old way gets its secrets only from the declaration the controller seals, so forwarding its verbs would report success and change nothing on its machine: the switch sends a module's verbs to the vault only once its secrets are filled the new way, and is off for every module until V4. What the controller carries it cannot open: the vault makes a key for each call; the terminal seals an accepted value to it, and the desk's prompt and a provider's tool seal theirs to it. The vault's seat promises the four verbs, optional until the vault that runs serves them, and the controller asks them in its own name, granted from the records as the issued tools are, so the installer's first user list is unchanged. Co-Authored-By: Claude Opus 5.5 --- cmd/mesh-controller/desk_secret.go | 11 ++++++ cmd/mesh-controller/issued.go | 11 +++++- cmd/mesh-controller/secret.go | 58 +++++++++++++++++++++++++++--- 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/cmd/mesh-controller/desk_secret.go b/cmd/mesh-controller/desk_secret.go index a0da69d4..bc686c7e 100644 --- a/cmd/mesh-controller/desk_secret.go +++ b/cmd/mesh-controller/desk_secret.go @@ -67,6 +67,9 @@ type deskGive struct { // nil keeps no record (a test that does not look). end closes it with how it ended. open func(node, module, name, desk string) (int64, error) end func(id int64, outcome string) error + // vault, when set, is the path of a module whose secrets are filled the new way (vault_secrets.go): the vault + // records the ask, holds its bounds and opens what is typed; open, end and accept are not used. + vault vaultAsker } // askedByName is the caller as the prompt names it: the first clause of what the bus said, in the characters @@ -133,6 +136,9 @@ func (d deskGive) give(node, module, name, desk string) (string, error) { "bus, where an agent may answer first (novox/hq ADR 0259 ยง10)", module, name, node, module, name) } } + if d.vault != nil { + return d.giveToVault(node, module, name, desk) + } // The bounds, read and kept before anybody is asked to type (novox/hq ADR 0277): one open prompt per secret, // few an hour. How the ask ends is recorded whatever happens below. outcome := "failed" @@ -275,6 +281,11 @@ func askAtDesk(ctx context.Context, node, module, name, desk string) error { }, announce: func(node, module, name, how string) error { return announceSecretGiven(ctx, node, module, name, how) }, } + // A module whose secrets are filled the new way is asked for by the vault (ADR 0349 clause 9); every other + // keeps today's path (clause 17). + if secretsOfTheVault(module) { + d.vault = theVault(ctx) + } words, err := d.give(node, module, name, desk) if err != nil { return err diff --git a/cmd/mesh-controller/issued.go b/cmd/mesh-controller/issued.go index 1f1b348c..1cce77d4 100644 --- a/cmd/mesh-controller/issued.go +++ b/cmd/mesh-controller/issued.go @@ -53,6 +53,10 @@ type issuedTaker struct { keep func(provision, provider, value string) (inventory.IssuedKept, error) record func(act link.HandAct) error send func(machines []string) error + // ofTheVault says whether a provider module's secrets are filled the new way; for one that is, the value goes + // into the vault (vault, vault_secrets.go) and nothing is kept, sealed or sent here. Nil is none. + ofTheVault func(module string) bool + vault vaultAsker } // take asks every provider of the provision for its software's value and keeps each; it answers what it did, in @@ -90,6 +94,9 @@ func (t issuedTaker) take(provision string) (string, error) { // takeFrom is one provider: asked, opened, kept, recorded and sent when it changed. func (t issuedTaker) takeFrom(provision string, p issuedProvider) (string, error) { + if t.ofTheVault != nil && t.ofTheVault(p.Module) { + return t.takeIntoVault(provision, p) + } public, private, err := secrets.Keypair() if err != nil { return "", fmt.Errorf("no key could be made to take the value: %w", err) @@ -262,7 +269,9 @@ func secretIssued(ctx context.Context, args []string) error { return err }) }, - send: func(machines []string) error { return sendTo(ctx, open, machines) }, + send: func(machines []string) error { return sendTo(ctx, open, machines) }, + ofTheVault: secretsOfTheVault, + vault: theVault(ctx), } words, err := t.take(provision) if words != "" { diff --git a/cmd/mesh-controller/secret.go b/cmd/mesh-controller/secret.go index 373dbd7b..ad5529c4 100644 --- a/cmd/mesh-controller/secret.go +++ b/cmd/mesh-controller/secret.go @@ -108,7 +108,12 @@ func secretCommand(ctx context.Context, args []string) error { if value == "" { return errors.New("there is nothing to seal") } - return acceptForProvision(ctx, node, module, name, value) + // The provider's module decides the path: filled the new way, the vault keeps the provision's credential. + return bySwitch(module, secretsOfTheVault, + func() error { return acceptForProvision(ctx, node, module, name, value) }, + func() error { + return said(acceptIntoVault(theVault(ctx), provisionArgs(node, name), value, actingAs())) + }) } if *desk != "" { if *from != "" || *provider != "" { @@ -125,23 +130,50 @@ func secretCommand(ctx context.Context, args []string) error { if value == "" { return errors.New("there is nothing to seal") } + return bySwitch(module, secretsOfTheVault, + func() error { return acceptKept(ctx, node, module, name, *provider, *local, value) }, + func() error { + // Sealed here to the vault's key for this one call; only the sealed box leaves this process. + target := ownSecretArgs(node, module, name) + if *provider != "" { + if *local != "" { + return errors.New("--local names a credential inside the module; the vault keeps one credential " + + "per provider and provision, so it takes no --local: nothing was sealed") + } + target = provisionArgs(*provider, name) + } + return said(acceptIntoVault(theVault(ctx), target, value, actingAs())) + }) +} +// said prints what was done, or answers why not. +func said(words string, err error) error { + if err != nil { + return err + } + fmt.Println(words) + return nil +} + +// acceptKept is today's `secret accept`: the value sealed into the controller's store, for a module whose secrets +// are filled the old way. +func acceptKept(ctx context.Context, node, module, name, provider, local, value string) error { open, err := openStores(ctx) if err != nil { return err } defer open.Close() - if *provider != "" { + if provider != "" { // Into the pair, not into the module's own secrets: what the provider is asked to create // and what the consumer reads are the same value, and neither end can be told a different // one later without the other (novox/hq 04-ISSUES/070). - if err := open.inventory.AcceptSecretForPair(ctx, name, node, module, *provider, *local, value); err != nil { + if err := open.inventory.AcceptSecretForPair(ctx, name, node, module, provider, local, value); err != nil { return err } - fmt.Printf("%s on %s now holds %q from %s%s, sealed to both machines.\n", module, node, name, *provider, asLocal(*local)) + fmt.Printf("%s on %s now holds %q from %s%s, sealed to both machines.\n", module, node, name, provider, asLocal(local)) fmt.Printf(" the mesh cannot read it back, will not replace it with one of its own, and will not rotate it\n") - fmt.Printf(" run `push %s` and `push %s` to send it\n", *provider, node) + fmt.Printf(" run `push %s` and `push %s` to send it\n", provider, node) return nil } // A trusted party's secret is announced before it is kept (the confirmation review of 2026-10-09, N1-give): @@ -485,6 +517,22 @@ func secretRotate(ctx context.Context, args []string) error { return errors.New(secretUsage) } node, module, name := rest[0], rest[1], rest[2] + return bySwitch(module, secretsOfTheVault, + func() error { return rotateKept(ctx, node, module, name, why) }, + func() error { + words, err := rotateInVault(theVault(ctx), ownSecretArgs(node, module, name), actingAs()) + if err != nil { + return err + } + why.record(ctx, "secret rotate", []string{node, module, name}) + fmt.Println(words) + return nil + }) +} + +// rotateKept is today's `secret rotate`: made anew in the controller's store and sent, for a module whose +// secrets are filled the old way. +func rotateKept(ctx context.Context, node, module, name string, why handActFlags) error { open, err := openStores(ctx) if err != nil { return err