SCOPE, layer.yaml and the custody doc said the platform JWT role was unpublished. The role is applied, its consumer contract is published (railiance-platform 21b8af3) and login was proven on 2026-10-10. The role is login-only, so engine actions still use the named providers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 544698@bnt-lap001 Assistant-Session: 279b8520-335f-4041-ba2a-9491335473db
7.3 KiB
KeyCape secrets-engine-openbao client secret: consumer custody
Rewritten 2026-10-06 by secrets-engine. Tracked in SECRETS-WP-0012. Source and documentation only. No value was generated, fetched or written; no host, cluster, OpenBao or KeyCape state was changed.
Direction
Founder direction 2026-10-06: the secrets-engine-openbao KeyCape client
secret lives only in OpenBao. There is no persistent workstation copy.
ops-mason decided placement and the read lane in
ops-mason/plans/keycape-secrets-engine-openbao-client-custody.md (commit
658cdbc, workplan MASON-WP-0009; status reviewed, founder approvals A1–A3
pending). That direction and plan supersede the earlier consumer-file packet
and its decisions D1–D4 (host, OS user, persistent file path, rotation
cadence). The consumer-file custody child in CCR-2026-0033 is out of direction
and is being revised by railiance-platform.
Coordinates (non-secret)
| Item | Value |
|---|---|
| Authoritative entry | KV v2 mount platform, path workloads/keycape/secrets-engine-openbao (API platform/data/workloads/keycape/secrets-engine-openbao), field CLIENT_SECRET |
| Issuer copy | sso Secret keycape-secrets-engine-openbao-client key client-secret, via ESO on Kubernetes auth (platform/KeyCape; unchanged) |
| Consumer copy | none — the value exists only in process memory for one command |
| Consumer read lane | OIDC mount auth/netkingdom, role keycape-secrets-engine-openbao-operator-workload-kv-read, policy workload-kv-read-keycape-secrets-engine-openbao-operator (read on the data path, update on auth/token/revoke-self, nothing else), 5m TTL, 4 uses, bound groups=net-kingdom-admins |
| Companion config | BAO_ADDR=http://127.0.0.1:18200 (operator tunnel; bao.coulomb.social retired 2026-09-15), SECRETS_ENGINE_KEYCAPE_TOKEN_URL=https://kc.coulomb.social/token, SECRETS_ENGINE_KEYCAPE_ISSUER=https://kc.coulomb.social, SECRETS_ENGINE_OPENBAO_JWT_LOGIN=<platform-published contract file> |
The mount, path, field and role are pinned in
service_auth.OpenBaoOidcClientSecretSource; any other value is refused.
What the code does (per command that selects service-jwt)
- Runs
bao login -method=oidc -path=netkingdom -no-store -format=json role=keycape-secrets-engine-openbao-operator-workload-kv-read. The browser/MFA prompt goes to the operator's terminal (stderr); the login response is captured from a stdout pipe into memory.BAO_TOKENandVAULT_TOKENare removed from the child environment, and-no-storekeeps the token out of the token helper. For non-token methods the CLI does not send a helper-stored token. The token never appears in argv, env, a file, a log or evidence. - One in-process HTTPS
GET /v1/platform/data/workloads/keycape/secrets-engine-openbaowith the token only in theX-Vault-Tokenheader; extractsCLIENT_SECRET(must be exactly one non-empty token). POST /v1/auth/token/revoke-selfwith the same token — always, also when the read fails.- The value goes in memory to the KeyCape
/tokenclient_credentialsexchange (HTTP Basic only), then the existing OpenBao JWT login on the platform role and its self-revoke at command end. The value is dropped when the exchange request is built.
Refusals: SECRETS_ENGINE_KEYCAPE_CLIENT_SECRET_FILE set → refused for this
profile; a KeyCapeServiceAuthConfig with a file → refused; a provider for this
profile without the in-memory source → refused. service-jwt still never falls
back to --bootstrap-token-file, BAO_TOKEN or AppRole. The approval client
(secrets-engine-approval) keeps its own file source and
service-auth check-secret-file for it.
Evidence is value-free: secret_source=openbao-oidc-in-memory, the operator
and engine accessor fingerprints (12 hex chars of SHA-256), and both
revoke-self results. Tests with synthetic values:
tests/test_openbao_secret_source.py.
First live use (founder-run, attended)
Run only after all of: founder approval of the ops-mason plan (A1), the
revised CCR-2026-0033 applied by railiance-platform (A2/A3: KV entry written,
operator OIDC role and policy, ESO reader, JWT role), the KeyCape
secrets-engine-openbao registration live and KeyCape restarted after the KV
write (KEY-WP-0037), and the platform JWT role applied (CCR-2026-0038, applied
2026-10-08). This is the evidence step for KEY-WP-0037-T04 / RPF-WP-0035-T02 and
plan verification item 6.
CCR-2026-0038 publishes the consumer contract file only after the login proof passes, so the probe cannot wait for it. Write the non-secret probe input locally from the applied CCR values first:
mkdir -p ~/.secrets-engine/keycape && (umask 077; cat > ~/.secrets-engine/keycape/openbao-jwt-login.yaml <<'YAML'
mount: keycape-services
role: secrets-engine-openbao
bound_issuer: https://kc.coulomb.social
YAML
)
The proof passed on 2026-10-10
(docs/evidence/2026-10-10-keycape-openbao-probe.json). railiance-platform then
published the consumer contract as docs/contracts/secrets-engine-openbao-jwt-login.yaml
(21b8af3). That contract is identical to this probe input, so the local file
stays as it is.
cd /home/worsch/secrets-engine && \
env -u BAO_TOKEN -u VAULT_TOKEN -u SECRETS_ENGINE_KEYCAPE_CLIENT_SECRET_FILE \
BAO_ADDR=http://127.0.0.1:18200 \
SECRETS_ENGINE_KEYCAPE_TOKEN_URL=https://kc.coulomb.social/token \
SECRETS_ENGINE_KEYCAPE_ISSUER=https://kc.coulomb.social \
SECRETS_ENGINE_OPENBAO_JWT_LOGIN=/home/worsch/.secrets-engine/keycape/openbao-jwt-login.yaml \
uv run secrets-engine service-auth probe-openbao --json
A browser opens for the KeyCape login with MFA (an existing session is
reused); stderr shows the bao OIDC prompt. Expected stdout, exit 0 — one
line, only these keys, <fp> being 12 hex characters:
{"auth_provider": "service-jwt", "established": true, "ok": true, "operator_revocation_attempted": true, "operator_revocation_succeeded": true, "operator_session_handle": "<fp>", "revocation_attempted": true, "revocation_succeeded": true, "secret_source": "openbao-oidc-in-memory", "session_handle": "<fp>"}
Anything else is a failure to record, not to retry blindly: exit 1 with
error: ... on stderr names the failed step (OIDC login exit code, KV HTTP
status, KeyCape HTTP status, JWT login) and, for a source failure, both
operator revoke flags. ok: false means a revoke-self did not succeed; the
token stays TTL/use-bound (5m/4 uses, engine token per platform role). Record
the JSON line only; never a value, token or full accessor.
Rotation, revocation, rollback
Platform-owned (plan §3.5): roll-forward KV writes with cas=<current>,
event-driven plus 90 days, then KeyCape restart. The consumer needs no action:
the next command reads the latest version. Exchanges fail closed (401) between
the KV write and the KeyCape restart. On compromise KeyCape/NetKingdom disable
the registration first. secrets-engine can neither write nor rotate this entry
and never reads an old version.
Not established
No live OIDC login, KV read, revoke-self, KeyCape exchange or JWT login has
run. The operator role, policy, KV entry and JWT contract file do not exist
yet as far as this repository knows. bao login -no-store -format=json
output shape for OIDC is taken from the CLI contract and is first exercised by
the founder run above.