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

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:

  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.