net-kingdom/docs/keycape-exposure-resolver-reconciliation.md
tegwick c99b8da41f
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
docs: name where operator credentials live, and label unexercised procedures
Three gaps from NK-WP-0033, none of them the fault we set out to fix.

Operator custody (platform-root-custody.md). This document named a
password-safe entry that had never been created, so the 2026-08-23
rotation had nowhere to put the new LLDAP admin value and the outgoing
one survived only in a browser entry that was overwritten on 2026-08-27.
Decided: operators/<subsystem>/<account> in OpenBao for everything
reachable once logged in, plus a short closed offline list for what you
need when OpenBao is not — platform-root's own password, unseal shares,
break-glass. Anything on that list in OpenBao would be circular;
anything not on it does not belong in the packet.

The rule that closes it: every rotation names, as a numbered step, where
the outgoing and incoming values go. NK-WP-0033-T04 rotated four classes
with no such step, and that is the root cause of the session that
followed — not the four software defects it also surfaced.

Secret disclosure by trust stage, tied to the existing S0-S6 table. A
fingerprint may be spoken to confirm two parties hold the same
credential; the whole value never may. Used ad hoc throughout 2026-08-27
and worth writing down.

Attended procedures (new standard). reconcile-lldap-resolver-live.sh was
approved, documented, revision-pinned and had never run; its first
execution found four defects, two of which meant it could never have
completed. verify-t06.sh passed throughout. An unexecuted procedure is
unfinished work and must say so in its runbook header.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 3377672@bnt-lap001
Assistant-Session: 15463ccf-238f-4e13-b163-93aa25c6d166
2026-08-28 10:33:48 +02:00

126 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# KeyCape exposure: privacyIDEA resolver reconciliation
Incident: `KEYCAPE-EXPOSURE-20260823-01`
Workplan: `NK-WP-0033`
NetKingdom procedure: `sso-mfa/k8s/privacyidea/reconcile-lldap-resolver-live.sh` (repaired 2026-08-27 in `4a38511`; the previously pinned revision could not complete a run — see NK-WP-0033 T05)
Platform recovery contract: railiance-platform `453fed3`
Owner cutover receipt: State Hub message `45b236c8-052f-43d3-a472-44f8e9694da2`
Exercise status: exercised 2026-08-27 by Bernd Worsch — first run ever; four defects found and fixed (`4a38511`). See `attended-procedure-standard.md`.
This is the remaining attended provider-admin operation after the owner-reported
four-class cutover. It updates only privacyIDEA resolver `lldap-coulomb` so the
resolver uses the replacement LLDAP bind credential. It does not modify realms,
policies, tokens, KeyCape Secrets, or any other resolver.
No password, hash, token, Secret payload, or manifest belongs in this document,
State Hub, Git, chat, command arguments, or ordinary logs.
## Authority and pinning gate
The operator must record a private approval receipt containing, at minimum:
- incident `KEYCAPE-EXPOSURE-20260823-01`;
- the NetKingdom revision containing `reconcile-lldap-resolver-live.sh` and platform contract `453fed3`;
- the owner receipt `45b236c8-052f-43d3-a472-44f8e9694da2`;
- the exact start/end window, attended driver, and independent abort operator;
- confirmation that the replacement LLDAP credential is the provider-approved
value and that no exposed predecessor will be restored.
No live action is permitted if any revision, owner, cluster, or approval
identifier differs from the receipt.
## Preflight (metadata and health only)
Run from the approved operator workstation, with the cluster context and
provider endpoint already authorized. Do not render any Secret data.
1. Verify the approved checkout contains `reconcile-lldap-resolver-live.sh`
with mode `0755`; inspect its source, not live credential material.
2. Confirm LLDAP, privacyIDEA, KeyCape, Authelia, and
identity-provisioner are Ready using deployment/pod status fields only.
3. Confirm privacyIDEA availability via the documented unauthenticated
`/token/` probe (401/403 is expected) and KeyCape discovery health,
discarding response bodies. Do not use a command that prints a bearer token
or configuration response.
4. Confirm the approved window, driver, abort operator, provider custody, and
cleanup workspace are ready. Stop on any drift or missing owner.
## Apply (one attended operation)
1. From the approved checkout, run exactly:
```text
bash sso-mfa/k8s/privacyidea/reconcile-lldap-resolver-live.sh --apply
```
The helper securely prompts for pi-admin, replacement and predecessor
LLDAP passwords, and one approved MFA code; authenticates to privacyIDEA;
performs exactly one `POST /resolver/lldap-coulomb`; proves replacement
resolver lookup, privacyIDEA MFA, replacement LLDAP authentication, and
predecessor denial; rechecks readiness/health; securely cleans up; and
emits one sanitized PASS/FAIL receipt. It must not be combined with
`repair-realm-live.sh`, `bootstrap-realm.sh`, `creds-rotate.sh`, or any
full-bundle generator.
3. Stop immediately on any non-success response, timeout, unexpected endpoint,
or failed cleanup. Do not restore the exposed bundle or predecessor
credential.
## Postflight and predecessor denial
Record only status codes, readiness, timestamps, revision identifiers, and
boolean results.
1. Confirm privacyIDEA, LLDAP, KeyCape, Authelia, and identity-provisioner are
Ready again. Confirm the privacyIDEA health endpoint succeeds and the
resolver endpoint returns success without retaining its response body.
2. Exercise one approved KeyCape MFA path that requires the `coulomb` realm.
Record pass/fail only; never record the token or response body.
3. Using protected file inputs, prove an LDAP bind with the replacement value
succeeds and a bind with the exposed predecessor fails. The predecessor
test must be a boolean result and must not put the password in argv or
stdout. A failed predecessor bind is required evidence; do not retry it
against another provider.
4. Confirm the KeyCape owners existing four-class positive/negative receipt
remains associated with this resolver update. If any class lacks a receipt,
keep T05 open.
5. Securely remove the temporary workspace and record only cleanup success.
## Abort and rollback
Abort before mutation on revision drift, missing authority, unavailable health,
uncertain workspace cleanup, or any unsafe helper output. Abort forward after
mutation on a failed resolver response, failed readiness, failed replacement
MFA, or missing predecessor denial. The exposed bundle and every exposed
predecessor are never rollback material. Recovery after a partial write must
use a newly approved replacement value and revision, not a stale local bundle.
## Completion evidence
T03 may move to done only after the helper run and cleanup receipt are recorded
by the attended operator. T05 may move to done only after the resolvers
replacement success, predecessor denial, owner cutover receipt, and all
residual limitations are recorded as sanitized evidence.
## Revision note — 2026-08-27
The pinned revision of `reconcile-lldap-resolver-live.sh` had never completed a
run. Four defects were found by running it and are fixed in `4a38511`; see
`NK-WP-0033` T05 for the full findings. Two change how this procedure is
invoked:
- `--predecessor-unavailable` — use when the exposed predecessor cannot be
produced. The denial bind is not attempted and the receipt records
`predecessor denial=NOT-PROVEN`. Do **not** type a placeholder at the
predecessor prompt instead: a wrong value also fails the bind, and the run
records it as a *passing* denial proof — a receipt asserting a test that never
ran.
- `--note TEXT` — operator context carried verbatim in the receipt line, so a
claim and its caveat travel together. One line, 200 characters, no
credentials.
`TIMEOUT`, `CACHE_TIMEOUT` and `SIZELIMIT` are now sent with the resolver body
(default 5 / 120 / 500, overridable via the matching `LDAP_*` environment
variables). Before this fix, `--apply` dropped them, because a resolver write
replaces the whole object. A resolver with them unset still resolves users, but
the WebUI refuses to save or test it — so a hand repair was silently reverted by
the next run.