net-kingdom/history/2026-08-23-keycape-exposure-dependency-map.md
tegwick f2e578cf3e
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
feat(privacyidea): add guarded resolver reconciliation helper
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02929-244b-7391-b933-c04010e8eedb
2026-08-23 14:43:39 +02:00

9.7 KiB

KeyCape exposure dependency and rotation map

Date: 2026-08-23
Incident: KEYCAPE-EXPOSURE-20260823-01
Workplan: NK-WP-0033
NetKingdom revision reviewed: 9ae4e24305f30c32059d6da5459171c8c07931c7
Platform contract reviewed: railiance-platform 453fed3

This is value-safe planning evidence. It contains no credential value, encoded Secret payload, reusable hash, private key, or token. It does not authorize a live write.

Rulings

  1. The platform contract's single guarded KeyCape bundle is the correct anti-staleness boundary. keycape-config combines the signing private key and a full configuration document, so no independently generated full Secret may be applied during the incident.
  2. The LLDAP credential has four runtime consumers in this estate, not the two named in the platform contract: Authelia, KeyCape, identity-provisioner, and privacyIDEA's persisted lldap-coulomb resolver.
  3. Updating sso/lldap-secrets and restarting LLDAP does not by itself change the existing persistent admin account password. LLDAP documents LLDAP_LDAP_USER_PASS as initial-account input. The provider write must use a reviewed LLDAP password-change path; the Secret update is consumer and desired-state reconciliation.
  4. The privacyIDEA value embedded in KeyCape is an administrative session JWT issued by POST /auth, not an individually managed privacyIDEA MFA token. Current privacyIDEA documentation gives these JWTs an exp claim and a default one-hour validity (policy may override it), but exposes no individual session-JWT revocation endpoint. /token/revoke concerns user MFA authenticators and must not be used for this credential.
  5. Consequently, the platform contract cannot require both a 30-minute window and immediate predecessor-token revocation without another approved mechanism. T02 must choose either:
    • issue the successor, stop using the predecessor, retain a protected negative-test copy until its observed exp, and prove rejection after expiry; or
    • separately authorize rotation of privacyIDEA's JWT signing secret, with the wider impact and rollback limits documented. Changing the pi-admin password alone does not invalidate an already issued JWT.

Primary provider references:

Dependency map

Exposed class Authoritative provider write Consumer state that must change Reload/roll Predecessor-denial proof
KeyCape RS256 signing key KeyCape/railiance-platform guarded bundle apply using the KeyCape-approved signing strategy sso/keycape-config key.pem; downstream JWKS caches KeyCape and every declared downstream verifier KeyCape-owned controlled old JWT rejection after explicit invalidation or overlap expiry
LLDAP bind/admin credential Persistent LLDAP admin account through a reviewed official password-change path sso/lldap-secrets; Authelia's ldap_password; KeyCape lldap.bindPW; identity-provisioner LLDAP_ADMIN_PASSWORD; privacyIDEA resolver lldap-coulomb BINDPW Authelia, KeyCape, identity-provisioner; update/test privacyIDEA resolver; LLDAP only if its declared provider method requires it Protected old-value LDAP bind fails; replacement lookup passes in Authelia, KeyCape, identity-provisioner, and privacyIDEA
Authelia KeyCape client credential Authelia client registration hash in sso/authelia-secrets KeyCape authelia.clientSecret in the one guarded bundle Authelia and KeyCape Protected predecessor produces invalid_client; replacement completes the authorization-code exchange
privacyIDEA administrative JWT privacyIDEA POST /auth while predecessor remains valid KeyCape privacyidea.adminToken in the one guarded bundle; any separately maintained keycape-pi-token must be reconciled or retired as a duplicate source KeyCape Replacement performs the required MFA API call; predecessor rejects only after observed expiry unless global JWT-signing-secret invalidation is separately approved

Exact NetKingdom-owned sequence

These steps describe the required shape; they are not a live GO.

Prepare without provider writes

  1. Pin the reviewed non-secret KeyCape client configuration and all three repositories named by the platform contract.
  2. Create a mode-0700 private workspace with a cleanup trap. Receive each replacement through protected input into a separate mode-0600 file.
  3. Produce the Authelia bcrypt client hash from protected stdin/file, never a command argument. Do not record the hash as evidence.
  4. Build field-specific replacements for authelia-secrets and lldap-secrets. Preserve their unrelated fields from an owner-controlled input, not from captured agent output and not from a stale bootstrap bundle.
  5. Build exactly one complete KeyCape configuration and signing-key pair from the pinned non-secret client source and the replacement files.
  6. Prepare the privacyIDEA resolver update for lldap-coulomb with its complete non-secret resolver settings and the replacement bind value. Do not run the broad realm bootstrap, which also rewrites realms and policies.

Bounded cutover

  1. Guard all prepared writes on the approved Kubernetes UIDs/resource versions and Git revisions.
  2. Install the field-specific Authelia Secret changes without restarting it.
  3. Change the persistent LLDAP admin password using the reviewed provider path. Do not assume a Deployment restart applies the new value.
  4. Reconcile sso/lldap-secrets, update privacyIDEA resolver lldap-coulomb, and restart identity-provisioner. The resolver update must be a dedicated /resolver/lldap-coulomb operation, not full realm repair.
  5. Apply the single guarded sso/keycape-config bundle without rendering a manifest, then roll Authelia and KeyCape. The Authelia Secret update and the KeyCape bundle must contain the same LLDAP and client-credential revisions.
  6. Prove replacement LLDAP access through all four consumers, replacement Authelia client authentication, and replacement privacyIDEA access.
  7. Prove old LLDAP and Authelia credentials fail using protected inputs. Handle the old privacyIDEA JWT according to the corrected T02 decision.

After any provider write, abort is forward-only: finish or repair with the new revision. The exposed bundle and exposed predecessors are never rollback material.

Existing helper assessment

Helper Assessment for this incident
sso-mfa/bootstrap/creds-rotate.sh Prohibited. It prints replacements; its LLDAP branch omits identity-provisioner and privacyIDEA; its LLDAP provider change is manual; and its signing branch deliberately invalidates immediately.
sso-mfa/k8s/authelia/create-secrets.sh Not suitable as the cutover writer. It reconstructs the whole Secret from local bootstrap files, may generate unrelated signing material, and passes plaintext/hash values in command arguments. Its field mapping is useful as design evidence only.
sso-mfa/k8s/keycape/create-secrets.sh Not suitable. It reconstructs the full bundle from local files, can generate a new key implicitly, and its inline client list can lag the live reviewed source.
sso-mfa/k8s/keycape/refresh-pi-token-live.sh Not suitable. It reads the exposed live KeyCape payload, rebuilds the full Secret, and has no predecessor-expiry/revocation proof.
sso-mfa/k8s/privacyidea/repair-realm-live.sh Too broad. It updates the resolver but also reapplies realm, default-realm, and policy state. A dedicated resolver-only path is required.
sso-mfa/k8s/lldap/break-glass.sh password mutation Not accepted as the provider path. It passes the password in process arguments and assumes a GraphQL cleartext mutation that is not the documented general LLDAP password interface.

Remaining gates

  • KeyCape must publish and acknowledge its signing/JWKS/cache strategy.
  • Railiance-platform must correct the LLDAP consumer list and the privacyIDEA predecessor disposition in its owner contract.
  • NetKingdom must implement and review provider-specific, file-input helpers before T03 can complete. They must have dry-run/static tests proving that values cannot reach argv or stdout.
  • A named operator window, driver, abort operator, provider authority, and protected verification method are still required before any live action.

NetKingdom added sso-mfa/k8s/privacyidea/update-lldap-resolver-live.sh as the bounded resolver-only operation. It requires explicit --apply plus an interactive terminal, passes password files by pathname to a child process, updates only lldap-coulomb, and emits only a boolean result. It has not been run.

Owner-reported cutover outcome

KeyCape reported on 2026-08-23 that its governed recovery path completed the four-class replacement, including privacyIDEA signing-secret replacement, and that all four deployments were Ready with positive and negative checks passing. No credential values or live Secret payloads were returned to NetKingdom.

The remaining NetKingdom action is source reconciliation: Authelia 4.38 does not support *_FILE overrides for OIDC client list entries. The ConfigMap must therefore use the supported template filter reading the Secret-mounted bcrypt verifier, with X_AUTHELIA_CONFIG_FILTERS=template enabled in the Deployment.