Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02929-244b-7391-b933-c04010e8eedb
8.8 KiB
8.8 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.