secrets-engine/docs/openbao-stage-roles.md
tegwick a852d3f1ff feat(mvp): working secrets-engine CLI for the whynot-design npm publish lane
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>
2026-06-28 12:28:45 +02:00

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)

  1. In OpenBao — each policy carries explicit deny stanzas for other stages and for sys/*, auth/token/*, identity/*.
  2. In secrets-engineroles.assert_path_in_stage() and roles.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 outside create/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, not npm_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).