mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
One commit with exactly the tree of the reviewed head35b4509c0a, on main12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
332 lines
12 KiB
Go
332 lines
12 KiB
Go
// mesh-vault (novox/hq ADR 0085, ADR 0113, ADR 0349 §7 and §11): one binary, launched by its node's runtime
|
|
// and speaking MCP to it over stdio through the Go SDK. It holds the `mesh-vault` seat: it keeps every
|
|
// secret in its own store, sealed at rest and with a copy sealed to the operator key; it answers the seat's
|
|
// verb `get` with a secret sealed to the asker's key; it raises `secret-changed` without a value. Beside
|
|
// that it keeps the custody ledger of the pair credentials the controller still delivers to it.
|
|
//
|
|
// stdout is the MCP channel; everything this module says, it says on stderr.
|
|
package main
|
|
|
|
import (
|
|
"git.novox.be/novox/mesh-sdk/go/provisions"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
)
|
|
|
|
func say(format string, args ...any) {
|
|
fmt.Fprintf(os.Stderr, "[mesh-vault] "+format+"\n", args...)
|
|
}
|
|
|
|
func emit(event string, body map[string]any) error { return stdio.Emit(event, body) }
|
|
|
|
// build makes the vault from the module's words: directories, never values.
|
|
func build() *Env {
|
|
env := &Env{Receives: os.Getenv("MESH_RECEIVES"), Root: os.Getenv("MESH_VAULT_ROOT")}
|
|
if l, err := NewLedger(os.Getenv("MESH_VAULT_LEDGER")); err == nil {
|
|
env.Ledger = l
|
|
} else {
|
|
say("%v", err)
|
|
}
|
|
if env.Ledger != nil && env.Receives != "" {
|
|
env.Custody = &Custody{Ledger: env.Ledger, Receives: env.Receives, Resource: "secret", Node: os.Getenv("MESH_NODE"),
|
|
Emit: emit, Now: time.Now, Log: say}
|
|
}
|
|
store, err := OpenStore(os.Getenv("MESH_VAULT_STORE"))
|
|
if err != nil {
|
|
env.Broken = fmt.Errorf("the vault could not open its store: %w", err)
|
|
say("%v", env.Broken)
|
|
return env
|
|
}
|
|
own, err := OwnKey(os.Getenv("MESH_VAULT_STATE"), store)
|
|
if err != nil {
|
|
env.Broken = fmt.Errorf("the vault could not open its key: %w", err)
|
|
say("%v", env.Broken)
|
|
return env
|
|
}
|
|
// Who may ask (askers.go): a node's provisioner client, for what its module's plan there names.
|
|
// Where it sees a consumer bind a provider (pair.go): the same mirror, every key.
|
|
// The assignments as the vault's watch keeps them (watchAssignments), read over the bus until it has caught up.
|
|
assigned = &WatchedAssignments{Fallback: readPlanRaw}
|
|
watchedPlan := func(key string) ([]byte, error) { raw, err := assigned.Read(key); return asAssignment(key, raw, err) }
|
|
env.Vault = &Vault{Store: store, Own: own, Askers: Declared{Read: watchedPlan, Confirm: readAssignment, Store: store},
|
|
Assignments: Mirror{Keys: assignmentKeys, Read: readAssignment}, Emit: emit, Ask: stdio.Ask,
|
|
Now: time.Now, Log: say, Accounts: busAccounts{}, Haves: readHave, LeaseRead: readLease, WantRead: readWant,
|
|
HaveKeys: func() ([]string, error) { return stdio.State(HaveRead).Keys() }, Placement: placement, Signers: busSigners{},
|
|
AskCallers: askCallers(os.Getenv("MESH_VAULT_SECRET_ASK_CALLERS")),
|
|
Bound: Bound{
|
|
Get: func(key string) ([]byte, error) {
|
|
e, err := stdio.State(BoundState).Get(key)
|
|
if err != nil || e == nil {
|
|
return nil, err
|
|
}
|
|
return e.Value, nil
|
|
},
|
|
Put: func(key string, value []byte) error {
|
|
_, err := stdio.State(BoundState).Put(key, json.RawMessage(value))
|
|
return err
|
|
},
|
|
Delete: func(key string) error { return stdio.State(BoundState).Delete(key) },
|
|
Keys: func() ([]string, error) { return stdio.State(BoundState).Keys() },
|
|
Provision: func(key string) ([]byte, error) {
|
|
e, err := stdio.State(provisions.ProvisionsRead).Get(key)
|
|
if err != nil || e == nil {
|
|
return nil, err
|
|
}
|
|
return e.Value, nil
|
|
},
|
|
}}
|
|
return env
|
|
}
|
|
|
|
// assigned is the assignments mirror as the vault's watch keeps it.
|
|
var assigned *WatchedAssignments
|
|
|
|
// watchAssignments begins the watch of the assignments mirror the vault's rules read (issue 1126, 2026-10-28): a
|
|
// watch, never a poll, so `own` and `get` read no assignment over the bus once it has caught up. It answers whether the
|
|
// watch began; one that did not is begun again at the next minute pass, and reads go to the bus meanwhile.
|
|
func watchAssignments() bool {
|
|
if assigned == nil {
|
|
return true
|
|
}
|
|
// Each attempt begins from nothing held: one that failed partway leaves nothing a later one would not hand again.
|
|
attempt := assigned.Begin()
|
|
if err := stdio.State(AssignmentsRead).Watch("", attempt.Take); err != nil {
|
|
assigned.Begin() // what the failed attempt took counts for nothing, and reads go to the bus
|
|
say("the assignments are not watched, so each is read over the bus; begun again at the next pass: %v", err)
|
|
return false
|
|
}
|
|
attempt.Caught()
|
|
say("the assignments are watched: the vault's rules read them from memory")
|
|
return true
|
|
}
|
|
|
|
// readHave is one key of the node-engines' have bucket, read through the runtime under the vault's own account (its
|
|
// manifest `reads` it), or nil when there is none.
|
|
func readHave(key string) ([]byte, error) {
|
|
e, err := stdio.State(HaveRead).Get(key)
|
|
if err != nil || e == nil {
|
|
return nil, err
|
|
}
|
|
return e.Value, nil
|
|
}
|
|
|
|
// watchHandings adopts from the bus module's have whenever it changes (adopt.go): a watch, never a poll. It answers
|
|
// whether the watch began; one that did not is begun again at the next minute pass.
|
|
func watchHandings(env *Env) bool {
|
|
if env.Vault == nil {
|
|
return true
|
|
}
|
|
// The have changes at every look of the bus module's health: why nothing was adopted is said once, and again when
|
|
// it changes.
|
|
var said string
|
|
err := stdio.State(HaveRead).Watch("*."+BusModule, func(c stdio.StateChange) error {
|
|
if c.Op == "delete" || len(c.Value) == 0 {
|
|
return nil
|
|
}
|
|
node := strings.TrimSuffix(c.Key, "."+BusModule)
|
|
_, err := env.Vault.AdoptFrom(node, c.Value, "the watch of "+HaveRead, false)
|
|
if line := fmt.Sprint(err); err != nil && line != said {
|
|
say("adopted nothing from %s: %v", c.Key, err)
|
|
said = line
|
|
} else if err == nil {
|
|
said = ""
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
say("the bus module's have is not watched, so no hash is adopted; begun again at the next pass: %v", err)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// watchWants mints from the grants part's wanted accounts as they change (mint.go, ADR 0561 §2): a watch, never a poll.
|
|
// It answers whether the watch began; one that did not is begun again at the next minute pass.
|
|
func watchWants(env *Env) bool {
|
|
if env.Vault == nil {
|
|
return true
|
|
}
|
|
err := stdio.State(WantedRead).Watch("", func(c stdio.StateChange) error {
|
|
env.Vault.WantHeard(c.Key, c.Value, c.Op == "delete" || c.Op == "purge" || len(c.Value) == 0)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
say("the grants part's wanted accounts are not watched, so nothing is minted; begun again at the next pass: %v", err)
|
|
return false
|
|
}
|
|
// The watch returns once every current key was handed: what has no key now is no longer wanted.
|
|
env.Vault.WantsRead()
|
|
return true
|
|
}
|
|
|
|
// placement reads the seat mesh-placement's placement through the runtime under the vault's own account (its manifest reads it).
|
|
var placement = PlacementReader{
|
|
Keys: func() ([]string, error) { return stdio.State(PlacementRead).Keys() },
|
|
Read: func(key string) ([]byte, error) {
|
|
e, err := stdio.State(PlacementRead).Get(key)
|
|
if err != nil || e == nil {
|
|
return nil, err
|
|
}
|
|
return e.Value, nil
|
|
},
|
|
}
|
|
|
|
// readWant is one wanted account of the grants part, read afresh (issue-account).
|
|
func readWant(user string) ([]byte, bool, error) {
|
|
e, err := stdio.State(WantedRead).Get(user)
|
|
if err != nil || e == nil {
|
|
return nil, false, err
|
|
}
|
|
return e.Value, true, nil
|
|
}
|
|
|
|
// readLease is the controller's lease key, read through the runtime under the vault's own account (its manifest reads it).
|
|
func readLease() ([]byte, bool, error) {
|
|
e, err := stdio.State(LeaseRead).Get(LeaseKey)
|
|
if err != nil || e == nil {
|
|
return nil, false, err
|
|
}
|
|
return e.Value, true, nil
|
|
}
|
|
|
|
// watchLease turns minting on and off as the controller's lease is written (lease.go, ADR 0561 §3): a watch, never a
|
|
// poll; and the lease is read afresh before every mint. It answers whether the watch began.
|
|
func watchLease(env *Env) bool {
|
|
if env.Vault == nil {
|
|
return true
|
|
}
|
|
env.Vault.LeaseHeard(readLease())
|
|
err := stdio.State(LeaseRead).Watch(LeaseKey, func(c stdio.StateChange) error {
|
|
deleted := c.Op == "delete" || c.Op == "purge" || len(c.Value) == 0
|
|
env.Vault.LeaseHeard(c.Value, !deleted, nil)
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
say("the controller's lease is not watched, so minting stays as last read; begun again at the next pass: %v", err)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// adoptOperatorKey takes the operator key from the export the mesh writes to the vault's root (what
|
|
// `operator key set` recorded; the controller keeps only a public half there), and seals again any
|
|
// operator copy not yet sealed to the current key. Run every minute, so a failure partway is finished. It
|
|
// returns why the pass did not re-seal everything, or nil.
|
|
func adoptOperatorKey(env *Env) error {
|
|
if env.Vault == nil {
|
|
if env.Broken != nil {
|
|
return env.Broken
|
|
}
|
|
return errors.New("the vault was not built")
|
|
}
|
|
if raw := exportedOperatorKey(env.Root); env.Root != "" && raw != "" {
|
|
key, err := publicPEM(raw)
|
|
if err == nil {
|
|
_, err = env.Vault.SetOperatorKey(key, "the mesh's export")
|
|
}
|
|
env.refuseKey("")
|
|
if err != nil {
|
|
env.refuseKey(err.Error())
|
|
say("the operator key in the mesh's export is not taken, or not every copy is sealed to it yet: %v", err)
|
|
}
|
|
return err
|
|
}
|
|
_, err := env.Vault.Reseal()
|
|
if err != nil {
|
|
say("%v", err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// readAssignment is one value of the controller's assignments mirror, read through the runtime under the vault's
|
|
// own account (its manifest `reads` it), or nil when there is none.
|
|
func readAssignment(key string) ([]byte, error) {
|
|
raw, err := readPlanRaw(key)
|
|
return asAssignment(key, raw, err)
|
|
}
|
|
|
|
// readPlanRaw is one key of the seat mesh-provisioner's plans as it is kept, nil when there is none.
|
|
func readPlanRaw(key string) ([]byte, error) {
|
|
e, err := stdio.State(AssignmentsRead).Get(key)
|
|
if err != nil || e == nil {
|
|
return nil, err
|
|
}
|
|
return e.Value, nil
|
|
}
|
|
|
|
// assignmentKeys is every key of the controller's assignments mirror, read the same way.
|
|
func assignmentKeys() ([]string, error) { return stdio.State(AssignmentsRead).Keys() }
|
|
|
|
func main() {
|
|
env := build()
|
|
go func() {
|
|
tick := time.NewTicker(time.Minute)
|
|
defer tick.Stop()
|
|
// At start, every covered account it holds whose hash is not of its current generation (ADR 0547 clause 9).
|
|
if env.Vault != nil {
|
|
written, failed := env.Vault.HashHeld()
|
|
say("hashed %d bus accounts at start; %d could not be, tried again every minute: %v", len(written), len(failed), failed)
|
|
}
|
|
// The operator key first: nothing is kept without it, so a want heard before it would not be minted.
|
|
env.pass()
|
|
watched, watching := watchHandings(env), watchAssignments()
|
|
lease := watchLease(env)
|
|
wants := watchWants(env)
|
|
for {
|
|
if !watched {
|
|
watched = watchHandings(env)
|
|
}
|
|
if !watching {
|
|
watching = watchAssignments()
|
|
}
|
|
if !lease {
|
|
lease = watchLease(env)
|
|
}
|
|
if !wants {
|
|
wants = watchWants(env)
|
|
}
|
|
env.pass()
|
|
if env.Vault != nil {
|
|
// The signing key and authority once the operator key is taken, and `signers` when it differs (ADR 0550 §2).
|
|
if err := env.Vault.SigningPass(); err != nil {
|
|
env.Vault.signingSaid(err)
|
|
} else {
|
|
env.Vault.signingSaid(nil)
|
|
}
|
|
env.Vault.HashOwed()
|
|
env.Vault.MintOwed()
|
|
env.Vault.RetirePass()
|
|
}
|
|
env.pairPass()
|
|
if env.Custody != nil {
|
|
env.Custody.Pass()
|
|
}
|
|
<-tick.C
|
|
}
|
|
}()
|
|
if err := stdio.Serve("", tools(env)); err != nil {
|
|
say("%v", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// askCallers are the callers the vault's manifest names for secret-ask besides the controller (novox/hq ADR 0563 §8):
|
|
// MESH_VAULT_SECRET_ASK_CALLERS, comma-separated, each a module (on any machine), <machine>.<module> or person.<name>.
|
|
func askCallers(list string) stdio.Callers {
|
|
var out stdio.Callers
|
|
for _, c := range strings.Split(list, ",") {
|
|
if c = strings.TrimSpace(c); c != "" {
|
|
out = append(out, c)
|
|
}
|
|
}
|
|
return out
|
|
}
|