Repository navigation
feat: keyring instance: one person's keyring with sealed state and admin API (#45) - #53
Conversation
…#45) keyring instance serves an admin API on loopback (bearer token, only its hash given) and nothing else until created or unlocked. Its whole state (network, services, roles, consent settings, keys) is one AES-256-GCM blob bound to the instance id, sealed with a data key the supervisor supplies and keyring never writes. Optional --state keeps the blob on disk; otherwise the supervisor stores it from GET /state. Every change is validated, new listeners bound first, then sealed, written, swapped into the proxy. Consent links store keys through the same path. Rotate reseals with a new key; DELETE /instance shreds the state file, forgets state and key, stops listeners and exits. keyring serve (files, SIGHUP, dashboard) is unchanged.
Decision: Findings
Tests Run
Raw runtime output was retained in local Gitmoot state and is not posted here. |
… the consent token hash; audit required Review of 5149470 (three P2, one P3): - create/unlock after DELETE /instance brought a wiped instance back (410 now) - a moved or stopped listener could stay bound until Serve registered it; the listener is now closed directly - GET /status showed consent.api_token_sha256 - New refuses a nil audit writer; AuditName defaults
|
Review round 1 (job
|
Decision: Findings
Tests Run
Raw runtime output was retained in local Gitmoot state and is not posted here. |
|
Merge gate (keyring seat): |
Closes #45. Part of #46. Assignment: keyring seat brief 2026-10-14 (owner-approved via Jarvis); owner chose Cloudflare Containers (B1) on #46, so this is built as one process per person driven by a supervisor (the person's Durable Object at Cloudflare; any process manager on a host). Design notes:
/root/fleet-tools/state/keyring-seat/design-45.md.What
keyring instance --admin-listen 127.0.0.1:P --admin-token-sha256 HEX [--state FILE] [--audit FILE]Authorization: Bearer <admin token>on every call (only the hash is given). Untilcreate/unlockit serves nothing else.--statekeeps the blob on disk (mode 600, atomic write); without it the blob is memory-only and the supervisor fetches it viaGET /state(B1, B1: keep the encrypted instance state in the Durable Object, not on the container disk #49).policy.Config.Validate→ bind new listeners first → seal → write → swap into the proxy → close old listeners. A refused change leaves version, file and running proxy untouched. Version +1 per change.server.Handlerandconsent.Server.Why one process per person (issue asks to justify): OS address-space separation between persons, the proxy/policy code runs unchanged, and it is exactly B1's one-container-per-person model.
Idle footprint (built binary, 3 instances, Linux x64, Go 1.24.13, one service + one role each): VmRSS ≈ 12 MB, 8 threads, 0 CPU ticks over 30 s idle.
Unchanged (hard rule)
keyring serve, file mode, dashboard, SIGHUP and the Mac install are untouched; the only change outsideinternal/instanceandcmd/keyring/instance.gois three new flags and one newcaseincmd/keyring/main.go.Tests
go test ./... -race(Go 1.24.13): all ok.internal/instance/instance_test.go: create → use → rotate role token → rotate data key (old key opens nothing new; file matches) → delete (file gone, proxy closed, status locked, /state 409, unlock refused, audit clean); restart from a sealed blob with no disk (wrong key 403, right key restores the proxy); two instances cannot read or use each other's keys (tokens cross-refused 401, each upstream gets only its own value, keys don't open each other's state, relabeled state refused); admin token required, wildcard admin refused; refused changes (bad service, unknown fields, service in use, unknown service in role, bad key name, empty value, busy port, admin port, wildcard, second create, bad data key, create over an existing state file from a second process) leave version, file and proxy untouched; network move rebinds and applies new sources; consent link value lands in the sealed state, not the audit.test-check prove: inconclusive (new package). Instead 16 hand mutants ofinstance.go/state.go, all red: AAD ignores id, open ignores id mismatch, no admin token check, rotate keeps old key, wipe keeps file / listeners / state, role token not rotated, status leaks values, bind failure ignored, admin address allowed for proxy, moved listener kept, commit without validate, create overwrites state file, consent bypasses sealed state, no file write. (First two were green against the initial tests; the relabel and id assertions were added, then red.)Not in this PR
#49 stale-blob version check on the DO side; #50 counts/links/audit across a container sleep; #51 container listen mode; #52 router proof. Interface agreed with phobos-dev (gitmoot message 313693).
Review focus
Crypto (GCM nonce, AAD binding, key handling and lifetimes), isolation between instances, commit atomicity under refused changes and concurrent consent writes, admin API auth, and that
keyring serveis unchanged.