Implements all 7 tasks from NK-WP-0005:
T01: creds-state.yaml → schema_version: 2, agent_mode: true
Replaces keepass_confirmed with emergency_bundle_delivered,
adds phase tracking fields for fully automated flow.
T02: creds-bootstrap-agent.sh — single entrypoint for autonomous
bootstrap. 10 phases, idempotent re-runs via state file.
Only human touchpoint: emergency bundle confirmation gate.
T03: emergency-bundle.sh — assembles and displays emergency bundle
(age key + break-glass passwords + ops bundle location).
Writes temp file, shreds on confirmation, clears screen.
Supports --reprint for re-delivery.
T04: ~/.claude/commands/creds-init.md — /creds-init skill replaces
/creds-bootstrap. Fully autonomous execution via the agent.
T05: Makefile — creds-agent-init, creds-agent-status,
creds-emergency-reprint targets.
T06: creds-rotate.sh — --non-interactive flag for agent-driven
rotation. Auto-confirms all gates; tracks last_rotated_<key>
in creds-state.yaml. LLDAP web UI step prints warning in
non-interactive mode.
T07: canon/standards/credential-management_v0.2.md — updated
standard: KeePassXC removed from operational path, agent
bootstrap as Phase 0, emergency bundle section, prohibited
patterns updated.
Also: creds-status.sh handles both schema v1 (legacy) and v2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.7 KiB
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:
- Verifies prerequisites (age, kubectl, openssl, cluster)
- Generates or verifies the age keypair at
~/.config/sops/age/keys.txt - Generates all service secrets via
gen-secrets.sh - Encrypts them to
secrets.enc/with age and commits - Injects them into the cluster via
creds-apply.sh - Verifies all K8s Secrets exist
- Waits for privacyIDEA to be Ready, then runs enckey bootstrap + admin creation
- Applies KeyCape secrets (requires pi-admin)
- Creates the ops bundle (age-encrypted snapshot)
- Delivers the emergency bundle to the terminal for human storage ← only human touchpoint
- 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=<name> # guided interactive mode
SECRET=<name> 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
- Restore
~/.config/sops/age/keys.txtfrom the emergency bundle - Clone net-kingdom repo
- Run:
make creds-apply— re-injects all secrets fromsecrets.enc/ - 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:
- Generate a new age key
- Re-encrypt all
secrets.enc/files with the new public key (make sops-rotate) - Commit
- 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-<date>.tar.age | tar xf -
7. Prohibited Patterns
The following are permanently prohibited:
-
Committing
secrets/(plaintext) to git — the pre-commit hook blocks this; do not bypass with--no-verify -
Storing secrets in KeePassXC as the primary credential store — KeePassXC is for optional personal backup of the emergency bundle only
-
Skipping the
emergency_bundle_deliveredconfirmation 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. -
Hardcoding secrets in manifests or ConfigMaps — all service secrets must flow through K8s Secrets created by
create-secrets.shscripts -
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=<PI_ADMIN_PASSWORD>
LLDAP admin username=admin password=<LLDAP_LDAP_USER_PASS>
PostgreSQL root username=postgres password=<PG_ROOT_PASSWORD>
break-glass user username=break-glass password=<BREAKGLASS_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.