security: prepare scoped Playwright consumer registrations and owner delivery
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
This commit is contained in:
tegwick 2026-10-13 23:13:48 +02:00
parent 30f0d27840
commit 5dd74dfbdf
5 changed files with 633 additions and 0 deletions

View file

@ -0,0 +1,117 @@
# Playwright registration owner contract
Source preparation under NK-WP-0050; live custody, rollout and consumer acceptance
remain open. Implements the registration part of RPF-WP-0056 and
CUST-WP-0080-T05. The existing browser SecurityCaseType remains unchanged: its
HTTPS/standard-scope admission does not cover this loopback/custom-scope use.
No case acceptance record is fabricated or borrowed from Forgejo/OpenBao.
## Prepare the non-secret records
Run `python3 sso-mfa/k8s/keycape/playwright-registration.py` for the public
client, or add `--agents <identity-references.json>` for the complete proposed
Playwright agent inventory. The latter is an array with one object per existing
registered identity:
```json
[
{
"agent_id": "test-agent",
"service_subject": "service:test-agent:platform",
"tenant": "tenant:test",
"identity_record": "synthetic/agent-record.json"
}
]
```
This example is synthetic; it is not an admitted consumer. The identity owner
must confirm each real record, subject, tenant and agent assignment. A reference
string is not evidence of registration. Initial eligibility already covers all
identified platform humans and coding agents; no new membership decision is
needed. The compiler does not provision identities or retrieve/mint credentials.
| Field | Public human client | Each coding agent |
| --- | --- | --- |
| client ID | `playwright-cli` | `playwright-agent-<agent_id>` |
| grant | authorization code, public PKCE | client credentials, confidential |
| audience | `playwright-kubernetes` | `playwright-kubernetes` |
| scope | `openid profile groups playwright:connect` | `playwright:connect` |
| redirect | exact `http://127.0.0.1:18764/callback` | none |
| subject/tenant | existing directory user and tenant | exact registered subject/tenant supplied by owner |
| role | existing human roles | only `coding-agent` |
| lifetime | existing issuer setting, explicit and at most one hour | 15 minutes |
The public client has no per-client lifetime override; KeyCape supports that only
for client-credentials clients. Existing issuer lifetime outside the accepted
bound refuses preparation and needs owner reconciliation; this tool does not
change every client's lifetime. No public tenant override or MFA opt-out is added.
The callback, dedicated audience and custom scope match Platform's candidate.
The resource uses access tokens; ID tokens retain the client audience.
Each agent gets a distinct proposed env binding from
`keycape-<client-id>-client`, key `client-secret`, to
`KEYCAPE_<CLIENT_ID_WITH_UNDERSCORES>_CLIENT_SECRET`. These names describe a
proposed delivery contract, **not existing Secrets or admitted custody**. Platform
must bind an exact OpenBao/ESO and consumer delivery route with its own source
task and acceptance. No existing OpenBao client or secret is reused. Secret
values must never enter this identity-reference file, Git, command arguments,
coordination messages or receipts.
## Registry and activation boundary
The pure `prepare_registry(secret, identities, operation)` adapter reuses
`keycape-client.py`'s byte-preserving insertion/removal. It validates the entire
inventory first, refuses differing existing records, ambiguous YAML, duplicate
IDs, shared secret references and unreviewed registrations for this dedicated
audience. An identical registration is a no-op. A separate existing OpenBao
registration for the same agent subject is preserved, with its distinct audience
and secret reference. Unrelated configuration, including signing material, is
unchanged. The encoded result contains private registry data and must stay inside
the protected activation process; the CLI never accepts or emits that registry.
This is not a live apply command. NK-WP-0050-T02 must finish the exact owner
activation path using existing registry/Deployment operations:
1. Bind verified identity inventory and source digests to the proposal. Obtain
the governed per-agent custody and delivery references from Platform.
2. Pin current cluster, issuer image and object identities. Read the registry
only within the existing protected owner execution path. Run the actual
pinned KeyCape `check-config` on live and proposed configurations, with the
exact Deployment env names and policy-store mode; suppress private diagnostics.
3. Prepare UID/resource-version/data-bound registry mutation and exact env
bindings. Preserve all unrelated data and authentication methods. Retain
created-only provenance, restart prerequisites, readback and guarded rollback
before proposing any live action. A compare-and-swap conflict means reconcile,
never replay a stale whole registry.
4. Obtain the scoped live action disposition under the existing supervision
rules, execute its exact revision, verify config/env/image/readiness and
existing human/service login. No new runtime authority comes from this file.
`operation=remove` is an exact inventory removal proposal, not automatic
rollback: the activation adapter must remove only registrations it created,
preserve pre-existing identical entries and refuse intervening drift. Keep
custody material until recovery/retention requirements are met. Disabling/removing
registration stops new issuance after the issuer loads the change; issued JWTs
remain valid until expiry, and established connections need separate termination
through the transport owner. No immediate global revocation is claimed.
## Verification and completion
Python tests exercise preservation, add/no-op/remove, lifetime bounds, identity
conflicts, distinct credentials and contained invalid-input output. The native
Go fixture `tests/playwright_registration_native_test.go` is copied into a
disposable committed KeyCape checkout at `src/internal/authclient/` and consumes
the merged synthetic configuration through `PLAYWRIGHT_SYNTHETIC_CONFIG`.
It loads the config and domain registrations, performs native token/JWKS
exchanges for both client kinds, and checks dedicated claims, wrong secret,
scope, audience, signature, PKCE and callback refusals. It uses synthetic users
and seeded authorization sessions; it does not prove upstream login, production
image compatibility, native Kubernetes CEL validation or consumer eligibility.
T03 needs actual issuer issuance and native API allow/deny receipts, one real
human and one real registered coding agent independently running Platform's
consumer command and provider responsive/keyboard trial, renewal, revocation and
active connection termination. Cluster authentication and ops-bridge transport
remain their owners' implementations. Platform/provider must accept the receipts
before managed discovery is published. Same-image abrupt provider recovery is
already evidenced; cross-version rollback is still explicitly unproven.