Files
net-kingdom/canon/standards/credential-management_v0.2.md
Bernd Worsch 95656f2324 feat(creds): NK-WP-0005 — agent-driven credential bootstrap
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>
2026-03-21 08:38:52 +00:00

231 lines
7.7 KiB
Markdown

# 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=<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
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-<date>.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=<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.