Add the warden-sign auth-capability lane, AppRole handoff, verification guards, docs, and tests. Point the whynot-design pilot at the canonical decision and add the real publish closeout preflight/runbook.
3.9 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-*; exact SECRETS-WP-0004 warden-sign policy/AppRole exception |
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. Auth-capability lanes useroles.assert_auth_capability_safe(), which refuses broadssh/*, wildcard,sys/,auth/token/,identity/, root-like, or non-updategrants and, for the SSH mount, renders only exactssh/sign/<role>paths. 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).