Files
mesh-controller/cmd/mesh-controller/issued_to_vault.go
T
jschoubben 7a169eae76
mesh/delivery merged: source.main moved to novox/mesh-controller@c58b311ea774
mesh/delivery-group group feat/072-moves merged: every member is merged
mesh/check failed: shanks: the check of novox/mesh-controller#487 failed: go mod download in modules/route-proxy: go: git.novox.be/novox/mesh-controll…
Keep the controller on main's mesh-sdk pin and mint until the grants part takes over, so its impact stays small and no account is minted that the bus it loads refuses (issue 774)
The controller is being removed, so its root module goes back to origin/main's mesh-sdk pin (2da0de7) and
testdata/beside's capture; route-proxy keeps its own pins. Its one functional change stays the account issue:

- Before the grants part's take-over (its key user-list.sealed not saying FollowGrants) the bus loads the
  controller's list, whose hashes come only from its store, so the controller mints as before and hands every
  account it mints, covered or not, to an installed vault at once: no account waits on a node reporting its hash.
- From the take-over it forwards the issue to the vault's issue-account (d791052f) and mints nothing.

And the controller no longer declares or asserts the ASSIGNMENTS stream, which the bus module's tools delete at the
lease write and nothing may make again (research 072 row 33); the verified-sender re-issue stops once it is gone.

Research 072 piece: the gap "vault mints after the lease", the minting piece moves to the vault from the take-over.
2026-10-31 10:37:13 +01:00

108 lines
5.0 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"github.com/nats-io/nats.go"
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-sdk/go/catalogue"
sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery"
"github.com/novox/mesh-controller/internal/broker"
"github.com/novox/mesh-controller/internal/inventory"
)
// A covered account is handed to the vault when it is minted (novox/hq ADR 0374, issue 787).
//
// **Covered** are the bus accounts `module issue` mints for a core module (catalogue.CoreCredentialModules) or for a
// module marked moved; no other account is. Until the vault makes bus accounts (ADR 0113 clause 6, issue 774) the
// controller mints such an account and writes its hash to the bus's user list, as before, and then hands the whole
// value at once to the vault's `accept` with origin `issued`: sealed here to the key the vault made for that call, so
// the vault alone opens it (ADR 0349 clause 8). The vault keeps it as the module's own secret on that machine and
// raises `secret-changed`; a re-issue is the same hand with a new value, which replaces the old in place. Until issue 774 an
// account not covered was minted and kept as it was and handed to nobody.
//
// **Since issue 774 every account the controller mints is handed, covered or not**, once a vault is installed: until
// the grants part's take-over the controller still mints (the bus loads its list), and a vault that holds every value
// at once needs no node to report a hash for it to adopt (ADR 0547 clause 9). From the take-over the vault mints every
// account itself (issueAccount forwards to its issue-account), so this hand is reached only before it, and by
// `rollout mint`'s re-issue of a module's account onto a new bus. The vault never lets such a hand replace an account it
// minted.
// vaultAskerForIssue is the vault as `module issue` asks it. A test replaces it.
var vaultAskerForIssue = func(ctx context.Context) vaultAsker { return theVault(ctx) }
// vaultInstalled says whether the vault is assigned to any machine. Until it is, as in genesis (ADR 0113 §15 to §18,
// mesh-host's bootstrap, which issues the bus's and the provisioner's accounts before the vault step), there is no
// vault to hand to: the value is kept in the controller's store as before, and the vault takes it afterwards by the
// move (move-in takes a value of the controller's store with origin made, accepted or issued, as it is). Only a
// vault that is installed and does not answer fails the issue.
func vaultInstalled(ctx context.Context, inv *inventory.Inventory) (bool, error) {
entries, err := inv.Catalogued(ctx)
if err != nil {
return false, err
}
for _, e := range entries {
if e.Manifest.Module == "mesh-vault" && len(e.On) > 0 {
return true, nil
}
}
return false, nil
}
// grantsTookOver says whether the grants part took over the bus's user list: its key `user-list.sealed` says
// FollowGrants (novox/hq ADR 0547 clauses 7 and 8), which the bus node's engine follows. No bucket or no key yet is no
// take-over; a bus that cannot be read is an error, never a guess. A test replaces it.
var grantsTookOver = func(ctx context.Context) (bool, error) {
over := false
err := onTheBus(func(conn *nats.Conn) error {
js, err := jetstream.New(conn)
if err != nil {
return err
}
kv, err := js.KeyValue(ctx, broker.GrantsUserListBucket)
if errors.Is(err, jetstream.ErrBucketNotFound) {
return nil
} else if err != nil {
return err
}
e, err := kv.Get(ctx, broker.GrantsUserListKey)
if errors.Is(err, jetstream.ErrKeyNotFound) {
return nil
} else if err != nil {
return err
}
g, err := sdkdelivery.DecodeGrantsUserList(e.Value())
if err != nil {
return err
}
over = g.Follow == sdkdelivery.FollowGrants
return nil
})
if err != nil {
return false, fmt.Errorf("whether the grants part took over the bus's user list cannot be read: %w", err)
}
return over, nil
}
// handIssuedToVault hands the value just minted for module's own secret name on node to the vault, once one is
// installed, covered or not (issue 774), and does nothing before. Loud when the vault does not take it: the bus's user
// list already holds the new hash, so a vault that kept the old value would serve a password that no longer logs in.
func handIssuedToVault(ctx context.Context, inv *inventory.Inventory, m catalogue.Manifest, node, name, held string) error {
if installed, err := vaultInstalled(ctx, inv); err != nil {
return fmt.Errorf("whether the vault is installed cannot be read, so %s of %s was not handed to it: %w", name,
m.Module, err)
} else if !installed {
return nil
}
target := ownSecretArgs(node, m.Module, name)
target["origin"] = "issued"
if _, err := acceptIntoVault(vaultAskerForIssue(ctx), target, held, "module issue"); err != nil {
return fmt.Errorf("the credential is minted and kept here, and the bus's user list holds its hash, but the "+
"vault did not take %s of %s on %s: %w (`module issue` again hands a new one)", name, m.Module, node, err)
}
return nil
}