# Credential Management Standard — net-kingdom **Version:** 0.2 **Status:** current **Supersedes:** v0.1 (retired with NK-WP-0004) --- ## 1. Purpose Define how service credentials are generated, stored, rotated, and recovered in the net-kingdom SSO/MFA platform. This standard governs operational security of all secrets used by the Authelia + LLDAP + KeyCape + privacyIDEA stack and its PostgreSQL backend. --- ## 2. Trust Hierarchy ``` age private key ─────────────► encrypts secrets.enc/ in git │ └──► ops bundle (encrypted snapshot of all secrets) └──► stored offsite; decrypt with age key break-glass passwords ────────► direct service access if cluster/auth is down │ └──► stored in human's personal password manager K8s Secrets ──────────────────► live credential store for running services │ └──► created by create-secrets.sh scripts; sourced from secrets.enc/ ``` **KeePassXC is NOT in the operational path.** If you choose to import the emergency bundle into KeePassXC for personal use, that is your business — it is not required or assumed by any tooling in this repo. The age private key and SOPS/age-encrypted git files are the credential store. The ops bundle is the backup. The emergency bundle is the human's key ring. --- ## 3. Credential Lifecycle ### Phase 0 — Bootstrap (run once, agent-driven) ``` make creds-agent-init ``` This single command runs the full bootstrap end-to-end: 1. Verifies prerequisites (age, kubectl, openssl, cluster) 2. Generates or verifies the age keypair at `~/.config/sops/age/keys.txt` 3. Generates all service secrets via `gen-secrets.sh` 4. Encrypts them to `secrets.enc/` with age and commits 5. Injects them into the cluster via `creds-apply.sh` 6. Verifies all K8s Secrets exist 7. Waits for privacyIDEA to be Ready, then runs enckey bootstrap + admin creation 8. Applies KeyCape secrets (requires pi-admin) 9. Creates the ops bundle (age-encrypted snapshot) 10. Delivers the emergency bundle to the terminal for human storage ← **only human touchpoint** 11. Shreds all plaintext and marks `bootstrap_complete: true` The script resumes from where it left off if interrupted — each phase is tracked in `creds-state.yaml`. ### Phase 1 — Normal operation No human credential management is needed after bootstrap. All secrets live in: - `secrets.enc/` — encrypted in git (decrypt with age key) - K8s Secrets — live cluster state (updated by `creds-apply.sh`) ### Phase 2 — Rotation ``` make creds-rotate SECRET= # guided interactive mode SECRET= bash sso-mfa/bootstrap/creds-rotate.sh --non-interactive # agent mode ``` Rotation is handled per-secret with appropriate atomicity guarantees. See Section 5 for details. ### Phase 3 — Recovery Use the emergency bundle stored in your personal password manager. See Section 4 — Emergency Bundle. --- ## 4. Emergency Bundle ### Contents | Item | Purpose | |------|---------| | age private key | Decrypt any `secrets.enc/*.age` file from git | | privacyIDEA admin password | Direct access to privacyIDEA if cluster/auth is down | | LLDAP admin password | Direct LDAP directory access | | PostgreSQL root password | Direct database access | | break-glass user password | Emergency login if Authelia/KeyCape is down | | ops bundle location + decrypt command | Point-in-time snapshot of all secrets | ### Delivery The emergency bundle is displayed once in the terminal at the end of `make creds-agent-init`. The operator must copy it into their personal password manager before pressing Enter to continue. The confirmation gate is a **deliberate security control** — bootstrap does not complete until the human confirms receipt. ### Re-delivery If the emergency bundle is lost or stolen: ``` make creds-emergency-reprint ``` This reprints the bundle from the current age key and secrets. It does **not** rotate any secrets — if you suspect compromise, rotate the affected secrets separately with `make creds-rotate`. ### Storage recommendations Store the emergency bundle in one or more of: - 1Password, Bitwarden, KeePassXC, or equivalent - Encrypted offline storage - Printed and physically secured (air-gapped scenarios) The agent does not care which — it only cares that you confirm receipt. ### Recovery procedure 1. Restore `~/.config/sops/age/keys.txt` from the emergency bundle 2. Clone net-kingdom repo 3. Run: `make creds-apply` — re-injects all secrets from `secrets.enc/` 4. Use break-glass passwords for direct service access if needed --- ## 5. Secret Rotation ### Rotatable secrets | Secret | Blast radius | Notes | |--------|-------------|-------| | `PI_SECRET_KEY` | Low — invalidates PI sessions | Safe to rotate anytime | | `PI_DB_PASSWORD` | Medium — DB + pod | Atomic update required | | `LLDAP_JWT_SECRET` | Low — invalidates LLDAP sessions | Safe to rotate anytime | | `LLDAP_LDAP_USER_PASS` | High — 3-way coordinated | Authelia + KeyCape + LLDAP web UI | | `AUTHELIA_SESSION_SECRET` | Low — invalidates sessions | Safe to rotate anytime | | `AUTHELIA_KEYCAPE_CLIENT_SECRET` | Medium — 2-way atomic | Authelia + KeyCape together | | `KEYCAPE_RSA_KEY` | High — invalidates all JWTs | Causes brief auth outage | | `BREAKGLASS_PASSWORD` | Minimal | Rotate freely | ### Non-rotatable | Secret | Reason | |--------|--------| | `PI_PEPPER` | Rotating requires re-hashing all PI user passwords | ### Age key rotation Rotating the age private key is a special case: 1. Generate a new age key 2. Re-encrypt all `secrets.enc/` files with the new public key (`make sops-rotate`) 3. Commit 4. A **new emergency bundle must be delivered** before the old key is revoked (see Section 4) --- ## 6. Ops Bundle The ops bundle is an age-encrypted tar archive of all plaintext secrets at a point in time. It is created automatically during bootstrap and can be refreshed with: ``` make creds-bundle ``` Store the bundle offsite (cloud, external drive, second location). Decrypt: ``` age -d -i ~/.config/sops/age/keys.txt ops-bundle-.tar.age | tar xf - ``` --- ## 7. Prohibited Patterns The following are permanently prohibited: 1. **Committing `secrets/` (plaintext) to git** — the pre-commit hook blocks this; do not bypass with `--no-verify` 2. **Storing secrets in KeePassXC as the primary credential store** — KeePassXC is for optional personal backup of the emergency bundle only 3. **Skipping the `emergency_bundle_delivered` confirmation gate** — even in non-interactive or automated runs, the agent MUST NOT mark bootstrap complete until the human confirms receipt. This gate exists to ensure break-glass access is always available. 4. **Hardcoding secrets in manifests or ConfigMaps** — all service secrets must flow through K8s Secrets created by `create-secrets.sh` scripts 5. **Storing the age private key in the repo** — the key lives outside the repo at `~/.config/sops/age/keys.txt` --- ## Appendix A — KeePassXC Group Structure (optional) If you choose to import the emergency bundle into KeePassXC, a suggested group structure for the break-glass passwords is: ``` net-kingdom/ Break-glass/ privacyIDEA admin username=pi-admin password= LLDAP admin username=admin password= PostgreSQL root username=postgres password= break-glass user username=break-glass password= age key (attach keys.txt as binary attachment) ops bundle decrypt (store path + decrypt command as a note) ``` This is entirely optional — the agent does not read from or write to KeePassXC.