Select service-jwt, bootstrap, or env exclusively: JWT login uses a JSON file, self-revokes, and never falls back to bootstrap or BAO_TOKEN. The platform JWT mount/role is still unpublished, so auto keeps named bootstrap/env providers. session revoke --accessor-file revokes an already-issued token with fingerprint-only evidence. Production remains fail-closed. Assistant: grok Assistant-Session: 01a05f07-ae72-7781-9fcb-19efd61add00
10 KiB
secrets-engine CLI
Terminology (Gitea is overloaded — be explicit)
| Term we use | Means | Example | Not to be confused with |
|---|---|---|---|
| org | the Gitea organisation | coulomb |
the npm scope |
| repo | the Gitea repository / product | whynot-design |
an org; a Gitea "project" board |
| npm scope | the @-prefix npm name |
@whynot |
the org or the repo |
| npm package | the published artifact | @whynot/design |
the repo it's built from |
| lane / catalog id | a secrets-engine secret lane | whynot-design-npm-publish |
— |
A catalog entry carries org + repo explicitly (never a bare "owner"), and the
npm registry/scope live in delivery_config.npm as data — the engine never
hardcodes a registry. The pilot publishes @whynot/design from the
coulomb/whynot-design repo to https://forgejo.coulomb.social/api/packages/coulomb/npm/.
Existing production lanes additionally distinguish mount ownership, native
delivery auth, and workload delivery. See
catalog-admission.md. In particular,
mount_management: existing makes mount handling non-mutating; it does not
authorize secrets-engine to replace an existing ESO/Kubernetes delivery path.
Install
uv venv && uv pip install -e ".[dev]"
source .venv/bin/activate
secrets-engine --version
Environment
| Var | Default | Purpose |
|---|---|---|
BAO_ADDR |
http://127.0.0.1:8200 |
OpenBao address |
BAO_TOKEN |
(unset) | Named env OpenBao token; never a fallback from service-jwt |
SECRETS_ENGINE_HUB_URL |
http://127.0.0.1:8000 |
State Hub for decisions + evidence (empty to disable) |
SECRETS_ENGINE_CATALOG |
./catalog |
catalog directory |
SECRETS_ENGINE_EVIDENCE |
./.evidence |
local non-secret evidence log |
SECRETS_ENGINE_UNSAFE_DEMO |
(unset) | allow a prod-labeled lane only when Hub is disabled and OpenBao is loopback; throwaway demos only |
SECRETS_ENGINE_KEYCAPE_TOKEN_URL |
(unset) | KeyCape token endpoint for service-jwt |
SECRETS_ENGINE_KEYCAPE_ISSUER |
(unset) | KeyCape issuer; must match the JWT login contract |
SECRETS_ENGINE_KEYCAPE_CLIENT_SECRET_FILE |
(unset) | mode-0600 out-of-repo client secret |
SECRETS_ENGINE_OPENBAO_JWT_LOGIN |
(unset) | platform JWT mount/role contract YAML |
Commands
secrets-engine catalog list
secrets-engine catalog show <catalog-id>
secrets-engine decision inspect <decision-or-ccr-id>
secrets-engine plan <ref> --stage <build|test|prod>
secrets-engine apply <ref> --stage <stage> [--dry-run] [--bootstrap-token-file F]
secrets-engine provision <catalog-id> --stage <stage> --field NAME (--from-file F | --generate)
secrets-engine verify <catalog-id> [--field NAME] [--positive] [--negative] [--negative-token-file F]
secrets-engine handoff <catalog-id> --stage <stage> --role-id-file F --secret-id-file F
secrets-engine exec --catalog <catalog-id> [--field NAME] [--mode auto|npm-config|exec-env] -- CMD...
secrets-engine policy publication <catalog-id>
secrets-engine route <catalog-id> [--json]
secrets-engine revoke <catalog-id> [--dry-run]
secrets-engine session revoke --accessor-file F [--stage stage]
secrets-engine lifecycle suspend <catalog-id> [--dry-run]
secrets-engine lifecycle deactivate <catalog-id> [--dry-run]
secrets-engine lifecycle destroy <catalog-id> [--dry-run] [--confirm-destroy <catalog-id>]
secrets-engine audit <catalog-id> [--json]
policy publication resolves a lane's effective publication scope and the env
var the token is injected under, per the netkingdom publication-scope policy
(see docs/publication-scope-policy.md).
<ref> is a catalog id or a decision/CCR ref (matched against
approval.decision_ref). plan and apply --dry-run never mutate OpenBao.
For decision-gated lanes they may render with decision: <none> when the
approval object is not reachable. Production live commands remain disabled
until State Hub exposes the durable exact-action authorization object. A legacy
local decision is accepted for a prod-labeled lane only with
SECRETS_ENGINE_UNSAFE_DEMO=1, an empty Hub URL, and loopback OpenBao; the demo
scripts set those three conditions themselves.
handoff is for kind: auth-capability lanes such as warden-sign. It mints a
fresh AppRole secret_id and writes role_id plus secret_id to caller-chosen
mode-0600 files outside Git worktrees. It never prints the secret_id; use the
resulting files only for attended out-of-band delivery.
revoke currently means deactivate native secrets-engine access. For
engine-managed delivery auth it deletes the lane AppRole and policy; it always
preserves KV metadata/values, existing externally managed auth, and workload
delivery. There is no general KV-destruction command. provision uses
CAS-aware create/patch behavior with a strict temporary input reference: values
are absent from argv, sibling fields are preserved, and stale writes fail.
With no --field, verify checks every declared KV field positively and runs
one path-level negative probe. KV denial requires --negative-token-file with a
real unrelated identity's mode-0600 token file outside Git; absence fails the
check without calling OpenBao. route likewise requires every declared field
and reports only missing field names. An explicit --field narrows positive
verification; production use will bind such subsets to the action approval.
Explicit lifecycle commands separate intent:
suspendremoves the engine-managed AppRole but preserves policy and KV;deactivateremoves the engine-managed AppRole and policy but preserves KV;destroy --dry-runrenders deactivation plus irreversible KV metadata deletion. Live execution is fail-closed even with exact catalog-id confirmation untilSECRETS-WP-0007-T04supplies the canonical exact-action approval contract.
All three preserve externally managed auth and workload delivery. The legacy
revoke command is a compatibility alias for safe native deactivation, never
KV destruction.
Exec and verification AppRole logins are scoped sessions. The issued token self-revokes on every exit path before exec starts (or when verification ends), and evidence stores only a short accessor fingerprint plus cleanup outcome.
audit is a read-only, local evidence summary. It reports action and result
counts, canonical decision references, session cleanup outcomes, and State Hub
delivery outcomes for one cataloged lane. Its parser allowlists those fields and
does not echo arbitrary JSONL detail. State Hub failures are recorded locally as
append-only companion receipts. Edge-relay queued receipts and their non-secret
outbox ids are recorded too; replay remains an operator/State Hub responsibility.
Live apply, provision, verify, handoff, exec, revoke, suspend, and
deactivate share one evidence guard. It records an attempt before lane approval
is resolved and a terminal outcome on every normal or exceptional exit. Rejected
approval and backend/input failures record only approval state and exception
class, not exception text. This evidence describes what the CLI observed; it is
not an authorization decision and does not replace OpenBao audit logs.
Exit codes
| Code | Meaning |
|---|---|
| 0 | success |
| 2 | catalog error (missing/invalid lane) |
| 3 | decision error (unapproved / superseded / missing) |
| 4 | policy guard error (out-of-stage / wildcard / broad admin) |
| 5 | backend error (OpenBao unreachable / failed) |
| 6 | provisioning error (bad file mode / inside repo / missing field) |
| 7 | verification failed |
| 8 | delivery error |
End-to-end demo
SECRETS_ENGINE_HUB_URL="" bash scripts/demo-e2e.sh
Boots a throwaway in-memory OpenBao dev server and runs the whole pilot chain: plan → apply → provision → verify(+/-) → exec (npm-config injection) → route → revoke. Nothing is persisted; the token is a throwaway local string.
npm publish wiring (dry-run)
bash scripts/npm-publish-demo.sh
Boots a throwaway OpenBao, applies + provisions the lane with a fake token, and
runs a real npm publish --dry-run through secrets-engine exec against a
scratch package. Proves npm in the child resolves its auth token from the temp
.npmrc secrets-engine injected, builds the tarball, and reaches the publish
step — while the parent shell never holds the token. For a real publish,
provision a real npm automation token the same way and drop --dry-run.
Auth-capability: warden-sign
# Preview the non-KV policy/AppRole lane; no mutation.
SECRETS_ENGINE_HUB_URL="" secrets-engine apply warden-sign --stage prod --dry-run
# Live apply and handoff require an approved decision and an out-of-repo
# bootstrap token file. Values are written to files, not stdout.
BAO_ADDR=https://bao.coulomb.social \
secrets-engine handoff warden-sign --stage prod \
--bootstrap-token-file ~/.secrets-engine/bootstrap/prod-warden-sign.token \
--role-id-file ~/.secrets-engine/handoff/warden-sign.role_id \
--secret-id-file ~/.secrets-engine/handoff/warden-sign.secret_id
See warden-sign-auth-capability.md for the full runbook and State Hub pointer payload.
Pilot closeout: whynot-design real publish
scripts/whynot-real-publish-preflight.sh
The real publish path is documented in whynot-design-real-publish-closeout.md.
Pilot: whynot-design npm publish
# 1. inspect the approved decision
secrets-engine decision inspect whynot-design-npm-publish
# 2. preview the OpenBao changes (no mutation)
secrets-engine plan whynot-design-npm-publish --stage prod
# 3. apply policy + approle
secrets-engine apply whynot-design-npm-publish --stage prod
# 4. provision the token from a mode-0600 file OUTSIDE the repo
secrets-engine provision whynot-design-npm-publish --stage prod \
--field npm_token --from-file ~/.secrets-engine/whynot.token
# 5. prove access without printing the value
secrets-engine verify whynot-design-npm-publish --field npm_token --positive --negative \
--negative-token-file /secure/path/unrelated.token
# 6. publish with the token injected into the child only
secrets-engine exec --catalog whynot-design-npm-publish -- npm publish