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
133 lines
7.3 KiB
Markdown
133 lines
7.3 KiB
Markdown
# 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`)
|
||
|
||
1. 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_TOKEN` and
|
||
`VAULT_TOKEN` are removed from the child environment, and `-no-store` keeps
|
||
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.
|
||
2. One in-process HTTPS `GET /v1/platform/data/workloads/keycape/secrets-engine-openbao`
|
||
with the token only in the `X-Vault-Token` header; extracts `CLIENT_SECRET`
|
||
(must be exactly one non-empty token).
|
||
3. `POST /v1/auth/token/revoke-self` with the same token — always, also when
|
||
the read fails.
|
||
4. The value goes in memory to the KeyCape `/token` `client_credentials`
|
||
exchange (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:
|
||
|
||
```bash
|
||
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.
|
||
|
||
```bash
|
||
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:
|
||
|
||
```json
|
||
{"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.
|