Implements SECRETS-WP-0002 end to end as a uv-managed Python package: - catalog: non-secret lane registry + strict validator (build/test/prod) - stage roles + OpenBao ACL policies; guards refuse wildcards, sys/, identity/, admin names, and cross-stage paths before any backend call - plan/apply: dry-run-first, idempotent policy + approle apply, decision-gated - decisions: State Hub lookup with local-fixture fallback; non-secret evidence to JSONL + hub progress, scrubbed of any value - provision/verify: mode-0600 file import + generated test values; positive/ negative checks that never print the value - exec delivery: `exec --catalog ... -- npm publish` injects the token via a temp .npmrc for the child only, cleaned up on exit/failure/interrupt - ops-warden routing contract + hardening backlog docs - 34 tests incl. live OpenBao integration; scripts/demo-e2e.sh runs the full chain against a throwaway bao dev server Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.6 KiB
OpenBao Stage Roles & Bootstrap
secrets-engine talks to OpenBao through three stage roles, never as root or a platform admin. Each role is confined to one stage's KV prefix and a small, explicit capability set.
| Role | Policy file | KV prefix | May do | May NOT do |
|---|---|---|---|---|
secrets-engine-build |
policies/secrets-engine-build.hcl |
secret/.../build/ |
manage build metadata + generated test values; own se-build-* policies/roles |
touch test/prod, sys/*, auth/token/*, identity/*, act as root |
secrets-engine-test |
policies/secrets-engine-test.hcl |
secret/.../test/ |
manage test metadata + values; run positive/negative checks; own se-test-* |
touch build/prod, sys/*, auth/token/*, identity/*, act as root |
secrets-engine-prod |
policies/secrets-engine-prod.hcl |
owner-scoped prod lanes | apply approved prod ACL policies + approle roles; write approved values; own se-prod-* |
reach build/test, edit the stage roles themselves, admin sys/auth, sys/mounts, identity/*, auth/token/*, act as root |
The product requirement is the stage distinction and the denials, not the exact policy names — those may evolve as info-tech-canon hardens.
Negative guarantees (enforced two ways)
- In OpenBao — each policy carries explicit
denystanzas for other stages and forsys/*,auth/token/*,identity/*. - In secrets-engine —
roles.assert_path_in_stage()androles.assert_policy_safe()reject any plan that would touch another stage's prefix, use a wildcard, name itself like an admin policy, or carry a capability outsidecreate/read/update/delete/list. A bad plan fails closed before any OpenBao call.
Run the negative checks:
pytest tests/test_guards.py -q
Bootstrap token files (temporary)
Until OIDC/service auth exists (see the hardening backlog), a platform-root operator may mint a short-lived OpenBao token for a stage role and hand the agent the file path — never the token value.
Requirements for a bootstrap token file:
- mode 0600, owned by the invoking user;
- located outside any Git worktree (e.g.
~/.secrets-engine/bootstrap/); - named by role + environment only, never by value
(e.g.
prod.token, notnpm_abc123.token); - revocable and temporary — tracked with a revocation task;
- referenced by path, e.g.:
secrets-engine apply whynot-design-npm-publish --stage prod \
--bootstrap-token-file ~/.secrets-engine/bootstrap/prod.token
secrets-engine refuses a bootstrap token file that is group/other-readable or
that lives inside the repo worktree (provision/apply check st_mode & 0o077).
Minting (operator, one-time, root context)
# Write each stage policy into OpenBao.
bao policy write secrets-engine-build policies/secrets-engine-build.hcl
bao policy write secrets-engine-test policies/secrets-engine-test.hcl
bao policy write secrets-engine-prod policies/secrets-engine-prod.hcl
# Mint a short-lived token for one stage role, store mode-0600 outside the repo.
install -m 700 -d ~/.secrets-engine/bootstrap
bao token create -policy=secrets-engine-prod -ttl=1h -field=token \
> ~/.secrets-engine/bootstrap/prod.token
chmod 600 ~/.secrets-engine/bootstrap/prod.token
Revocation (always have a path)
# Revoke by accessor (preferred) or delete the file when the TTL is short.
bao token revoke -accessor <accessor>
shred -u ~/.secrets-engine/bootstrap/prod.token
Every minted bootstrap token MUST have a corresponding revocation task in the
hardening backlog (docs/hardening-backlog.md).