Files
net-kingdom/canon/standards/credential-management_v0.2.md
tegwick 7b211acd57 Add OpenBao runtime secret authority; complete NK-WP-0006/0007/0008
Refine the recursive platform security architecture to make OpenBao the
canonical runtime secret authority, with SOPS/age, K8s Secrets, and the
emergency bundle reframed as bootstrap/delivery/break-glass mechanisms.

- credential-management standard v0.2: add OpenBao runtime authority
  section, rotation rules, and prohibited patterns (OpenBao-as-PDP,
  tenant platform-root)
- platform-identity-security-architecture: mark implemented; add
  flex-auth/Topaz implications, Coulomb onboarding path, and a
  production-readiness checklist
- NK-WP-0004/0005: document bootstrap-to-OpenBao handoff boundary
- NK-WP-0006/0007: status -> done with implementation reviews; add
  recursive platform/tenant split and OpenBao broker/audit role for
  object-storage STS vending
- NK-WP-0008: status -> done; repoint corpus to infospace-bench
- new ADR-0007 (orchestration boundary), ADR-0008 (STS vending
  boundary), and the object-storage STS credential-vending architecture

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 22:51:20 +02:00

11 KiB

Credential Management Standard — net-kingdom

Version: 0.2 Status: current with OpenBao runtime refinement Supersedes: v0.1 (retired with NK-WP-0004)


1. Purpose

Define how service credentials are generated, stored, rotated, handed off to runtime secret authority, 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, its PostgreSQL backend, and the OpenBao runtime secret authority used by the platform control plane.


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  ──────────────────►  bootstrap/delivery state for running services
      │
      └──► bootstrap/delivery mechanism; created by create-secrets.sh scripts

OpenBao runtime authority ─────►  scoped workload secrets and dynamic credentials
      │
      ├──► leases, revocation, and audit records
      └──► direct client, External Secrets Operator, or CSI delivery

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 bootstrap credential store. The ops bundle is the bootstrap backup. The emergency bundle is the human's break-glass key ring. OpenBao is the runtime secret authority once the platform control plane is alive.


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. Hands off runtime secret authority to OpenBao when the Railiance platform layer is present and verified
  10. Creates the ops bundle (age-encrypted snapshot)
  11. Delivers the emergency bundle to the terminal for human storage ← only human touchpoint
  12. 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 — bootstrap or delivery state (updated by creds-apply.sh, External Secrets Operator, CSI, or other approved delivery paths)
  • OpenBao — runtime secret authority for platform services, workload secrets, dynamic credentials, leases, revocation, and audit

Once OpenBao is ready, new long-lived workload secret authority should be introduced there rather than by expanding the bootstrap SOPS/age surface.

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 6 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
OpenBao unseal/recovery material, if used Platform-control-plane break-glass only

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. OpenBao Runtime Authority

OpenBao is the canonical runtime secret authority for the platform control plane once deployed and verified. It stores, issues, leases, audits, and revokes runtime secret material; it does not replace identity or authorization.

Required boundaries:

  • SOPS/age remains the bootstrap and Git-at-rest protection mechanism.
  • OpenBao root tokens, unseal keys, recovery keys, platform mounts, and global auth methods are platform-root material.
  • Tenant administrators may manage tenant-scoped secret paths only through approved policies; they must not receive OpenBao platform-root authority.
  • Workloads should use scoped OpenBao auth roles, External Secrets Operator, CSI-mounted secrets, or another approved delivery mechanism.
  • flex-auth decides whether a secret or dynamic credential request is allowed when the request is authorization-sensitive; OpenBao performs storage, issuance, lease, revocation, and audit.
  • OpenBao audit logs must be shipped to durable storage and included in restore and break-glass drills.

OpenBao may issue dynamic credentials for databases, object storage, or other systems where provider support and policy make that safer than static secret distribution. Provider-native STS remains valid where it gives better-scoped temporary credentials; OpenBao can still broker, store, or audit the handoff where appropriate.


6. Secret Rotation

Rotatable secrets

Secret Blast radius Notes
OpenBao workload secret Variable Prefer lease expiry or dynamic regeneration
OpenBao auth role/policy Variable Requires policy review and audit check
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)

OpenBao root and recovery material

OpenBao root tokens, unseal keys, and recovery keys are not routine rotation targets. Treat them as platform-root break-glass material:

  1. Prefer short-lived or revoked root tokens after initial setup.
  2. Use scoped policies and auth methods for normal operations.
  3. Rotate recovery or unseal material only with an explicit maintenance window, backup verification, and post-rotation emergency-bundle update.

7. 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 -

8. 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

  6. Using OpenBao as a policy decision point — OpenBao stores, leases, audits, and revokes secret material; identity comes from the IAM Profile and authorization decisions come from flex-auth where a decision boundary is needed

  7. Giving tenants OpenBao platform-root authority — tenants may only receive scoped access paths approved for their tenant resources


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.