generated from coulomb/repo-seed
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>
231 lines
7.7 KiB
Markdown
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.
|