237 lines
8.0 KiB
Go
237 lines
8.0 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 (
|
|
"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.
|
|
env.Vault = &Vault{Store: store, Own: own, Askers: Declared{Read: 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}
|
|
return env
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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) {
|
|
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)
|
|
}
|
|
watched := watchHandings(env)
|
|
lease := watchLease(env)
|
|
wants := watchWants(env)
|
|
for {
|
|
if !watched {
|
|
watched = watchHandings(env)
|
|
}
|
|
if !lease {
|
|
lease = watchLease(env)
|
|
}
|
|
if !wants {
|
|
wants = watchWants(env)
|
|
}
|
|
env.pass()
|
|
if env.Vault != nil {
|
|
env.Vault.HashOwed()
|
|
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)
|
|
}
|
|
}
|