docs(secrets): interactive console runbook + 0.3.0 version bump

This commit is contained in:
Joseph Doherty
2026-07-19 10:37:37 -04:00
parent 1f7d26d4d9
commit 6dbb372adf
3 changed files with 299 additions and 2 deletions
+42 -1
View File
@@ -16,6 +16,45 @@ plaintext back on demand — from application code, from configuration, or from
A `secret` CLI (`set` / `get` / `list` / `rm` / `rotate` / `rewrap-all`) ships in the repo (not packed).
### Interactive console
Run `secret` with **no arguments** in a real terminal and it opens an interactive console
instead of printing usage — a menu-driven session for an operator sitting at a deployment,
rather than a scripted one-shot verb. The launch condition is exact: no verb **and** neither
stdin nor stdout is redirected. Any redirection (a pipe, `| cat`, CI, a script) falls straight
through to the headless usage/exit-2 behavior above, so nothing scripted ever hangs on a
prompt.
The first screen picks a **store target** — recent targets (persisted to
`~/.zb-secrets/recent-targets.json`, names + paths only, never key material), an app's
`appsettings.json` (the console composes base + `appsettings.<env>.json` + environment
variables and binds `Secrets` exactly like the app itself, so every operation hits precisely
the store the app reads), or manual entry (a raw SQLite path + a master-key environment
variable name). If the configured KEK cannot be resolved in the operator's shell, the session
still opens — **degraded**, metadata-only — and any flow that needs the key prompts for one
(masked paste or a key file) to upgrade it in place.
Once a target is open, the menu offers:
- **List secrets** — metadata table (name, content type, KEK id, revision, updated); no KEK needed.
- **Set / rotate a secret** — masked value prompt, seals a fresh row under the session KEK.
- **Get (reveal) a secret** — confirm-gated; prints the plaintext once, never cached or logged.
- **Delete a secret** — confirm-gated tombstone; no KEK needed.
- **Reference audit & seed** — scans the target's composed configuration for `${secret:NAME}`
tokens, classifies each Ok / Missing / Tombstoned / Undecryptable / InvalidName, and walks the
gaps through a seed prompt. This is *the* deployment-setup flow.
- **KEK doctor (lockout recovery)** — per-row diagnosis (Ok / WrongKek / Corrupt, with the
foreign KEK id) plus a guided remedy: rewrap every row from an old KEK, or re-set individual
rows when the old KEK is unrecoverable.
- **Export bundle (ciphertext-only)** / **Import bundle** — move rows between stores. Bundles
carry name + metadata + wrapped DEK + ciphertext, never plaintext; importing across KEKs
verifies the pasted source key against the bundle's KEK id before re-wrapping, conflicts
resolve last-writer-wins (with an optional per-row prompt), and the format is versioned.
There is no login — the console runs locally with direct file/DB access, the same trust model
as any other admin CLI. See the operator runbook for the full walkthrough:
[`docs/operations/interactive-console.md`](docs/operations/interactive-console.md).
## How it protects secrets
Each value is encrypted with a fresh **data key (DEK)** using AES-256-GCM; the DEK is then
@@ -149,4 +188,6 @@ node can always reach a shared database, because you pay real distributed-system
stays invisible to last-writer-wins. Run `rewrap-all` against **each** independent store.
See [`docs/operations/clustered-secrets.md`](docs/operations/clustered-secrets.md) for operator
setup, and [`docs/operations/kek-rotation.md`](docs/operations/kek-rotation.md) for rotation.
setup, [`docs/operations/kek-rotation.md`](docs/operations/kek-rotation.md) for rotation, and
[`docs/operations/interactive-console.md`](docs/operations/interactive-console.md) for the
`secret` interactive console (deployment seeding, lockout recovery, bundles).