secrets-engine/docs/keycape-openbao-consumer-custody.md

115 lines
6.5 KiB
Markdown
Raw Normal View History

# 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 login contract file published. This
is the evidence step for KEY-WP-0037-T04 / RPF-WP-0035-T02 and plan
verification item 6.
```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.