railiance-platform/docs/net-kingdom-credential-custody-contract.md
codex debf981097
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 3s
Close verified incident task and finish local workplan loose ends
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e3c3-621b-7350-9f77-50a8d3ee7657
2026-09-27 19:04:54 +02:00

133 lines
7.7 KiB
Markdown

# NetKingdom credential custody contract
Historical resolver lanes: **draft / blocked**.
KeyCape factor service lane: **active**, established and verified 2026-09-13 (below).
Incident: `KEYCAPE-EXPOSURE-20260823-01`
Consumer procedure: NetKingdom `NK-WP-0033`; latest attended check used
checkout `6096c395` (script `4a38511`) on 2026-09-23.
This document defines the Railiance-side contract without containing or
deriving any credential value. It is not an authorization to fetch, export,
apply, or rotate a secret.
## Ownership
- `railiance-platform` owns OpenBao custody, policy, delivery, rotation
metadata, and revocation evidence.
- `net-kingdom` owns the privacyIDEA resolver contract and attended consumer
reconciliation.
- `ops-warden` routes the lane only; it does not fetch or proxy either value.
## Required OpenBao metadata (owner must fill, never guess)
| Lane | Canonical path | Field | Delivery | Status |
| --- | --- | --- | --- | --- |
| `net-kingdom-lldap-bind-credential` | `REQUIRED_FROM_OPENBAO_OWNER` | `REQUIRED_FROM_OPENBAO_OWNER` | protected attended input to NetKingdom resolver helper | blocked |
| `net-kingdom-privacyidea-admin-token` | `REQUIRED_FROM_OPENBAO_OWNER` | `REQUIRED_FROM_OPENBAO_OWNER` | protected attended input to provider-admin helper | blocked |
The owner receipt must also identify the KV mount/version semantics, the
least-privilege read/update policy, the authentication method, the expiry or
rotation behavior, and the approved attended handoff. No value belongs in this
file, State Hub, Git, chat, argv, or ordinary logs.
## Consumer handoff
Once the OpenBao owner supplies the metadata above, Railiance-platform records
the receipt identifier and passes only protected input to the pinned
NetKingdom procedure. The resolver repair must use the minimal two-input
`--reconcile` flow; audit proofs (lookup, MFA, predecessor denial) remain a
separate read-only operation.
## Current gate
The routing lane is registered but remains `resolvable: false`. No
`warden access --fetch`, proxy execution, resolver retry, or predecessor
restoration is authorized until the OpenBao owner publishes the missing
metadata and the attended handoff is approved.
Operator decision 2026-09-15: leave both historical resolver lanes blocked.
Do not invent mount, path, or field names. The KeyCape factor service lane
below is separate and does not close this gate.
September 27 evidence review: NetKingdom's September 23 operator ruling and
green attended `--check --predecessor-unavailable` receipt close the incident
verification obligation (RPF-WP-0027-T05). The predecessor remains NOT-PROVEN;
the operator explicitly accepted its unavailable disposition. The owner records
human custody at `operators/lldap/admin` and `operators/privacyidea/pi-admin`,
including KV v2 LLDAP version 1 and withheld delete under `operator-custody`.
These are confirmed owner coordinates, but they do not by themselves establish
either historical route's complete field, auth, expiry and handoff contract.
T03/T06 and the non-resolvable historical routes therefore remain blocked.
See `docs/evidence/2026-09-27-keycape-incident-owner-return.json`.
## KeyCape factor service lane — authorized setup, 2026-09-13
RPF-WP-0040 / CCR-2026-0023 establish a new dedicated service lane. The user,
acting as platform operator, authorized establishing storage/delivery and performed
attended OpenBao authentication. This is separate from the historical resolver
repair lanes above; their incident receipts are not retroactively completed.
| Purpose | KV v2 CLI path | Fields | Consumer |
| --- | --- | --- | --- |
| Scoped provider JWT | platform/workloads/net-kingdom/keycape-factor-read | TOKEN, EXPIRES_AT, REQUEST | KeyCape via namespace-scoped ESO and adminTokenFile |
| Renewable service login | platform/workloads/net-kingdom/keycape-factor-issuer | USERNAME, PASSWORD, REQUEST | Owner bootstrap, then dedicated renewal worker; never KeyCape |
Provider identity: keycape-factor-reader (local privacyIDEA service admin).
Effective permission: tokenlist for realm coulomb only. Preserve the existing
no-admin-policy baseline for other administrators using an explicit fallback
that excludes this service identity. Only the two reviewed named policies may
be introduced; unexpected existing admin policies abort setup.
Generate the service password in owner-process memory and persist with KV CAS=0
before provider creation, allowing interrupted setup to resume. The attended
worker passes values through stdin/captured pipes only, never argv or receipts.
Only explicit KV absence permits creation. Existing request provenance and
policy definitions must match; retries do not rotate an existing identity.
Reader policy: workload-kv-read-keycape-factor-read, role
keycape-factor-workload-kv-read, bound sso/keycape-factor-eso, audience openbao,
15-minute maximum OpenBao session, no default policy. Metadata apply/readback
passed via the attended owner envelope. Provider issuance, native ESO delivery,
KeyCape mounted-file activation, per-user lookup and rotation without restart
passed. The reader can inspect and revoke only its own OpenBao session in
addition to reading the exact JWT data/metadata paths. See the acceptance record
`docs/evidence/2026-09-13-keycape-factor-custody.md`.
Provider JWTs expire independently of OpenBao sessions. Require 5 minutes to
2 hours remaining lifetime at issuance; renew well before expiry. Disable the
service's tokenlist policy (while retaining fallback exclusion) to withdraw its
factor access; do not rotate privacyIDEA's global signing key as routine renewal.
Verify expiry/permission refusal separately from OpenBao self-revocation.
Renewal: `sso/keycape-factor-renewer` runs every ten minutes under its own
service account, with a five-minute maximum OpenBao session. Its separate policy
reads the issuer password and reads/writes only the delivered JWT path. The
worker verifies provider identity, expiry, factor lookup and denied policy
administration before a CAS write and readback, then revokes its session.
ESO refreshes once per minute. KeyCape mounts only `admin-token`, mode 0440,
through fsGroup 65534; the issuer password never enters its namespace Secret.
Operations: `make keycape-factor-tests` runs offline failure/retry and config
preservation tests. `make keycape-factor-live-acceptance` creates bounded Jobs
that verify native delivery scope and a second renewal reaching the mounted
file. The runner emits booleans and resource identifiers only. CronJob failures
and ExternalSecret Ready=False are the native degraded-state signals. A failed
renewal leaves the last verified token in custody; expiry causes authentication
to fail closed. Restore provider/custody connectivity and launch a renewal Job
from the CronJob; verify SecretSynced and repeat live acceptance.
Attended setup is reproducible through `openbao-attended-exec.py` with
`keycape_factor_metadata.py --apply --receipt <new-file>`,
`keycape_factor_bootstrap.py --receipt <new-file>` and
`keycape_factor_renewal_metadata.py --receipt <new-file>` in that order, followed
by the custody manifest and guarded `keycape_factor_activate.py --apply`.
Use unique receipt paths. Existing mismatched policies/roles/provenance fail
closed; never replace an unknown owner credential. The separate
`keycape_factor_eso_metadata.py` records the migration from initial read-only
metadata to ESO's self-session permissions; fresh setup uses the final policy.
Outstanding acceptance is explicit in RPF-WP-0040-T04: genuine provider JWT
expiry and provider-policy revocation/recovery drills. A newly issued JWT does
not itself revoke its predecessor. Native mounted rotation has passed, but it
is not evidence of predecessor revocation. Optional MFA rollout and lost-factor
recovery remain KEY-WP-0035 / USER-WP-0030, not completed by this service lane.