secrets-engine/docs/service-jwt-final-acceptance.md
tegwick 4378ec1fc2 Prepare reviewed service JWT boundary verification and retain execution adoption gates
Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
2026-10-11 14:11:59 +02:00

111 lines
6.9 KiB
Markdown

# Service-JWT final acceptance — SECRETS-WP-0008-T06
Prepared 2026-10-11; source preparation only. T06 remains wait. This packet
neither grants live authority nor provides owner acknowledgment.
## Delivered producer and consumer work
SECRETS-WP-0012 is finished: protected in-memory client-secret acquisition,
operator cleanup, KeyCape exchange, service JWT login and service cleanup passed
in `docs/evidence/2026-10-10-keycape-openbao-probe.json`. The platform applied
CCR-2026-0038 on October 8 and published
`railiance-platform/docs/contracts/secrets-engine-openbao-jwt-login.yaml` on
October 10. Mount `keycape-services`, role `secrets-engine-openbao`, issuer
`https://kc.coulomb.social` are published. An unpublished-mount or
unprovisioned-registration wait is obsolete.
The consumer no-fallback receipt
`docs/evidence/2026-10-10-service-jwt-no-fallback.json` proves synthetic selection
and real Bao CLI header behavior against a loopback listener. It is not native
claim rejection evidence. KEY-WP-0039-T01 is source acceptance done; T02 owns the
attended actual negative-token session. RPF-WP-0035-T02 retains native claims,
effective policy/capability denials, and MASON-WP-0009-T05 return.
WP0007 is six of seven complete with native per-lane adoption outstanding.
WP0009 credential apply/verify/delivery/revocation passed in the September 27
attended receipts, but its task cannot finish: held EUR 10 reservation still
needs accounting reconciliation, replacement runtime admission and separate
retry authority. These are existing live tasks; this packet closes neither.
## Executable missing identity-boundary check
`tools/verify_service_jwt_boundary.py` defaults to backend-free preparation.
It checks exact applied CCR, published mount/role and issuer, client-secret-file
refusal and exact guarded paths. Preparation cannot produce a passing native
receipt. After explicit authorization for the attended metadata check, add
`--execute` to the same command:
```bash
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/railiance-platform/docs/contracts/secrets-engine-openbao-jwt-login.yaml \
uv run python tools/verify_service_jwt_boundary.py \
--ccr /home/worsch/railiance-platform/credential-change-requests/CCR-2026-0038-secrets-engine-service-jwt-login.yaml
```
Live mode uses existing attended custody and performs two service-token metadata
requests: lookup-self and one batch capabilities-self. The batch checks both
client-secret guarded paths, `sys/policies/acl/*`, `auth/*/role/*` and
`auth/token/create`; it never reads their values or attempts writes. Cleanup
runs even on failed verification. Two metadata calls reserve room for cleanup
within the service token's eight-use budget. Passing requires exactly the
self policy, no identity-policy additions, deny on every requested path and
both operator and service revocation success. Output contains hashes, capability
booleans and session fingerprints. No backend response/error body is printed.
Transport fixes bind each request to the configured OpenBao origin, disable
environment proxies and refuse all redirects including relative redirects.
Only HTTP 200 application/json with a bounded 64 KiB body and object data is
accepted. Errors and response bodies are suppressed. Session evidence is
restricted to the contract whitelist with strict boolean types, the fixed
source name, and twelve-hex session handles. Native acceptance requires every
contract field (established, source, both fingerprints and attempted/succeeded
cleanup flags), a normally returned cleanup call, and no verification failure.
Stale true evidence cannot override a cleanup exception. HTTP JSON with an
errors member is refused even when a data member also exists. Synthetic/local-loopback tests
cover redirects, proxy environment, size/status/content/shape failures, absent
responses, unknown exceptions and unknown evidence fields with cleanup.
This does not prove expiry/use exhaustion, native claim negatives or adoption.
Platform retains those native test obligations and owner review of this tool.
## Protected-operation adoption is a distinct missing contract
INTENT requires task-bounded authority and a functional Lifecycle API. The
platform design explicitly says this login policy is insufficient for native
lanes; future execution entitlement requires a separately reviewed CCR type and
backend-enforced scope. No standing wildcard policy is accepted.
Actual CLI flow (`cli._require_lane_approval`, `_open_backend`, `cmd_apply`) is
claim → validated PDP Check → approval CAS consume → select provider → OpenBao
operation. `_open_backend` supplies the login-only service token directly.
There is no per-action delegated grant or scoped backend exchange in that path.
Consequently a service-jwt default flip would consume a real approval then fail
the protected operation. Configured `auto` chooses service-jwt; unconfigured
`auto` still chooses bootstrap/env. Neither selection proves steady-state
adoption. Keep existing service policy and provider defaults until the execution
contract exists; failed JWT never switches provider.
Founder/owner choice needed: retain login-self-only and implement a separately
owned task-scoped delegation contract (recommended), or explicitly redesign
backend execution mediation with platform enforcement. Both must preserve
claim/PDP/CAS binding, exact action/lane/target/expiry/principal scope, failure
closed, revocation and audit evidence; neither means granting broad standing
ACLs. Retaining attended bootstrap/env as a transition leaves T06 open.
Concrete missing owner obligation: secrets-engine and railiance-platform must
agree the execution entitlement's owner and CCR schema, implementation endpoint,
backend policy enforcement, exact request binding, TTL/use budget and revocation,
then wire it after successful consume. KeyCape supplies identity, not authority.
Existing native AppRole delivery sessions are short-lived workload read sessions,
not an engine administrative entitlement. Existing native lane cutover requires
scoped attended platform authority but does not implement this exchange.
T06 closes only after native KEY39/CCR negative/expiry/use/cleanup evidence and
MASON return, a reviewed implemented execution entitlement, and a selected real
protected action through service identity plus delegated authority: exact bound
PDP/CAS evidence, intended backend result, unauthorized target/action/expired or
replayed request refusal, cleanup on success and failure, no standing engine
credential, and steady-state invocation/default migration evidence. Do not use
`apply` or any other protected live command to discover the missing entitlement.
All of this remains in T06 and owner tasks; no residual is silently finished.