Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02929-244b-7391-b933-c04010e8eedb
9.4 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
- The platform contract's single guarded KeyCape bundle is the correct
anti-staleness boundary.
keycape-configcombines the signing private key and a full configuration document, so no independently generated full Secret may be applied during the incident. - 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-coulombresolver. - Updating
sso/lldap-secretsand restarting LLDAP does not by itself change the existing persistentadminaccount password. LLDAP documentsLLDAP_LDAP_USER_PASSas initial-account input. The provider write must use a reviewed LLDAP password-change path; the Secret update is consumer and desired-state reconciliation. - 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 anexpclaim and a default one-hour validity (policy may override it), but exposes no individual session-JWT revocation endpoint./token/revokeconcerns user MFA authenticators and must not be used for this credential. - 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-adminpassword alone does not invalidate an already issued JWT.
- issue the successor, stop using the predecessor, retain a protected
negative-test copy until its observed
Primary provider references:
- LLDAP configuration describes the admin password as initial-account input and offers a force-reset recovery setting: https://github.com/lldap/lldap/blob/main/lldap_config.docker_template.toml
- LLDAP scripting documentation describes its authentication and password interfaces: https://github.com/lldap/lldap/blob/main/docs/scripting.md
- privacyIDEA authentication documentation describes
/authJWT issuance, theexpclaim, and default validity: https://privacyidea.readthedocs.io/en/stable/modules/api/auth.html
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
- Pin the reviewed non-secret KeyCape client configuration and all three repositories named by the platform contract.
- Create a mode-
0700private workspace with a cleanup trap. Receive each replacement through protected input into a separate mode-0600file. - Produce the Authelia bcrypt client hash from protected stdin/file, never a command argument. Do not record the hash as evidence.
- Build field-specific replacements for
authelia-secretsandlldap-secrets. Preserve their unrelated fields from an owner-controlled input, not from captured agent output and not from a stale bootstrap bundle. - Build exactly one complete KeyCape configuration and signing-key pair from the pinned non-secret client source and the replacement files.
- Prepare the privacyIDEA resolver update for
lldap-coulombwith 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
- Guard all prepared writes on the approved Kubernetes UIDs/resource versions and Git revisions.
- Install the field-specific Authelia Secret changes without restarting it.
- Change the persistent LLDAP
adminpassword using the reviewed provider path. Do not assume a Deployment restart applies the new value. - Reconcile
sso/lldap-secrets, update privacyIDEA resolverlldap-coulomb, and restart identity-provisioner. The resolver update must be a dedicated/resolver/lldap-coulomboperation, not full realm repair. - Apply the single guarded
sso/keycape-configbundle 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. - Prove replacement LLDAP access through all four consumers, replacement Authelia client authentication, and replacement privacyIDEA access.
- 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.
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.