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>
14 KiB
id, type, title, domain, repo, status, owner, topic_slug, created, updated, state_hub_workstream_id
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | state_hub_workstream_id |
|---|---|---|---|---|---|---|---|---|---|---|
| NK-WP-0004 | workplan | Credential Management Foundation | netkingdom | net-kingdom | done | custodian | netkingdom | 2026-03-20 | 2026-05-18 | d9cf7c4b-886b-4cd1-ad7b-99c4e1929c9e |
Credential Management Foundation
Goal
Make credential management a first-class, reliable foundation rather than a manual side-task. By the end of this workplan an operator can:
- Run
make creds-initto set up the full SOPS + age + KeePassXC workflow - Run
make creds-generateto produce all service secrets and be guided on KeePassXC entry - Run
make creds-applyto inject secrets into the cluster in the correct order - Run
make creds-statusto see what is generated, applied, and verified - Invoke
/creds-bootstrapin Claude Code for guided assistance through the bootstrap process
This workplan is a pre-condition for NK-WP-0003 (cluster deployment). NK-WP-0003-T01 is blocked until this workplan is complete.
Problem
Current state:
gen-secrets.shandpack-bundle.shexist but are run manually, in isolation, with no orchestration- The five
create-secrets.shscripts must be run in a specific order (postgres → lldap → authelia → privacyidea → keycape) but this is undocumented and unenforced - Shared secrets (LLDAP_LDAP_USER_PASS, PI_DB_PASSWORD) are referenced across component scripts but there is no enforcement that source exists before consumer runs
- No git pre-commit hook — plaintext secrets can accidentally be committed
- No
.sops.yaml— net-kingdom is not SOPS-enabled, unlike railiance-infra - No credential state file — no way to know which secrets are generated, which are applied, which are verified, without manual cluster inspection
- The enckey-bootstrap.sh step is time-sensitive (must run while the privacyIDEA pod is live) but nothing flags this or sequences it
- Operator must hold all of this in their head
Architecture
Operator
│
├── make creds-init # one-time: age key check, .sops.yaml, git hook
├── make creds-generate # run gen-secrets.sh → guided KeePassXC entry
├── make creds-bundle # age-encrypt ops bundle → offsite
├── make creds-apply # run all create-secrets.sh in correct order
├── make creds-verify # check all K8s secrets exist with expected keys
├── make creds-status # show credential state file
└── make creds-rotate SECRET=<name> # guided rotation for one secret
Claude Code skill: /creds-bootstrap
└── guided session for first-time bootstrap (reads credential state,
knows what's done, provides KeePassXC entry instructions,
warns about time-sensitive steps like enckey-bootstrap)
NK-WP-0006 Runtime Secret Refinement
This workplan remains the bootstrap credential foundation. With OpenBao in the platform stack, its outputs are not the final runtime secret model. They establish enough trust to bring up identity, MFA, and platform services safely.
Trust-state mapping:
- bare host and cluster trust are established by Railiance layers;
- bootstrap secret trust is established by SOPS/age, encrypted bundles, emergency material, and Kubernetes Secret injection;
- bootstrap identity trust is established by local/key-cape/bootstrap identity paths;
- runtime secret trust begins only after OpenBao is deployed, initialized, unsealed or auto-unsealed by the approved mechanism, audited, backed up, and ready to issue scoped secrets or dynamic credentials.
After runtime secret trust exists, Kubernetes Secrets created here should be treated as bootstrap artifacts, delivery caches, or compatibility mechanisms. Long-lived workload secret authority belongs in OpenBao, governed by NetKingdom policy and Railiance platform operations.
Dependency on canon standard
All design decisions in this workplan follow
canon/standards/credential-management_v0.2.md.
The KeePassXC group structure, phase model, SOPS policy, and prohibited
patterns defined there are normative. This workplan implements them.
Tasks
T01 — SOPS integration
id: NK-WP-0004-T01
status: done
priority: high
state_hub_task_id: "2340f2a3-9c11-44a8-b264-41d75b6dbc3e"
Add SOPS encryption infrastructure to net-kingdom, aligned with railiance-infra (same age key, same approach).
Steps:
-
Verify the operator age key exists:
ls ~/.config/sops/age/key.txt || age-keygen -o ~/.config/sops/age/key.txtThe public key (
age1aq8twfd78wvpra0had8cezcnj96tj4q0068edrz5jez8d6xwmflqdepsh4for the primary operator) is already in railiance-infra. Reuse the same keypair — one age key per operator across all repos. -
Create
keys/age.pubat the repo root:age1aq8twfd78wvpra0had8cezcnj96tj4q0068edrz5jez8d6xwmflqdepsh4 -
Create
.sops.yamlat the repo root:creation_rules: - path_regex: secrets/.*$ key_groups: - age: - age1aq8twfd78wvpra0had8cezcnj96tj4q0068edrz5jez8d6xwmflqdepsh4 -
Add
secrets/to.gitignore(plaintext secrets MUST NOT enter git). SOPS-encrypted files (.sops.yamlextension) may be committed. -
Create
.githooks/pre-commitmirroring railiance-infra:- Blocks any commit that includes a file under
secrets/lackingsops:or"sops":marker (i.e. plaintext) - Also blocks any file named
*.envoutside ofsso-mfa/bootstrap/being committed
- Blocks any commit that includes a file under
-
make hookstarget to enable the hook:hooks: git config core.hooksPath .githooks
T02 — Makefile: SOPS targets
id: NK-WP-0004-T02
status: done
priority: high
state_hub_task_id: "f6ad469c-e1d3-4253-b855-e0554e43f612"
Create the top-level Makefile for net-kingdom. Port SOPS targets from
railiance-infra and add net-kingdom-specific targets.
Targets to implement:
## One-time setup
sops-setup: # Copy age key to ~/.config/sops/age/keys.txt
hooks: # Enable git pre-commit hook
## SOPS operations
sops-edit: # sops <file>
sops-encrypt: # sops --encrypt --in-place $(FILE)
sops-decrypt: # sops -d $(FILE) (stdout only, never write plaintext to disk)
sops-rotate: # sops --rotate --in-place $(FILE) (after adding new recipient)
check-secrets: # fail if any secrets/ file is not SOPS-encrypted
## Credential lifecycle
creds-init: # prerequisite check + sops-setup + hooks
creds-generate: # run gen-secrets.sh + print KeePassXC entry guide
creds-bundle: # run pack-bundle.sh with operator age public key
creds-apply: # run all create-secrets.sh in dependency order
creds-verify: # check all expected K8s secrets exist
creds-status: # print credential state file
## Single-secret rotation
creds-rotate: # guided rotation for SECRET= (generate → KeePassXC → apply → verify)
T03 — Credential orchestrator: creds-apply ordering
id: NK-WP-0004-T03
status: done
priority: high
state_hub_task_id: "4b386b92-8db9-440c-b116-52dbb2bd68cb"
The creds-apply Makefile target must run create-secrets.sh scripts in
the correct dependency order, with prerequisite checks at each step.
Dependency graph:
postgres/create-secrets.sh (no dependencies)
│
lldap/create-secrets.sh (needs: lldap/secrets.env)
│
├── authelia/create-secrets.sh (needs: lldap/secrets.env → LLDAP_LDAP_USER_PASS)
│
└── keycape/create-secrets.sh (needs: lldap/secrets.env + PI_ADMIN_TOKEN)
└── PI_ADMIN_TOKEN available only after T04
privacyidea/create-secrets.sh (needs: privacyidea/secrets.env)
│
└── enckey-bootstrap.sh ← TIME-SENSITIVE: must run while pod is live
Implementation:
Create sso-mfa/bootstrap/creds-apply.sh that:
- Checks
KUBECONFIGis set and cluster is reachable - Checks each
secrets/<component>/secrets.envexists before sourcing it - Runs scripts in order: postgres → lldap → authelia → privacyidea
- Explicitly skips keycape (requires PI_ADMIN_TOKEN from post-T04 bootstrap)
- Prints the keycape step as a manual reminder with the exact command
- On success, updates
sso-mfa/bootstrap/creds-state.yaml
T04 — Credential state file
id: NK-WP-0004-T04
status: done
priority: high
state_hub_task_id: "5bc125a7-ae42-40a3-864c-c356e5fc122d"
Create sso-mfa/bootstrap/creds-state.yaml — a tracked file (safe to
commit, contains no secrets) that records what has been done:
# Credential state — net-kingdom SSO/MFA stack
# This file is safe to commit. It contains no secrets.
# Updated automatically by make creds-* targets.
generated_at: null # ISO datetime from last gen-secrets.sh run
bundle_at: null # ISO datetime from last pack-bundle.sh run
keepass_confirmed: false # Manually set to true after KeePassXC entry
secrets_applied:
postgres: false
lldap: false
authelia: false
privacyidea: false
keycape: false # Requires PI_ADMIN_TOKEN (post privacyIDEA T04)
enckey_bootstrapped: false # Set after enckey-bootstrap.sh runs
pi_admin_created: false # Set after bootstrap-admin.sh runs
The make creds-status target reads this file and prints a human-readable
status table. The make creds-verify target checks actual K8s secret
existence and updates secrets_applied accordingly.
keepass_confirmed is the only field that requires manual operator
intervention to set to true — it represents the irreducibly human step
in the bootstrap process.
T05 — git pre-commit hook + check-secrets gate
id: NK-WP-0004-T05
status: done
priority: high
state_hub_task_id: "d8ea8fbf-ae89-4675-afba-958187ca37f1"
Implement .githooks/pre-commit that prevents plaintext secrets from
entering git. Port from railiance-infra with net-kingdom-specific additions:
Blocks:
- Any file under
secrets/without a SOPS marker - Any file matching
*.envoutside ofsso-mfa/bootstrap/ - Any file containing any of these patterns:
PI_SECRET_KEY=,PI_PEPPER=,LLDAP_JWT_SECRET=,AUTHELIA_,BREAKGLASS_PASSWORD=
Warning only (does not block):
- Files matching
*-bundle*.tar.agebeing committed (large encrypted artifacts belong offsite, not in git)
Add make hooks-test target that verifies the hook blocks plaintext
(mirrors railiance-infra pattern).
T06 — Claude Code skill: /creds-bootstrap
id: NK-WP-0004-T06
status: done
priority: medium
state_hub_task_id: "b9ecbd3f-17f0-4c1d-97e5-84bfbb43d360"
Create ~/.claude/commands/creds-bootstrap.md — a Claude Code skill that
provides guided assistance during the credential bootstrap process.
When to use it: First-time bootstrap or onboarding a new operator.
The skill reads sso-mfa/bootstrap/creds-state.yaml and provides
contextual guidance based on what has been done.
Skill behavior:
- Read
creds-state.yamlto determine current state - Identify the next required step (first
falsein dependency order) - For KeePassXC entry steps: display the exact group path and field names
to enter, with values sourced from
secrets/env files (if present) - For time-sensitive steps (enckey-bootstrap): print a prominent warning with the exact command and timing constraint
- For verification steps: run
make creds-verifyand interpret results - After each confirmed step: prompt operator to update
creds-state.yamlor do it automatically when the state can be derived from cluster state
Skill definition file structure:
---
description: "Guide through net-kingdom credential bootstrap. Reads creds-state.yaml and provides step-by-step KeePassXC entry instructions, timing warnings, and verification."
argument-hint: "[--repo-path /path/to/net-kingdom]"
allowed-tools:
- Read
- Bash(make creds-status:*)
- Bash(make creds-verify:*)
- Bash(kubectl get secret:*)
---
Note: The skill does NOT automate KeePassXC entry (that remains a human step). It provides the information an operator needs to do it correctly and verifies the result afterwards.
T07 — Secret rotation runbook
id: NK-WP-0004-T07
status: done
priority: medium
state_hub_task_id: "e27762d9-aa6a-4a7e-9c34-f8c546797548"
Document and automate the rotation procedure for each secret type. Different secrets have different rotation complexity:
| Secret | Rotation impact | Procedure |
|---|---|---|
| PI_SECRET_KEY | Flask session reset — all users logged out | Stop pod, rotate, restart |
| PI_PEPPER | Cannot rotate without re-hashing all passwords | Treat as permanent |
| PI_DB_PASSWORD | DB + K8s Secret must be rotated atomically | pg GRANT + Secret update |
| LLDAP_JWT_SECRET | All LLDAP sessions invalidated | Rotate Secret, restart pod |
| LLDAP_LDAP_USER_PASS | Must update LLDAP + Authelia + KeyCape atomically | 3-step coordinated |
| AUTHELIA_SESSION_SECRET | All Authelia sessions invalidated | Rotate, restart |
| AUTHELIA_KEYCAPE_CLIENT_SECRET | Must update Authelia (bcrypt) + KeyCape simultaneously | Coordinated 2-step |
| KeyCape RSA signing key | All issued tokens immediately invalidated | Brief auth outage |
| PI_ENCFILE | Cannot rotate — replace and re-enroll all tokens | Major operation |
| BREAKGLASS_PASSWORD | Low impact, rotate freely | Simple update |
Implement make creds-rotate SECRET=<name> that:
- Validates the secret name is known
- Prints the rotation impact and required coordination steps
- Generates a new value (same entropy as original)
- Guides through the atomic update sequence for that secret
- Updates
creds-state.yamland ops bundle after rotation
Done criteria
make creds-initruns cleanly on a fresh workstation (age key check + setup)make creds-generateproduces all secrets and prints KeePassXC entry guidemake creds-bundleproduces an age-encrypted ops bundlemake creds-applyruns allcreate-secrets.shscripts in dependency ordermake creds-verifyaccurately reflects K8s secret statemake creds-statusshows a readable state table fromcreds-state.yamlmake hooks-testconfirms pre-commit hook blocks plaintext commits/creds-bootstrapskill loads, reads state, and provides correct next step- NK-WP-0003-T01 can be marked done by referencing this workplan as complete