net-kingdom/docs/playwright-registration.md
tegwick 276b95f6ba
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
security: reject Playwright audience and delivery alias collisions
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a1346b-72a4-7d40-b8bd-8b8ca346cfa2
2026-10-13 23:44:41 +02:00

11 KiB

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:

[
  {
    "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

Guarded activation patch preparation

sso-mfa/k8s/keycape/playwright-activation.py now implements the private proposal, ordered patch preparation and readback checks for the existing protected owner path. It has no CLI or Kubernetes connection. build takes original Secret and Deployment API objects, the complete identity-reference inventory and the exact reviewed source revision. Its result contains the private original registry and must remain inside that execution path. Only summary is a public receipt. Source revision and digest bindings do not constitute an approval or custody grant.

Inventory checks use the issuer's effective audience, including its client-ID fallback when audience is absent/empty. A second issuer environment variable pointing to the same proposed agent Secret/key is refused even if its name differs. These checks do not replace Platform's wider custody/consumer isolation review.

The adapter admits one replica, Recreate, the pinned immutable KeyCape image, the expected registry mount and explicit environment bindings. It preserves unrelated settings and refuses differing bindings. check_registry pins the native verifier's binary digest, checks both configurations using their exact environment-variable names and policy-store mode, passes private input on stdin, clears ambient environment overrides and contains diagnostics. The operator must separately establish verifier compatibility with the deployed image; a locally built verifier is not production artifact admission.

The source deployment cannot atomically update its registry and environment: an environment edit starts a rollout, and pending authorization codes are process-local. The prepared sequence therefore requires an explicit exclusive maintenance window with a planned authentication interruption:

  1. stop: bind the original object UIDs, resource versions, data and complete Deployment spec; require the original single replica Ready; scale to zero.
  2. Wait for the controller to observe that generation with zero replicas and for every pod consuming keycape-config to disappear. Supply an unfiltered, complete sso PodList; terminating pods still count. Direct/projected volumes, env references and init/ephemeral containers are checked.
  3. registry: conditionally write the exact proposed registry data while stopped.
  4. bindings: conditionally add the agent environment bindings while still stopped.
  5. resume: after fresh matching registry/spec and quiescence checks, restore one replica. Then the protected executor must verify generation, exact pod/image, readiness, real issuance and existing human/service login. Scaling is not that acceptance proof.

Fresh reads of both objects precede every propose_step. Each patch tests the target UID, resourceVersion and complete data/spec before replacement. Use verify_response on the API response and subsequent fresh GET, retaining each sanitized receipt. Refuse and stop on any mismatch or unknown outcome; no automatic retry or stale whole-registry replay. Kubernetes offers no cross-object transaction, so concurrent issuer writers must be excluded by the owner maintenance procedure.

Rollback uses the retained original proposal, never a newly inferred baseline: rollback-stop, observed quiescence, rollback-registry, rollback-bindings, rollback-resume, then the same native readiness/login verification. Exact original/candidate matching preserves registrations and bindings that predated this proposal. Any unrelated intervening data/spec change refuses rollback for owner reconciliation. No completed changes means no rollback outage. Retain private recovery state after failure; the adapter neither writes it to disk nor destroys credentials. Token expiry and connection-termination limits above remain.

Before wiring this into live execution, T02 still requires real identity/custody references, the protected executor and recovery-state custody, cluster/current image pinning, the exact supervisor disposition, and source/GitOps reconciliation. Do not invoke the generic browser helper with a fabricated --case-acceptance.

Activation tests use the independent jsonpatch Python package to apply RFC 6902 patches to synthetic API objects (test prerequisites: PyYAML and jsonpatch). They check ordered/partial recovery, created-only preservation, stale writes, quiescence, revision/readback refusal and verifier containment. Controller state is simulated; neither these tests nor native check-config replace live API and issuer acceptance.

Registration and consumer checks

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.