mesh-vault: the tools the TypeScript vault offered, in Go (issue 674)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 00:55:34 +00:00
co-authored by Claude Opus 5.5
parent 11fc3258b4
commit da04ad62ae
2 changed files with 470 additions and 0 deletions
+259
View File
@@ -0,0 +1,259 @@
package main
// The custody ledger (novox/hq ADR 0085, design 24), carried over from the TypeScript vault as it was:
// for each pair credential the controller delivers to this vault as the `secret` provider, who holds it,
// since when, its fingerprint and every rotation — and never the value. The controller still mints and
// seals those until the secrets move to the vault (design 57's V3, V4); this pass records what it delivered,
// so `secret_holders` and `secret_verify` answer as before. Its files are the same JSON, read as they are.
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
)
// isoMillis is JavaScript's toISOString, the form the ledger's times were first written in.
const isoMillis = "2006-01-02T15:04:05.000Z"
// Held is one holder of a secret this vault provides: everything the vault knows of it, and no value.
type Held struct {
As string `json:"as"`
Consumer string `json:"consumer"`
Fingerprint string `json:"fingerprint"`
Length int `json:"length"`
Since string `json:"since"`
Changed string `json:"changed"`
Rotations int `json:"rotations"`
History []Earlier `json:"history"`
}
// Earlier is a fingerprint a rotation replaced, and when.
type Earlier struct {
Fingerprint string `json:"fingerprint"`
Until string `json:"until"`
}
// Outcome is what recording a delivery found.
type Outcome string
const (
Granted Outcome = "granted"
Rotated Outcome = "rotated"
Unchanged Outcome = "unchanged"
)
// Fingerprint is sha256 of a value, as `sha256:<hex>`: the one thing about a secret that may be said.
func Fingerprint(value string) string {
sum := sha256.Sum256([]byte(value))
return "sha256:" + hex.EncodeToString(sum[:])
}
// Ledger is the directory of holders.
type Ledger struct{ dir string }
// NewLedger opens (and makes) the ledger at dir.
func NewLedger(dir string) (*Ledger, error) {
if dir == "" {
return nil, errors.New("MESH_VAULT_LEDGER is not set: the vault has nowhere to keep its ledger")
}
if err := os.MkdirAll(dir, 0o700); err != nil {
return nil, err
}
return &Ledger{dir: dir}, nil
}
var login = regexp.MustCompile(`^[a-z0-9][a-z0-9_.-]*$`)
func (l *Ledger) pathOf(as string) (string, error) {
if !login.MatchString(as) || strings.Contains(as, "..") {
return "", fmt.Errorf("a login is a name, not a path: %q", as)
}
return filepath.Join(l.dir, as+".json"), nil
}
// Record says the mesh delivered value for as: the same value again changes nothing, another is a rotation.
// The value is fingerprinted here and goes no further.
func (l *Ledger) Record(as, consumer, value string, now time.Time) (Held, Outcome, error) {
path, err := l.pathOf(as)
if err != nil {
return Held{}, "", err
}
fp, at := Fingerprint(value), now.UTC().Format(isoMillis)
before, ok := l.Get(as)
if !ok {
h := Held{As: as, Consumer: consumer, Fingerprint: fp, Length: len(value), Since: at, Changed: at, History: []Earlier{}}
return h, Granted, l.write(path, h)
}
if before.Fingerprint == fp && before.Length == len(value) {
return before, Unchanged, nil
}
h := before
h.Consumer, h.Fingerprint, h.Length, h.Changed = consumer, fp, len(value), at
h.Rotations++
h.History = append(append([]Earlier{}, before.History...), Earlier{Fingerprint: before.Fingerprint, Until: at})
return h, Rotated, l.write(path, h)
}
// Withdraw forgets a holder, saying whether there was one.
func (l *Ledger) Withdraw(as string) bool {
path, err := l.pathOf(as)
if err != nil {
return false
}
return os.Remove(path) == nil
}
// Get is one holder.
func (l *Ledger) Get(as string) (Held, bool) {
path, err := l.pathOf(as)
if err != nil {
return Held{}, false
}
raw, err := os.ReadFile(path)
if err != nil {
return Held{}, false
}
var h Held
if json.Unmarshal(raw, &h) != nil {
return Held{}, false
}
return h, true
}
// List is every holder, by login.
func (l *Ledger) List() []Held {
entries, err := os.ReadDir(l.dir)
if err != nil {
return nil
}
var out []Held
for _, e := range entries {
if strings.HasSuffix(e.Name(), ".json") {
if h, ok := l.Get(strings.TrimSuffix(e.Name(), ".json")); ok {
out = append(out, h)
}
}
}
sort.Slice(out, func(i, j int) bool { return out[i].As < out[j].As })
return out
}
func (l *Ledger) write(path string, h Held) error {
raw, err := json.MarshalIndent(h, "", " ")
if err != nil {
return err
}
return writeWhole(path, append(raw, '\n'))
}
// Contribution is one entry of the contributions file the mesh delivers to the vault.
type Contribution struct {
From string `json:"from,omitempty"`
Node string `json:"node,omitempty"`
As string `json:"as"`
Secret string `json:"secret"`
}
// Contributions is what the mesh asks this vault to serve: the entries with a login and a secret file.
func Contributions(receives string) ([]Contribution, error) {
raw, err := os.ReadFile(receives)
if err != nil {
return nil, err
}
var doc struct {
Given []Contribution `json:"given"`
}
if err := json.Unmarshal(raw, &doc); err != nil {
return nil, err
}
var out []Contribution
for _, g := range doc.Given {
if g.As != "" && g.Secret != "" {
out = append(out, g)
}
}
return out, nil
}
// delivered is the value the mesh delivered for c, without the newline a file may end with.
func delivered(c Contribution) (string, error) {
raw, err := os.ReadFile(c.Secret)
if err != nil {
return "", fmt.Errorf("the delivered secret is not readable: %v", err)
}
return strings.TrimSuffix(string(raw), "\n"), nil
}
// DeliveredFingerprint is the fingerprint and length of what the mesh currently delivers for c.
func DeliveredFingerprint(c Contribution) (string, int, error) {
v, err := delivered(c)
if err != nil {
return "", 0, err
}
return Fingerprint(v), len(v), nil
}
// Custody is the pass over the contributions file that keeps the ledger.
type Custody struct {
Ledger *Ledger
Receives string
Emit func(event string, body map[string]any) error
Now func() time.Time
Log func(format string, args ...any)
}
// Pass records every delivered value and withdraws every holder no longer asked for — but only from a
// contributions file it could read whole: an unreadable one withdraws nobody.
func (c Custody) Pass() {
asked, err := Contributions(c.Receives)
if err != nil {
c.Log("the contributions file is not readable (%s): %v — nothing withdrawn", c.Receives, err)
return
}
keep := map[string]bool{}
for _, a := range asked {
keep[a.As] = true
value, err := delivered(a)
if err != nil {
c.Log("%s: %v", a.As, err)
continue
}
held, outcome, err := c.Ledger.Record(a.As, a.Node, value, c.Now())
if err != nil {
c.Log("%s: %v", a.As, err)
continue
}
if outcome == Unchanged {
continue
}
event := "provisioned"
if outcome == Rotated {
event = "rotated"
}
c.Log("%s: %s (%s…, rotations %d)", held.As, outcome, held.Fingerprint[:19], held.Rotations)
c.say(event, map[string]any{"consumer": held.Consumer, "as": held.As, "fingerprint": held.Fingerprint, "rotations": held.Rotations})
}
for _, h := range c.Ledger.List() {
if !keep[h.As] && c.Ledger.Withdraw(h.As) {
c.Log("%s: withdrawn", h.As)
c.say("deprovisioned", map[string]any{"as": h.As})
}
}
}
func (c Custody) say(event string, body map[string]any) {
if c.Emit == nil {
return
}
if err := c.Emit(event, body); err != nil {
c.Log("%s could not be said: %v", event, err)
}
}
+211
View File
@@ -0,0 +1,211 @@
package main
// What the vault serves: the seat's verb `get`, and the three tools the TypeScript vault offered —
// secret_export, secret_holders and secret_verify — answering as they did. None returns a value: `get`
// returns one sealed to the asker's key, the others fingerprints and ciphertext only.
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"strings"
stdio "git.novox.be/novox/mesh-sdk/go"
)
// Env is what the tools work on. A part the module's words did not give is nil, and a tool that needs it
// says so.
type Env struct {
Vault *Vault
Ledger *Ledger
Receives string
Root string
// Broken is why the vault could not be built, said by `get`.
Broken error
}
// toolDef is a tool with its caller given apart from its arguments, so a test can be the runtime.
type toolDef struct {
Name string
Description string
Input map[string]any
run func(caller string, a map[string]any) (any, error)
}
// Run answers a call the runtime made: the caller is the one it proved.
func (t toolDef) Run(a map[string]any) (any, error) { return t.run(stdio.CallerOf(a), a) }
func (t toolDef) runAs(caller string, a map[string]any) (any, error) { return t.run(caller, a) }
func str(description string) map[string]any {
return map[string]any{"type": "string", "description": description}
}
func definitions(env *Env) []toolDef {
return []toolDef{
{Name: Seat + ".get",
Description: "One secret, sealed to the public key the asker sends (the sealed box of ADR 0206), with its " +
"generation; answered only to an asker the vault allows, as the runtime proved it (ADR 0349 §11). " +
"Never the value in the clear, and never to a private key.",
Input: map[string]any{"name": str("the secret's name"),
"public_key": str("the asker's X25519 public key as SPKI PEM — never a private key")},
run: func(caller string, a map[string]any) (any, error) {
if env.Vault == nil {
if env.Broken != nil {
return nil, env.Broken
}
return nil, errors.New("the vault is not ready")
}
name, _ := a["name"].(string)
key, _ := a["public_key"].(string)
return env.Vault.Get(caller, strings.TrimSpace(name), key)
}},
{Name: "secret_export",
Description: "The mesh's root secrets as this vault keeps them: every secret a module holds for itself, " +
"sealed to the operator's key (novox/hq ADR 0085, amended). Ciphertext — nothing here can open a line " +
"of it; the operator, holding the private key off the mesh, recovers one with " +
"`nox secret recover --from-export` on the control-node. Also lists what is NOT recoverable: secrets " +
"made before the mesh had an operator key.",
Input: map[string]any{"sealed": map[string]any{"type": "boolean",
"description": "include the sealed blobs (default true); false lists holders and the key only"}},
run: func(_ string, a map[string]any) (any, error) {
return exportAnswer(env.Root, a["sealed"] != false), nil
}},
{Name: "secret_holders",
Description: "Who holds a secret from this vault: each consumer's login, node and module, when it was " +
"granted, how many times it has been rotated and when, and the fingerprint of the current value. " +
"Fingerprints only — the value is never returned.",
run: func(string, map[string]any) (any, error) {
if env.Ledger == nil {
return map[string]any{"holders": []any{}, "count": 0, "error": "the vault has no ledger (MESH_VAULT_LEDGER is not set)"}, nil
}
var asked []Contribution
if env.Receives != "" {
asked, _ = Contributions(env.Receives)
}
holders := []map[string]any{}
for _, h := range env.Ledger.List() {
row := map[string]any{}
raw, _ := json.Marshal(h)
_ = json.Unmarshal(raw, &row)
row["module"], row["asked"] = nil, false
for _, c := range asked {
if c.As == h.As {
row["module"], row["asked"] = c.From, true
}
}
holders = append(holders, row)
}
return map[string]any{"holders": holders, "count": len(holders)}, nil
}},
{Name: "secret_verify",
Description: "Check one holder's secret without seeing it: the fingerprint the vault recorded against the " +
"fingerprint of the value the mesh currently delivers here, and optionally against a fingerprint " +
"computed on the holder's own machine (sha256 of the file, as `sha256:<hex>`). Two ends agreeing " +
"proves they agree, not that either works — the login itself is the test.",
Input: map[string]any{"as": str("the holder's login, e.g. anchor-redis"),
"fingerprint": str("optional: sha256:<hex> of the value as the holder reads it, computed there — never the value")},
run: func(_ string, a map[string]any) (any, error) {
as, _ := a["as"].(string)
given, _ := a["fingerprint"].(string)
return verifyAnswer(env, as, given), nil
}},
}
}
func exportAnswer(root string, withBlobs bool) map[string]any {
if root == "" {
return map[string]any{"available": false, "error": "this vault keeps no root secrets (MESH_VAULT_ROOT is not set)"}
}
raw, err := os.ReadFile(filepath.Join(root, "export.json"))
if err != nil {
return map[string]any{"available": false, "error": "the mesh has not written an export here yet: " + err.Error()}
}
var doc struct {
Export int `json:"export"`
OperatorKey string `json:"operator-key"`
Fingerprint string `json:"fingerprint"`
Kept []map[string]any `json:"kept"`
Unrecoverable []map[string]any `json:"unrecoverable"`
}
if err := json.Unmarshal(raw, &doc); err != nil {
return map[string]any{"available": false, "error": "the export is not readable: " + err.Error()}
}
kept := []map[string]any{}
for _, k := range doc.Kept {
if !withBlobs {
k = map[string]any{"node": k["node"], "module": k["module"], "name": k["name"], "origin": k["origin"], "made-at": k["made-at"]}
}
kept = append(kept, k)
}
if doc.Unrecoverable == nil {
doc.Unrecoverable = []map[string]any{}
}
return map[string]any{"available": true, "export": doc.Export, "operator-key": doc.OperatorKey,
"fingerprint": doc.Fingerprint, "count": len(kept), "kept": kept, "unrecoverable": doc.Unrecoverable}
}
// exportedOperatorKey is the operator key in the export the mesh writes to the vault's root, or "".
func exportedOperatorKey(root string) string {
raw, err := os.ReadFile(filepath.Join(root, "export.json"))
if err != nil {
return ""
}
var doc struct {
OperatorKey string `json:"operator-key"`
}
_ = json.Unmarshal(raw, &doc)
return doc.OperatorKey
}
func verifyAnswer(env *Env, as, given string) map[string]any {
if env.Ledger == nil {
return map[string]any{"as": as, "known": false, "error": "the vault has no ledger (MESH_VAULT_LEDGER is not set)"}
}
recorded, ok := env.Ledger.Get(as)
if !ok {
return map[string]any{"as": as, "known": false, "error": "this vault holds nothing for " + as}
}
var delivered, deliveredErr any
var deliveredMatches any
var asked *Contribution
if env.Receives != "" {
all, _ := Contributions(env.Receives)
for i := range all {
if all[i].As == as {
asked = &all[i]
}
}
}
if asked == nil {
deliveredErr = "the mesh does not currently ask this vault to serve that login"
} else if fp, _, err := DeliveredFingerprint(*asked); err != nil {
deliveredErr = err.Error()
} else {
delivered, deliveredMatches = fp, fp == recorded.Fingerprint
}
var givenAny, givenMatches, earlier any
if given != "" {
givenAny, givenMatches = given, given == recorded.Fingerprint
was := false
for _, h := range recorded.History {
was = was || h.Fingerprint == given
}
earlier = was
}
return map[string]any{"as": recorded.As, "known": true, "recorded": recorded.Fingerprint,
"rotations": recorded.Rotations, "changed": recorded.Changed,
"delivered": delivered, "deliveredError": deliveredErr, "deliveredMatchesRecorded": deliveredMatches,
"given": givenAny, "givenMatchesRecorded": givenMatches, "givenIsAnEarlierValue": earlier,
"ok": deliveredMatches != false && givenMatches != false}
}
// tools are the definitions as the SDK serves them.
func tools(env *Env) []stdio.Tool {
var out []stdio.Tool
for _, d := range definitions(env) {
out = append(out, stdio.Tool{Name: d.Name, Description: d.Description, Input: d.Input, Run: d.Run})
}
return out
}