NET-WP-0017: complete T06 dry-run + T07 review/retire (onboarded+locked+offboarded t06-dryrun test user via T05 flow + verifs; evidence+validate pass; archived superseded 0015/16 + old NK-0003/4/5 bootstrap plans per T07; set platform_reopened; updated T06/T07 notes + frontmatter finished)

This commit is contained in:
2026-06-03 02:01:38 +02:00
parent 8ad71f7f26
commit bcac6076cb
6 changed files with 31 additions and 3 deletions

View File

@@ -0,0 +1,514 @@
---
id: NET-WP-0015
type: workplan
title: "King Credential And OpenBao Identity Bootstrap"
domain: netkingdom
repo: net-kingdom
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-05-24"
updated: "2026-06-01"
depends_on:
- NK-WP-0006
- NK-WP-0012
state_hub_workstream_id: "6b9c25e4-1008-429a-8de6-54361872c0dd"
---
# NET-WP-0015 - King Credential And OpenBao Identity Bootstrap
## Goal
Define and execute the first safe bridge between low-trust setup operations, a
dedicated king credential, NetKingdom identity, and Railiance OpenBao
bootstrap.
The revised decision is that `tegwick` / `bernd.worsch@gmail.com` is the
initial accountable setup operator and notification contact, not the long-term
platform root of trust. The actual platform-root authority should move to a
separate king credential before OpenBao becomes live secret custody.
## Context
Railiance owns OpenBao deployment and operations. NetKingdom owns the identity,
custody, and security semantics that say who can administer the platform and
how that authority transitions from bootstrap material into normal IAM claims.
The platform is still in MVP/prototype bootstrap. That means early databases,
admin accounts, tokens, and access paths must be treated as potentially
contaminated by convenience. The platform should be assembled in low-trust
mode, then handed over to the king credential, reset/rotated, checked, and
reopened under explicit custody.
## Scope
In scope:
- record the setup operator/contact identity;
- define the separate king credential target;
- define the temporary single-operator king custody exception;
- specify target NetKingdom IAM claims for the first admin identity;
- coordinate the OpenBao initialization prerequisites with Railiance;
- define the transition from OpenBao root token to scoped admin access; and
- add follow-up gates for independent escrow, OIDC/JWT admin auth,
reset/rotation, scan checks, and restore verification.
Out of scope:
- storing any secret material in this repo;
- running `bao operator init` from an unattended agent session;
- deploying key-cape, Keycloak, privacyIDEA, or OpenBao itself; and
- granting tenant administrators platform-root authority.
## Tasks
### T01 - Record Setup Operator And King Credential Model
```task
id: NET-WP-0015-T01
status: done
priority: high
state_hub_task_id: "60659e25-fed1-478e-b8a3-4bc7b2f3846b"
```
Record `tegwick` / `bernd.worsch@gmail.com` / Gitea `tegwick` as the initial
setup operator and contact. Define the separate king credential as the actual
platform-root target.
**2026-05-24:** Added `docs/platform-root-custody.md` and updated
`docs/platform-identity-security-architecture.md` plus `SCOPE.md`.
**2026-05-24:** Revised the custody model: `tegwick` is no longer modeled as
the platform root of trust. The day-to-day account can assemble and observe the
platform, while a dedicated king credential receives final custody after the
guided bootstrap path is ready.
### T02 - Define King Credential Kit
```task
id: NET-WP-0015-T02
status: done
priority: high
state_hub_task_id: "1a1c45a2-be66-4667-89f8-581f4fe9970b"
```
Define the first king credential kit: dedicated identity name, local/offline
password-safe storage, second factor, recovery-code handling, no email secret
transfer, no day-to-day browsing/Git use, and operator instructions clear
enough for a non-expert.
**2026-05-24:** Defined the v1 kit in
`docs/security-bootstrap-king-credential-kit.md`: label `platform-root`, setup
operator/contact `tegwick`, notification-only email
`bernd.worsch@gmail.com`, local password safe plus offline custody packet,
TOTP/WebAuthn/hardware-token second factor, no day-to-day use, and no email or
Git secret transfer. Added
`examples/security-bootstrap/king-credential-metadata.example.json` plus
console validation for non-secret kit metadata. Custody-mode approval remains
blocked under T03.
### T03 - Approve King Custody Mode
```task
id: NET-WP-0015-T03
status: done
priority: high
state_hub_task_id: "56a6266a-4acd-41e6-a395-85e90a5c35c6"
```
Choose either the preferred independent two-of-three king custody model or an
explicit temporary single-operator king credential exception for pre-production
bootstrap. Do not run OpenBao initialization until this choice is recorded.
**2026-05-24:** Added local approval surfaces for this human gate:
`approve-custody-mode` for the CLI and `web-ui` for the localhost console.
Both write non-secret metadata only and keep live OpenBao initialization as a
separate attended ceremony. Current recommended approval mode is
`temporary-single-king`; `two-of-three-planned` records the target state but
does not unblock live init.
**2026-05-24:** Tightened MFA handling after review: a TOTP QR code or setup
key must come from the authority that will verify login, not from the local
metadata console. Custody approval now requires explicit non-secret
confirmation that the factor was enrolled with its real verifier.
**2026-05-24:** Clarified credential placement in the UI and custody docs:
the dedicated king account currently belongs in the lightweight NetKingdom
identity path (LLDAP user, Authelia login, privacyIDEA MFA, KeyCape OIDC).
OpenBao is the secrets/audit/admin-policy custody service after the ceremony,
not the place where the human password or OTP seed lives.
**2026-05-24:** Expanded the local UI toward a NetKingdom control surface:
the bootstrap flow now has action buttons for LLDAP, privacyIDEA, and KeyCape,
plus non-secret progress saving for account creation, MFA enrollment, OIDC
verification, and custody approval.
**2026-05-24:** Clarified the LLDAP first-user path in the UI and docs:
LLDAP has no registration flow; the operator logs in as bootstrap `admin`
using `LLDAP_LDAP_USER_PASS` from `net-kingdom/LLDAP/admin`, then creates the
dedicated `platform-root` or `king` account and assigns the current lightweight
admin group.
**2026-05-24:** Added explicit non-secret UI confirmations for the account
having been created, assigned to `net-kingdom-admins`, stored in the password
safe/offline packet, and later verified through the login path. Automated
LLDAP detection is deferred because it would require authenticated access to
LLDAP and should be built as an audited integration.
**2026-05-24:** Improved the KeyCape login-check path: the local bootstrap UI
now acts as the `demo-app` OIDC callback, exposes `/oidc/start` and
`/oidc/callback`, and adds hover-help text to the external action buttons.
The live KeyCape rollout still needs the updated `keycape-config` Secret
applied from decrypted `sso-mfa/bootstrap/secrets/` inputs. If the browser
flow reaches Authelia but never presents an OTP challenge, KeyCape needs a
browser MFA prompt surface before this gate can be marked verified.
**2026-05-24:** Filed `KEY-WP-0003` in the KeyCape repo for the current OIDC
verification blocker. The immediate error
`redirect_uri does not match any registered URI` means the local bootstrap
callback is not yet registered in live KeyCape. The follow-up KeyCape work also
covers the browser OTP challenge needed after Authelia password login.
**2026-05-24:** Implemented `KEY-WP-0003` in source. KeyCape now supports a
dedicated `netkingdom-bootstrap-console` client, split browser/server Authelia
URLs, and a browser OTP challenge before issuing the final OIDC code. The local
control surface now uses that dedicated client. Live verification remains
pending until the updated KeyCape image and regenerated `keycape-config` Secret
are rolled out.
**2026-05-24:** Rolled the fix to the public Railiance SSO host
(`kc.coulomb.social`, currently resolving to `railiance01`). The live
`keycape-config` Secret was patched without printing or rotating secret values,
the `main-1d68639` KeyCape image was direct-imported into k3s, and the
deployment was set to `IfNotPresent`. Public `/authorize` now accepts
`netkingdom-bootstrap-console` and redirects to
`https://auth.coulomb.social/...`. Follow-up: clean up the Gitea HTTP registry
push/pull path so direct image import is no longer needed.
**2026-05-24:** Fixed the next live login failure before OTP: Authelia rejected
KeyCape's token exchange because the upstream `keycape` client only permits
`client_secret_basic`, while KeyCape was sending `client_secret_post`. KeyCape
commit `56d279a` now uses HTTP Basic auth for the upstream token exchange, the
image `main-56d279a` was direct-imported into Railiance k3s, and the live
deployment runs that tag.
**2026-05-24:** Fixed the follow-up `mfa check error`. Live privacyIDEA
validation succeeds in the `coulomb` realm, while KeyCape had been configured
for `netkingdom` and was also trying to pre-list tokens with an expired or
invalid privacyIDEA admin JWT. KeyCape commit `937cb39` adds bootstrap mode
`privacyidea.requireForAll`, which requires OTP for every authenticated user
without depending on token-list admin credentials. The live `keycape-config`
now uses `realm: coulomb` and `requireForAll: true`, and Railiance runs image
`main-937cb39`.
**2026-05-25:** Fixed the subsequent token-exchange `user not found` error.
Live LLDAP stores users under `ou=people`, while KeyCape's default lookup base
was `ou=users`. KeyCape commit `06d20c3` makes the LLDAP OU settings explicit
in YAML, live `keycape-config` now sets `userOU: ou=people` and
`groupOU: ou=groups`, and Railiance runs image `main-06d20c3`.
**2026-05-25:** End-to-end OIDC login verification succeeded for
`platform-root`. The local bootstrap-console callback exchanged the code and
showed issuer `https://kc.coulomb.social`, audience
`netkingdom-bootstrap-console`, subject
`uid=platform-root,ou=people,dc=netkingdom,dc=local`, email
`bernd.worsch@gmail.com`, and group `net-kingdom-admins`. Local non-secret
bootstrap progress now records both MFA enrollment confirmation and OIDC login
verification.
**2026-05-25:** Reworked the bootstrap-console flow after operator review. The
UI now follows the use case top to bottom, hides hardware-token storage unless
the selected policy uses hardware tokens, specifies the exact recovery material
contents, distinguishes recovery material from the OpenBao custody packet, and
turns "no secret capture" into an automatic control-surface boundary gate
rather than a user checkbox.
**2026-05-25:** Corrected the custody/OpenBao ordering in the console:
strategy selection now comes before recovery/packet preparation, the custody
packet is prepared for the selected strategy before approval, and the OpenBao
panel now explains when to run Railiance preflight, init/unseal,
post-unseal configuration, root-token disposition, and restore proof. The
console still refuses to capture root tokens or unseal shares.
**2026-05-25:** Restructured the bootstrap UI around the operator mental model:
Roles & Responsibilities, Subsystems & Scope, Integration & Tests, and
Artefacts & Locations. Role, subsystem, integration, and artefact rows now use
the same `name`, `description`, `subsystem`, `responsibility`, `location`, and
`state` fields, and console commands are shown as copyable command blocks.
**2026-05-25:** Refined the new model after operator review: role chips now sit
under subsystem labels to keep artefact rows narrow, responsibility editing is
inside a dirty-state Save/Cancel foldout, future quorum contact uses the same
effective-value prefill as the role display, and command cards now derive
`blocked`, `todo`, `redo`, or `done` status from bootstrap metadata.
**2026-05-25:** Added a Usecases & Runbooks section for trial-output exposure
and key-material compromise. The UI now records non-secret compromise response
state, separates "init output produced" from "initialized and unsealed", and
adds guided command cards for unseal and OpenBao `rotate-keys` replacement
share generation.
**2026-05-25:** Changed compromised/trial-exposed OpenBao material from a hard
block into an explicit taint model. Affected artefacts and downstream command
cards are shown with a light red background and retain the source reference, but
the operator can still proceed deliberately on a tainted workpath.
**2026-05-25:** Split OpenBao initial configuration from root-token disposition
in the bootstrap console. The initial config command can now be recorded as
applied while root-token revocation/escrow remains a separate gate.
**2026-05-25:** Added an Emergency lock-down runbook for sealing Railiance
OpenBao without placing tokens on the command line. Reordered the console into
Introduction & Actors, Subsystems & Scopes, Roles & Responsibilities,
Integration & Tests, Artefacts & Locations, Usecases & Runbooks, and
Terminology & Patterns.
**2026-05-25:** Added Restore drill runbook action cards so the existing
confirmation checkbox has a concrete path: prepare a restricted workspace,
create/copy/hash an OpenBao Raft snapshot, encrypt it to the custodian age
recipient, complete an isolated restore proof, rerun post-unseal verification,
and record only non-secret completion evidence.
**2026-05-25:** Refined the action/runbook model in the control surface:
Integration & Tests now carries stateful runbook tasks and gates, while
Usecases & Runbooks contains status-less action cards and neutral runbook
templates. Added copyable OpenBao inspection actions for `bao audit list`,
`bao secrets list`, and `bao auth list` with local hidden token prompts,
removed duplicate OpenBao status/unseal cards from the stateful Integration
command list, and restored Artefacts & Locations above Usecases & Runbooks in
the workflow.
**2026-05-25:** Added a five-stage visual stage rail and Final Handover
section to close the gap between OpenBao bootstrap and the final operating
state. The stage model now moves from S3 to S4 after OpenBao initial
configuration, root-token disposition, and restore drill are complete, then to
S5 only when the platform is explicitly reopened under custody.
**2026-05-25:** Corrected the OpenBao rotate-keys action cards after the
operator hit `permission denied` on rotation init. The rotation commands now
open an interactive pod TTY, prompt there for a root/sudo-capable OpenBao
token, keep the token out of the local command line, and then run rotate init,
share submission, or cancel.
**2026-05-26:** Added an explicit rotation-status action and clarified the
rotation flow after the operator successfully started rotate-keys and then hit
`rotation already in progress` by rerunning init. The UI now says init is a
run-once step and that the next step is checking status or submitting existing
shares with the nonce until quorum completes.
**2026-05-26:** Added a Usecases action card for creating the temporary
Railiance OpenBao `platform-admin` token with
`bao token create -policy=platform-admin -period=24h -orphan`. The command
prompts for the bootstrap/root token without placing it on the command line
and reminds the operator to store the emitted token through the approved secret
path.
**2026-05-26:** Promoted the KeyCape-to-OpenBao admin path into its own stage
before cleanup and hardening. The control surface now has S4 Admin Identity
Integration with gates for the dedicated KeyCape OpenBao client, OpenBao
OIDC/JWT auth configuration, and MFA-backed OpenBao admin login verification;
cleanup and reopening move to S5/S6.
**2026-05-26:** Refined the OpenBao trial-exposure taint model so direct
unseal-share taint clears after confirmed unseal-key rotation, and direct
initial-root-token taint clears after the exposed OpenBao root token is
revoked. Downstream work remains visibly tainted until derived access paths
are reviewed and the compromise response is explicitly recorded complete.
**2026-05-26:** Split Admin Identity Integration into development-owned
configuration and operator-owned integration work. The `openbao-admin` KeyCape
client is now code-defined in `sso-mfa/k8s/keycape/create-secrets.sh`, while
the UI action cards only ask the operator to apply live KeyCape config,
configure OpenBao with a protected token prompt, and verify MFA-backed login.
**2026-05-26:** Hardened the KeyCape OpenBao client deployment action after the
operator hit a non-executable `create-secrets.sh`. The action card now runs the
script through `bash`, uses absolute repo paths, and wraps the sequence in a
fail-fast heredoc so a failed config generation does not continue into a
KeyCape restart or verification.
**2026-05-26:** Removed the KeyCape OpenBao client action's dependency on
decrypted bootstrap secrets after the operator correctly hit the absent
`sso-mfa/bootstrap/secrets/` directory. Added a focused live Secret patcher and
verifier for the `openbao-admin` client so this non-secret client addition can
be applied without decrypting the full bootstrap secret bundle.
**2026-05-26:** Fixed the focused KeyCape OpenBao verifier after the live
KeyCape image lacked `wget`. The verifier now checks the live Secret and then
uses a short local `kubectl port-forward` plus Python HTTP request for OIDC
discovery, avoiding assumptions about tools installed inside the KeyCape
container.
**2026-05-26:** Fixed the OpenBao OIDC auth setup after OpenBao rejected an
empty `oidc_client_secret` even though the current KeyCape `openbao-admin`
client is public PKCE. The UI now points to a short helper script instead of a
long nested shell/JSON command, and the helper writes an explicit non-secret
compatibility value until KeyCape supports confidential downstream clients.
**2026-05-24:** Stepped back from ad hoc secret rollout and added the
custodian age-key bootstrap model to the control surface. The UI now records
the custodian public age recipient, a derived fingerprint, and a non-secret
private-key custody reference while refusing to treat the private key as normal
metadata. It also detects encrypted bootstrap bundle presence and plaintext
`sso-mfa/bootstrap/secrets/` exposure. This is the intended foundation for
trial-mode, custody-mode, unlock/apply, and later OpenBao handover flows.
**2026-05-26:** Closed this custody-approval task after review against the
live bootstrap metadata: `platform-root` is recorded as the king credential,
MFA and KeyCape OIDC login are verified, and `temporary-single-king` custody is
explicitly approved for the pre-production OpenBao bootstrap. Remaining
hardening and user-onboarding readiness work is tracked in `NET-WP-0017`.
### T04 - Complete Railiance OpenBao Bootstrap Ceremony
```task
id: NET-WP-0015-T04
status: done
priority: high
state_hub_task_id: "2102366e-064b-4071-8b6a-574d9d37d109"
```
Coordinate with `RAIL-PL-WP-0002-T03` to initialize and unseal OpenBao under
the king credential model, enable audit and the first mounts/policies, create a
non-root `platform-admin` access path, and revoke or offline-escrow the initial
root token.
**2026-05-26:** Closed the bootstrap ceremony portion after live verification:
Railiance OpenBao is initialized, unsealed, and post-unseal verified; initial
configuration was applied; the initial OpenBao root token is recorded as
revoked; trial unseal shares were rotated; and restore-drill confirmation is
recorded in the bootstrap metadata. Declarative audit/durable audit shipping
and routine OIDC admin access remain follow-up readiness gates under
`NET-WP-0017` and `RAIL-PL-WP-0002`.
### T05 - Provision First NetKingdom Admin Identity
```task
id: NET-WP-0015-T05
status: done
priority: high
state_hub_task_id: "d2a81d7b-9964-4bd5-9b8c-ef1324e02cd4"
```
Provision the first king/admin identity in the selected NetKingdom IAM
implementation. The target claims are `tenant=platform`,
`principal_type=human` or `break_glass`, MFA-backed assurance, and groups/roles
for `platform-root`, `platform-admin`, `netkingdom-admin`, and
`railiance-platform-admin`. `tegwick` may receive delegated day-to-day admin
roles later, but must be revocable without losing root custody.
**2026-05-26:** Closed for the bootstrap identity scope: the dedicated
`platform-root` user is recorded as created, assigned to
`net-kingdom-admins`, stored outside this repo, enrolled for MFA, and verified
through KeyCape OIDC. Richer IAM-profile claims for ordinary user onboarding
remain part of the user-onboarding readiness work in `NET-WP-0017`.
### T06 - Bind OpenBao Admin Auth To NetKingdom IAM
```task
id: NET-WP-0015-T06
status: done
priority: medium
state_hub_task_id: "ef97f3cb-9792-4b9d-bd2b-8871d368a50f"
```
Replace temporary operator tokens with NetKingdom IAM-backed OpenBao admin
auth when the issuer and claim mapping are ready. The OpenBao root token must
not be the normal admin path.
**2026-05-26:** The KeyCape `openbao-admin` client was code-defined, patched
into the live `keycape-config` Secret, rolled out, and verified without
requiring decrypted bootstrap secrets. At that point, OpenBao `auth/keycape`
still needed the fixed helper command and the MFA-backed
`bao login -method=oidc -path=keycape role=platform-admin` path still needed
verification.
**2026-06-01:** Added a guided bootstrap runbook action for the live
privacyIDEA state-loss case encountered during OpenBao OIDC login testing. The
new action recreates the `coulomb` realm, `lldap-coulomb` resolver,
self-enrollment policy, and phase-one passthrough policy by prompting for
`pi-admin` and LLDAP bind/admin passwords, writing them only to temporary
files through `repair-realm-live.sh`, and running `bootstrap-realm.sh` plus
`verify-t06.sh`. TOTP enrollment/re-enrollment and the final MFA-backed
OpenBao login verification remain operator steps.
**2026-06-01:** Closed after the `platform-root` MFA-backed OpenBao OIDC login
completed through KeyCape and the resulting token lookup showed
`platform-admin` in both token policy fields. The remaining OpenBao hardening,
audit, escrow, reset/rotation, and reopening gates continue under T07/T08 and
`NET-WP-0017`.
**2026-06-01:** Added OpenBao token revocation to the guided
Usecases & Runbooks section. The UI now includes a self-revoke card for the
current pod token-helper token and an accessor-based revocation card for
disclosed tokens, both keeping OpenBao token values off the local command line.
### T07 - Verify Recovery, Audit, And Rotation
```task
id: NET-WP-0015-T07
status: done
priority: medium
state_hub_task_id: "aa40cbb4-36d3-405d-b59d-0c21ae8c9539"
```
Confirm snapshot/restore drill, durable audit-log handling, root-token
disposition, unseal/recovery rotation expectations, and the follow-up owner
for adding at least one additional human escrow holder.
**2026-05-26:** Root-token disposition, unseal-key rotation, post-unseal
verification, and restore-drill confirmation are recorded. This task remains
open for declarative audit configuration/durable audit shipping, residual
taint-response closeout, and the next independent escrow holder.
**2026-06-01:** Closed for the bootstrap handoff scope. The bootstrap plan has
confirmed the available recovery/audit/rotation evidence and, more
importantly, now has explicit production-readiness follow-up gates:
`NET-WP-0017-T02` owns declarative/durable audit, restore evidence,
emergency seal/unseal drill evidence, and the next independent escrow holder;
`NET-WP-0017-T03` owns residual taint closeout. These items are no longer
tracked as unfinished bootstrap ceremony work.
### T08 - Reset, Rotate, And Reopen Under King Oversight
```task
id: NET-WP-0015-T08
status: done
priority: high
state_hub_task_id: "e6a60dca-547b-4493-a36c-f6b668d1bf52"
```
After the king credential accepts custody, reset or rotate bootstrap-era
database credentials, admin passwords, service tokens, OpenBao tokens, and
temporary access paths. Run host/workload checks and reopen the platform only
after the new custody state is verified.
**2026-06-01:** Closed as a bootstrap-plan handoff rather than as a claim that
all production cleanup is complete. `NET-WP-0017-T03` owns retirement of
bootstrap admin paths and residual taint response, `NET-WP-0017-T04` owns
bootstrap-era credential rotation/reset plus host/workload checks, and
`NET-WP-0017-T07` owns final review and retirement/archive of superseded
bootstrap workplans. `NET-WP-0018` will turn those gates into a smoother
bootstrap guide, control-surface automation, validations, and rebuild-risk
assessment.
## Closeout
**2026-06-01:** `NET-WP-0015` is finished. The first safe bridge is in place:
the dedicated `platform-root` identity exists outside day-to-day operator use,
custody mode is recorded, OpenBao was initialized and configured under the
bootstrap ceremony, the initial root token is not the normal admin path, and
routine OpenBao administration now works through NetKingdom/KeyCape OIDC with
MFA and the `platform-admin` policy. Remaining production-readiness work is
explicitly tracked in `NET-WP-0017`; rebuild automation and validation
improvements are tracked in `NET-WP-0018`.
## Acceptance Criteria
- The setup operator and king credential model are recorded without secret
values.
- The custody mode is explicit before OpenBao initialization.
- OpenBao root-token use is limited to bootstrap or break-glass handling.
- Routine admin access has a non-root path and a target NetKingdom IAM path.
- Production readiness has a clear gate for independent escrow, audit, restore,
reset/rotation, and reopening under king oversight.

View File

@@ -0,0 +1,202 @@
---
id: NET-WP-0016
type: workplan
title: "Guided Security Bootstrap Experience"
domain: netkingdom
repo: net-kingdom
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-05-24"
updated: "2026-05-24"
depends_on:
- NET-WP-0015
- NK-WP-0012
state_hub_workstream_id: "16069174-6698-4855-ad9e-5092c8571f38"
---
# NET-WP-0016 - Guided Security Bootstrap Experience
## Goal
Create the operator-facing bootstrap experience that makes NetKingdom and
OpenBao security setup understandable, repeatable, and safe for non-experts.
The platform should be possible to assemble with a low-trust setup operator,
then hand over to a dedicated king credential, reset and harden the bootstrap
state, and reopen under explicit custody.
## Context
Railiance and NetKingdom have reached a point where raw runbooks are not enough.
The infrastructure is still early and evolving, and the human operator does not
need to be an OpenBao/Keycloak/flex-auth expert to take the next safe step.
Good security here should feel like guided operations: visible trust stage,
clear blocked actions, plain-language explanations, and no accidental secret
exposure.
## Scope
In scope:
- define bootstrap use cases for king credential setup, user lifecycle,
OpenBao bootstrap, fabric setup, break-glass, and multi-custodian upgrade;
- design the first local operator console/checklist flow;
- define safety gates for live OpenBao initialization;
- define non-secret status records and audit/progress events;
- define where the UI reads status from NetKingdom, Railiance, and State Hub;
and
- implement a first minimal CLI or local UI if the design stabilizes.
Out of scope:
- storing or displaying secret values;
- implementing the full web UI before the workflow is validated;
- replacing OpenBao, key-cape, Keycloak, or flex-auth administrative UIs;
- unattended OpenBao initialization; and
- sending root material or recovery secrets by email.
## Tasks
### T01 - Define Bootstrap Use Cases
```task
id: NET-WP-0016-T01
status: done
priority: high
state_hub_task_id: "67af8a29-7ca1-4a9d-be3e-bdc48dd2d1fd"
```
Document the canonical bootstrap use cases and trust stages.
**2026-05-24:** Added `docs/security-bootstrap-use-cases.md` covering king
credential setup, onboarding, temporary lockout, permanent lockout/offboarding,
credential review/rotation, new fabric admin setup, OpenBao bootstrap, custody
handover, and later multi-custodian upgrade.
### T02 - Design The First Operator Journey
```task
id: NET-WP-0016-T02
status: done
priority: high
state_hub_task_id: "662e439b-5fba-4e17-bc62-0ace97ba8788"
```
Design the first command-driven or local-web operator journey: trust stage,
next safe action, blocked gates, preflight checks, custody packet template, and
clear plain-language instructions.
**2026-05-24:** Added `docs/security-bootstrap-operator-journey.md`. The first
journey uses a quiet `whynot-design` control surface: trust stage, one next
safe action, blocked gates, evidence rows, and a refusal boundary around live
OpenBao initialization.
### T03 - Define King Credential Kit Output
```task
id: NET-WP-0016-T03
status: done
priority: high
state_hub_task_id: "98aba75f-a7c1-4486-be7f-e8d1148d5303"
```
Define the non-secret artifacts the bootstrap experience can generate for the
king credential: checklist, custody packet template, OTP setup instructions,
password-safe guidance, and verification prompts.
**2026-05-24:** Added `docs/security-bootstrap-king-credential-kit.md`.
### T04 - Define User Lifecycle Flows
```task
id: NET-WP-0016-T04
status: done
priority: high
state_hub_task_id: "44766b45-21b8-45cd-8c0a-0ca8281ae8e9"
```
Define guided flows for onboarding, temporary lockout, permanent lockout,
offboarding, credential review, credential rotation, and delegated fabric admin
setup.
**2026-05-24:** Added `docs/security-bootstrap-user-lifecycle.md`.
### T05 - Define OpenBao Ceremony UX
```task
id: NET-WP-0016-T05
status: done
priority: high
state_hub_task_id: "53f55c99-8403-4b58-9ed4-b03e68c1ef3c"
```
Translate the Railiance OpenBao ceremony into a guided sequence that can show
status, block unsafe live init, guide offline custody, and record non-secret
completion evidence.
**2026-05-24:** Added `docs/security-bootstrap-openbao-ceremony-ux.md`.
### T06 - Prototype Local Bootstrap Console
```task
id: NET-WP-0016-T06
status: done
priority: medium
state_hub_task_id: "ef1c8ee4-250c-479a-b0fb-0b5cf4249bd9"
```
Implement the first minimal local operator console or CLI once the journey is
clear. It should read status, print checklists, run safe preflight commands,
and refuse live bootstrap when gates are missing.
**2026-05-24:** Added
`tools/security-bootstrap-console/security_bootstrap_console.py`, a read-only
local console with status, king-kit, custody-packet, handover-checklist,
metadata-template, and OpenBao preflight commands. Added Make targets for the
safe entry points. The console refuses live OpenBao init.
### T07 - Define Handover And Cleanup Gates
```task
id: NET-WP-0016-T07
status: done
priority: medium
state_hub_task_id: "46c7e3dc-e824-46ef-833d-9a83189735e0"
```
Define the post-king handover cleanup flow: reset databases, rotate tokens,
review admin accounts, run scan/check steps, verify backups, and mark the
platform reopened under king oversight.
**2026-05-24:** Added `docs/security-bootstrap-handover-cleanup.md`.
### T08 - Review Related Workplans On Closeout
```task
id: NET-WP-0016-T08
status: done
priority: medium
state_hub_task_id: "7665f6ac-6b0e-4a09-8a9b-9d2150310114"
```
When this workplan closes, review related NetKingdom and Railiance security
workplans to update stale bootstrap assumptions, retire superseded tasks, and
add follow-ups where the guided bootstrap experience becomes the canonical
operator path.
**2026-05-24:** Added
`docs/security-bootstrap-related-workplan-review.md`, kept `NK-WP-0004` and
`NK-WP-0005` as substrate workplans with closeout notes, left historical
`NK-WP-0001` archived, and updated stale Railiance OpenBao custody wording.
## Acceptance Criteria
- The setup operator can see the current trust stage and next safe action.
- Live OpenBao init remains blocked until king credential and custody gates are
satisfied.
- User lifecycle operations are described in plain, auditable flows.
- New fabrics can receive delegated admins without granting platform root.
- Secret values are never stored or displayed by the bootstrap experience.
- The path to two-of-three custody is explicit and low-friction.

View File

@@ -0,0 +1,390 @@
---
id: NK-WP-0003
type: workplan
title: "KeyCape + privacyIDEA Stack — Cluster Deployment"
domain: netkingdom
repo: net-kingdom
status: completed
owner: custodian
topic_slug: netkingdom
created: "2026-03-20"
updated: "2026-05-02"
state_hub_workstream_id: "f24cefd4-a09b-4fa1-9b25-94bf783b425e"
---
# KeyCape + privacyIDEA Stack — Cluster Deployment
## Goal
Deploy the full NetKingdom identity stack on the live k3s cluster without
Keycloak. KeyCape (v0.1, complete) is the OIDC orchestration layer; it
binds LLDAP (directory), Authelia (auth sessions), and privacyIDEA (MFA).
NK-WP-0001 was scoped around Keycloak and is deferred. This workplan
covers everything needed to reach a production-ready identity plane.
## Target cluster
**RAILIANCE01**`92.205.62.239` — k3s v1.35.1+k3s1, clean baseline.
Kubeconfig: `~/.kube/config-railiance01`
> Note: T02T07 were previously completed on CoulombCore (92.205.130.254) by
> mistake. CoulombCore is the old management host (Gitea/OCI registry only) and
> should not be touched. All SSO stack work targets RAILIANCE01 exclusively.
## Pre-conditions
- [x] k3s cluster healthy on RAILIANCE01 — v1.35.1+k3s1, node Ready ✓
- [x] kubeconfig available at `~/.kube/config-railiance01`
- [x] All manifests committed — net-kingdom `sso-mfa/k8s/`
- [x] KeyCape v0.1 complete — KEY-WP-0001 ✓
- [x] SOPS + age integrated into net-kingdom — NK-WP-0004 ✓
- [x] Agent-driven credential bootstrap ready — NK-WP-0005 ✓ (run `make creds-agent-init`)
## Architecture
```
Internet → Traefik (RAILIANCE01 k3s) → cert-manager TLS
├── auth.coulomb.social → Authelia
├── pink.coulomb.social → privacyIDEA portal
├── pink-account.coulomb.social → privacyIDEA account self-service
└── id.coulomb.social → KeyCape (OIDC)
KeyCape ──► Authelia (session, password)
──► LLDAP (directory, user lookup)
──► privacyIDEA (MFA challenges via trigger-admin token)
privacyIDEA ──► PostgreSQL (privacyidea_db via CloudNativePG)
LLDAP ──► SQLite (PVC)
Authelia ──► SQLite (PVC)
KeyCape image pulled from CoulombCore OCI registry: 92.205.130.254:32166
(insecure HTTP NodePort — requires registries.yaml on RAILIANCE01)
```
## Tasks
### T01 — Credential setup
```task
id: NK-WP-0003-T01
status: done
priority: high
state_hub_task_id: "6a22e17e-5854-4f8b-b419-9dc86d490357"
note: Credential foundation exists (NK-WP-0004 + NK-WP-0005). Secrets encrypted in
secrets.enc/. Before T02, run `make creds-agent-init` with KUBECONFIG pointing
to RAILIANCE01 to inject all secrets into the new cluster.
```
~~Net-kingdom currently uses a manual KeePassXC + age-bundle approach~~
Completed via NK-WP-0004 + NK-WP-0005. The credential foundation is in place:
- SOPS + age integrated — `~/.config/sops/age/keys.txt`, `.sops.yaml`, git hook
- Agent bootstrap: `make creds-agent-init` runs the full flow autonomously
- Credential standard: `canon/standards/credential-management_v0.2.md`
To bootstrap credentials into the RAILIANCE01 cluster before T02T09, run:
```bash
export KUBECONFIG=~/.kube/config-railiance01
make creds-agent-init
```
This generates all secrets, encrypts to `secrets.enc/`, injects into the
cluster, and delivers the emergency bundle. No KeePassXC steps required.
### T02 — Apply cluster foundations
```task
id: NK-WP-0003-T02
status: done
priority: high
state_hub_task_id: "a14e3a6b-18ee-4172-8a47-bd531f21e55a"
note: Done 2026-03-25 on RAILIANCE01. Namespaces, NetworkPolicies, cert-manager, ClusterIssuers,
insecure registry for CoulombCore OCI all applied and verified.
Known gotcha: added allow-traefik-to-acme-solver NetworkPolicy to sso + mfa namespaces
(default-deny-all blocked ACME HTTP-01 solver pods from receiving Traefik traffic).
```
Apply the K8s infrastructure foundations. All manifests already committed.
```bash
export KUBECONFIG=~/.kube/config-railiance01
kubectl apply -f sso-mfa/k8s/namespaces/
kubectl apply -f sso-mfa/k8s/network-policies/
kubectl apply -f sso-mfa/k8s/cert-manager/
```
Also configure the insecure OCI registry on RAILIANCE01 so k3s can pull the KeyCape image:
```bash
ssh tegwick@92.205.62.239 "sudo tee /etc/rancher/k3s/registries.yaml" <<'EOF'
mirrors:
"92.205.130.254:32166":
endpoint:
- "http://92.205.130.254:32166"
EOF
ssh tegwick@92.205.62.239 "sudo systemctl restart k3s"
```
Verify: `bash sso-mfa/k8s/verify-t02.sh`
Expected: namespaces `sso`, `mfa`, `databases` exist; NetworkPolicies applied;
cert-manager pods Running.
### T03 — Deploy PostgreSQL (CloudNativePG)
```task
id: NK-WP-0003-T03
status: done
priority: high
state_hub_task_id: "19e375d0-66bd-4cf0-9c2d-59d5c0d5989e"
note: Done 2026-03-25 on RAILIANCE01. CNPG operator + net-kingdom-pg cluster running,
privacyidea_db + role created. Verified via verify-t03.sh (8/8 PASS, 2 WARN for
superuser secret + scheduled backup — both expected at this stage).
```
Deploy the shared database cluster:
```bash
export KUBECONFIG=~/.kube/config-railiance01
kubectl apply -f sso-mfa/k8s/postgres/
```
Wait for cluster to be `Ready`, then verify: `bash sso-mfa/k8s/verify-t03.sh`
**Note**: Do not proceed to T04 until the CloudNativePG cluster is fully
healthy. Migration jobs will fail on a partially-started cluster.
### T04 — Deploy privacyIDEA
```task
id: NK-WP-0003-T04
status: done
priority: high
state_hub_task_id: "9c9c1ec9-0cf5-4546-a83e-d74dbf3b27af"
note: Done 2026-03-25 on RAILIANCE01. privacyIDEA pod Running, TLS certs issued,
enckey + audit keys bootstrapped (privacyidea-enckey + privacyidea-auditkeys Secrets created),
pi-admin + trigger-admin created, trigger-admin-rights policy created via REST API.
DEFERRED: pi-admin TOTP enrollment requires an admin realm (SQLresolver pointing to PI's
internal admin table) — pi-manage has no enroll command, WebUI token enrollment only works
for resolver-backed users. Admin MFA is production hardening; pi-admin auth works
password-only for now. Track as T09 hardening item.
```
Run credential bootstrap (injects privacyIDEA secrets + creates pi-admin/trigger-admin):
```bash
export KUBECONFIG=~/.kube/config-railiance01
make creds-agent-init
```
**Remaining manual step:**
Once `pink.coulomb.social` resolves to `92.205.62.239` and TLS cert is issued:
1. Log in to https://pink.coulomb.social as `pi-admin`
2. Enroll MFA for `pi-admin` (TOTP)
3. Verify/create trigger-admin policy: Policies → trigger-admin-rights
(Scope: admin, Action: triggerchallenge, AdminUser: trigger-admin)
### T05 — Deploy LLDAP
```task
id: NK-WP-0003-T05
status: done
priority: high
state_hub_task_id: "82fc90f7-8eb4-4718-b02a-dfd5fa39e5bc"
note: Done 2026-03-25 on RAILIANCE01. LLDAP pod Running, TLS cert issued (lldap.coulomb.social),
groups net-kingdom-users (id=4) + net-kingdom-admins (id=5) created via direct GraphQL.
bootstrap-users.sh has a bash set -e / json parse bug (workaround: direct curl).
```
Deploy LLDAP into the `sso` namespace:
```bash
export KUBECONFIG=~/.kube/config-railiance01
cd sso-mfa/k8s/lldap
bash create-secrets.sh
kubectl apply -f deployment.yaml
kubectl apply -f ingress.yaml
kubectl apply -f middleware.yaml
bash bootstrap-users.sh # creates base OU structure + initial admin user
```
Verify pod Running and LDAP bind works on `ldap.coulomb.social`.
### T06 — Deploy Authelia
```task
id: NK-WP-0003-T06
status: done
priority: high
state_hub_task_id: "3a28ff10-fbfa-443b-a64d-bbfe6153c544"
note: Done 2026-03-25 on RAILIANCE01. Authelia pod Running (1 restart on init, normal),
TLS cert issued (auth.coulomb.social), health endpoint returns {"status":"OK"}.
```
Deploy Authelia into the `sso` namespace:
```bash
export KUBECONFIG=~/.kube/config-railiance01
cd sso-mfa/k8s/authelia
bash create-secrets.sh
kubectl apply -f configmap.yaml
kubectl apply -f deployment.yaml
kubectl apply -f ingress.yaml
```
Verify: `bash sso-mfa/k8s/verify-t05.sh` (covers LLDAP + Authelia together)
### T07 — Deploy KeyCape
```task
id: NK-WP-0003-T07
status: done
priority: high
state_hub_task_id: "496a97c9-3e2a-486e-ba62-18449868c6cf"
note: Done 2026-03-25 on RAILIANCE01. KeyCape pod Running, TLS cert issued (kc.coulomb.social),
OIDC discovery endpoint live at https://kc.coulomb.social/.well-known/openid-configuration.
PI admin token refreshed via create-pi-token.sh (old token was from CoulombCore).
keycape-pi-token K8s Secret created in sso namespace.
```
Deploy KeyCape into the `sso` namespace:
```bash
export KUBECONFIG=~/.kube/config-railiance01
cd sso-mfa/k8s/keycape
bash create-secrets.sh # includes privacyIDEA trigger-admin token
bash create-pi-token.sh # registers KeyCape as a privacyIDEA application
kubectl apply -f deployment.yaml
kubectl apply -f ingress.yaml
kubectl apply -f middleware.yaml
```
Verify: OIDC discovery endpoint reachable at
`https://id.coulomb.social/.well-known/openid-configuration`
### T08 — End-to-end authentication test
```task
id: NK-WP-0003-T08
status: done
priority: high
state_hub_task_id: "0fba3392-c916-43fd-a2c1-24ce39481043"
note: Completed 2026-03-25. All 3 test packages pass (migration, negative, profile).
Go 1.22.10 found at ~/go/bin/go. DNS resolves to 92.205.62.239 (all 4 subdomains).
Tests run with: cd src && ~/go/bin/go test ./tests/... -v
Results: ok keycape/tests/migration, ok keycape/tests/negative, ok keycape/tests/profile
Note: tests use httptest.Server + mocks — no live cluster connection required.
Test user provisioned: testuser / test.user@coulomb.social
TOTP serial TOTP00007147, seed KVQLHEJCTKCI3K7G2UIF54QUE5BNLBAQ
Validated: auth PASS via privacyIDEA /validate/check.
pi-admin TOTP deferred to T09 hardening.
```
Prove the full auth flow works:
1. OIDC discovery resolves at `id.coulomb.social`
2. Authelia password auth succeeds for a test user
3. privacyIDEA TOTP challenge issued and accepted
4. KeyCape issues a valid access token
5. Token introspection returns expected claims (sub, groups, email)
Use the KeyCape acceptance test suite:
```bash
cd "$(git rev-parse --show-toplevel)/../key-cape"
go test ./tests/... -run TestProfileBaseline -v
```
### T08a — Create Cloudflare DNS A records
```task
id: NK-WP-0003-T08a
status: done
priority: high
state_hub_task_id: "c614f839-61c4-41f6-bfeb-b3f9525a7625"
note: Done — all 5 A records (kc, auth, pink, pink-account, lldap) resolve to 92.205.62.239
via @8.8.8.8. Confirmed 2026-03-25.
```
Create 5 A records in Cloudflare DNS, **proxy disabled (DNS-only / orange cloud OFF)**,
all pointing to `92.205.62.239` (RAILIANCE01 — where k3s/Traefik runs):
| Subdomain | Type | Value |
|-----------|------|-------|
| `kc.coulomb.social` | A | `92.205.62.239` |
| `auth.coulomb.social` | A | `92.205.62.239` |
| `pink.coulomb.social` | A | `92.205.62.239` |
| `pink-account.coulomb.social` | A | `92.205.62.239` |
| `lldap.coulomb.social` | A | `92.205.62.239` |
HTTP-01 ACME challenges require direct origin reachability — Cloudflare proxy blocks this.
Once DNS propagates, cert-manager's pending challenges will auto-resolve and TLS
certs will be issued for all ingresses.
Verify: `dig +short kc.coulomb.social @8.8.8.8``92.205.62.239`
### T08b — Install Go on RAILIANCE01
```task
id: NK-WP-0003-T08b
status: done
priority: high
state_hub_task_id: "fdfe595a-f5a8-466a-82e9-7cc2ad8e5c3e"
note: Go 1.22.10 already installed at ~/go/bin/go (workstation). Tests ran from workstation.
Also: Go v1.25.6 present on RAILIANCE01 via k3s.
```
Go is already installed on RAILIANCE01 via k3s (v1.25.6). No action needed.
Verify: `ssh tegwick@92.205.62.239 "go version"`
### T09 — Backup, DR, and monitoring
```task
id: NK-WP-0003-T09
status: done
priority: medium
state_hub_task_id: "a82751d8-4de8-4668-8568-8dc140a6322b"
note: Done 2026-05-02 consolidation. Backup CronJobs are live on RAILIANCE01 and
have recent successful runs for LLDAP, Authelia, and privacyIDEA. PVC backup
files exist for LLDAP and privacyIDEA enckey; Authelia job logs confirm
/data/backups/authelia.backup.2026-05-02. Break-glass and emergency bundle
state are confirmed in creds-state.yaml. DEFERRED to platform hardening:
CNPG object-store backup (requires MinIO/S3) and Prometheus scraping
(requires kube-prometheus-stack / monitoring CRDs).
```
Consolidation evidence (2026-05-02, RAILIANCE01):
- `lldap-backup`, `authelia-backup`, and `privacyidea-backup` CronJobs exist
and have recent successful runs.
- Latest job logs confirm:
- LLDAP: `/data/backups/users.backup.2026-05-02`
- Authelia: `/data/backups/authelia.backup.2026-05-02`
- privacyIDEA: `/data/backups/enckey.backup.2026-05-02`
- LLDAP PVC contains daily `users.backup.*` files through 2026-05-02.
- privacyIDEA PVC contains daily `enckey.backup.*` files through 2026-05-02.
- `creds-state.yaml` confirms:
- `ops_bundle_created: true`
- `emergency_bundle_delivered: true`
- `bootstrap_complete: true`
- DR runbook is present at `sso-mfa/k8s/backup/DR-RUNBOOK.md`.
- NetworkPolicies include default-deny and backup API egress allowance.
Deferred platform-hardening items:
- CNPG PostgreSQL object-store backup: CNPG is healthy, but no
`ScheduledBackup` resource is installed on RAILIANCE01. This requires a
MinIO/S3 target and should be tracked with the platform backup work rather
than blocking this SSO/MFA deployment workplan.
- Prometheus scraping: monitoring CRDs are not installed on RAILIANCE01
(`servicemonitor` resource type is absent). This requires a
kube-prometheus-stack deployment and should be tracked with cluster
observability work.
## Done criteria
- [x] Credentials: `bootstrap_complete: true` in `creds-state.yaml` (NK-WP-0005)
- [x] verify-t08.sh: PASS=15, FAIL=0 (WARNs are manual offsite confirmation only)
- [x] KeyCape acceptance test suite passes
- [x] DB restore drill completed (LLDAP SQLite — 2 users, all tables verified)
- [x] Emergency bundle delivered and stored in personal password manager (`creds-state.yaml`)
- [x] Ops bundle created and location recorded (`creds-state.yaml`)
- [x] privacyIDEA enckey backed up on PVC (/etc/privacyidea/backups/enckey.backup.*)
- [x] Monitoring/CNPG object-store backups explicitly deferred to platform hardening

View File

@@ -0,0 +1,393 @@
---
id: NK-WP-0004
type: workplan
title: "Credential Management Foundation"
domain: netkingdom
repo: net-kingdom
status: done
owner: custodian
topic_slug: netkingdom
created: "2026-03-20"
updated: "2026-05-24"
state_hub_workstream_id: "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:
1. Run `make creds-init` to set up the full SOPS + age + KeePassXC workflow
2. Run `make creds-generate` to produce all service secrets and be guided on
KeePassXC entry
3. Run `make creds-apply` to inject secrets into the cluster in the correct order
4. Run `make creds-status` to see what is generated, applied, and verified
5. Invoke `/creds-bootstrap` in 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.sh` and `pack-bundle.sh` exist but are run manually, in
isolation, with no orchestration
- The five `create-secrets.sh` scripts 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.
## NET-WP-0016 Closeout Review
`NET-WP-0016` keeps this workplan as the low-level bootstrap credential
foundation. SOPS/age, encrypted bundles, generated secrets, and Kubernetes
Secret injection remain useful substrate tooling.
The operator-facing path is no longer the old `/creds-bootstrap` experience by
itself. The canonical guided path is the security bootstrap console and related
docs from `NET-WP-0016`, with the dedicated king credential model from
`NET-WP-0015`. KeePassXC remains optional personal/offline storage; it is not
the platform root of trust.
## 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
```task
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:**
1. Verify the operator age key exists:
```bash
ls ~/.config/sops/age/key.txt || age-keygen -o ~/.config/sops/age/key.txt
```
The public key (`age1aq8twfd78wvpra0had8cezcnj96tj4q0068edrz5jez8d6xwmflqdepsh4`
for the primary operator) is already in railiance-infra. Reuse the same
keypair — one age key per operator across all repos.
2. Create `keys/age.pub` at the repo root:
```
age1aq8twfd78wvpra0had8cezcnj96tj4q0068edrz5jez8d6xwmflqdepsh4
```
3. Create `.sops.yaml` at the repo root:
```yaml
creation_rules:
- path_regex: secrets/.*$
key_groups:
- age:
- age1aq8twfd78wvpra0had8cezcnj96tj4q0068edrz5jez8d6xwmflqdepsh4
```
4. Add `secrets/` to `.gitignore` (plaintext secrets MUST NOT enter git).
SOPS-encrypted files (`.sops.yaml` extension) may be committed.
5. Create `.githooks/pre-commit` mirroring railiance-infra:
- Blocks any commit that includes a file under `secrets/` lacking
`sops:` or `"sops":` marker (i.e. plaintext)
- Also blocks any file named `*.env` outside of `sso-mfa/bootstrap/`
being committed
6. `make hooks` target to enable the hook:
```makefile
hooks:
git config core.hooksPath .githooks
```
### T02 — Makefile: SOPS targets
```task
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:**
```makefile
## 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
```task
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:
1. Checks `KUBECONFIG` is set and cluster is reachable
2. Checks each `secrets/<component>/secrets.env` exists before sourcing it
3. Runs scripts in order: postgres → lldap → authelia → privacyidea
4. Explicitly skips keycape (requires PI_ADMIN_TOKEN from post-T04 bootstrap)
5. Prints the keycape step as a manual reminder with the exact command
6. On success, updates `sso-mfa/bootstrap/creds-state.yaml`
### T04 — Credential state file
```task
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:
```yaml
# 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
```task
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 `*.env` outside of `sso-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.age` being 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`
```task
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:**
1. Read `creds-state.yaml` to determine current state
2. Identify the next required step (first `false` in dependency order)
3. For KeePassXC entry steps: display the exact group path and field names
to enter, with values sourced from `secrets/` env files (if present)
4. For time-sensitive steps (enckey-bootstrap): print a prominent warning
with the exact command and timing constraint
5. For verification steps: run `make creds-verify` and interpret results
6. After each confirmed step: prompt operator to update `creds-state.yaml`
or do it automatically when the state can be derived from cluster state
**Skill definition file structure:**
```yaml
---
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
```task
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:
1. Validates the secret name is known
2. Prints the rotation impact and required coordination steps
3. Generates a new value (same entropy as original)
4. Guides through the atomic update sequence for that secret
5. Updates `creds-state.yaml` and ops bundle after rotation
## Done criteria
- [ ] `make creds-init` runs cleanly on a fresh workstation (age key check + setup)
- [ ] `make creds-generate` produces all secrets and prints KeePassXC entry guide
- [ ] `make creds-bundle` produces an age-encrypted ops bundle
- [ ] `make creds-apply` runs all `create-secrets.sh` scripts in dependency order
- [ ] `make creds-verify` accurately reflects K8s secret state
- [ ] `make creds-status` shows a readable state table from `creds-state.yaml`
- [ ] `make hooks-test` confirms pre-commit hook blocks plaintext commits
- [ ] `/creds-bootstrap` skill loads, reads state, and provides correct next step
- [ ] NK-WP-0003-T01 can be marked done by referencing this workplan as complete

View File

@@ -0,0 +1,449 @@
---
id: NK-WP-0005
type: workplan
title: "Agent-Driven Credential Bootstrap — Zero Human Ops"
domain: netkingdom
repo: net-kingdom
status: done
owner: custodian
topic_slug: netkingdom
created: "2026-03-21"
updated: "2026-05-24"
depends_on: NK-WP-0004
state_hub_workstream_id: "75bc472b-cc0a-48f2-afb6-62b896f7cc19"
---
# Agent-Driven Credential Bootstrap — Zero Human Ops
## Problem
NK-WP-0004 built the right tooling but the wrong operator model. It was
designed around a human-as-operator workflow:
- Human runs `gen-secrets.sh`
- Human manually types every secret into KeePassXC
- Human confirms via `keepass_confirmed: true`
- Human runs `creds-apply`
This is the wrong interface. You delegated the security setup. Being told
"go open KeePassXC and type in 23 fields" is not delegation — it is
manual labour with extra ceremony.
## Goal
The agent owns the full credential lifecycle end-to-end. The only human
touchpoint is receiving the **emergency credential bundle** — a minimal
set of master keys for break-glass recovery — and storing it once in a
personal password store.
```
Agent Human
│ │
├── generate all secrets │
├── encrypt via SOPS/age → commit │
├── inject into cluster (kubectl) │
├── verify all K8s secrets live │
├── create age-encrypted ops bundle │
├── assemble emergency bundle ─────────────►│ store in personal password manager
│ │ (one-time, nothing else ever)
└── mark state complete │
```
**What the human stores (and nothing more):**
| Item | Why needed |
|------|------------|
| age private key | Decrypt any SOPS-encrypted secret from git |
| break-glass passwords (3-4) | Direct service access if cluster/auth is down |
| ops bundle passphrase | Decrypt point-in-time secret snapshot |
Everything else — service secrets, rotation, re-injection — is agent work.
## NK-WP-0006 Runtime Secret Refinement
With OpenBao in the platform stack, the agent-driven bootstrap is the
handoff mechanism from bootstrap secrets to runtime secret authority.
The agent may generate, encrypt, inject, and verify initial secrets, but
OpenBao becomes the normal authority for platform and workload secret
delivery once the control plane is alive.
The bootstrap flow therefore has one additional boundary:
1. SOPS/age and the emergency bundle establish bootstrap and recovery
authority.
2. Kubernetes Secrets carry the minimum initial material needed to start
the identity, MFA, database, and OpenBao platform services.
3. OpenBao is initialized, unsealed or auto-unsealed by the approved
mechanism, audit logging is enabled, backups are verified, and
workload auth methods are configured.
4. Runtime workloads receive scoped secrets, dynamic credentials, or
synchronized Kubernetes Secrets from OpenBao. They do not consume
platform-root bootstrap material.
OpenBao root tokens, unseal keys, or recovery keys are break-glass
material. They must not be stored as ordinary tenant secrets or exposed
to tenant administrators. If they are included in an emergency bundle,
that bundle is platform-control-plane break-glass material and requires
the strongest storage and review procedure available for the deployment.
## NET-WP-0016 Closeout Review
This workplan remains useful as automation substrate, but its "zero human ops"
framing is superseded at the product and custody layer by `NET-WP-0015` and
`NET-WP-0016`.
Agents may still generate, encrypt, inject, verify, and rotate bootstrap
material. They must not silently assume king credential custody, run live
OpenBao initialization unattended, or treat emergency bundles as ordinary
operator conveniences. The guided bootstrap experience is the canonical
operator path for king credential setup, OpenBao ceremony readiness, handover
cleanup, and reopening under custody.
## Design
### What changes from NK-WP-0004
| NK-WP-0004 | NK-WP-0005 |
|-----------|-----------|
| Human runs `make creds-generate` | Agent runs bootstrap automatically |
| Human enters secrets in KeePassXC | No KeePassXC in the operational path |
| `keepass_confirmed: false` gate | `emergency_bundle_delivered: false` gate |
| `/creds-bootstrap` skill = guided walkthrough | `/creds-init` skill = autonomous execution |
| Ops bundle created manually | Ops bundle created automatically |
| Rotation triggered manually | Rotation can be triggered by agent |
### KeePassXC role
KeePassXC is **removed from the primary workflow**. It becomes optional
personal infrastructure — if you choose to import the emergency bundle
into KeePassXC that is your business, but it is not required or assumed.
The age private key and SOPS-encrypted git files ARE the credential store.
The ops bundle IS the backup. The emergency bundle IS the human's key ring.
### What the agent cannot automate (genuine human gates)
1. **Confirming receipt of the emergency bundle** — the agent must not
mark the bootstrap complete until the human confirms they have stored
the bundle. This is a deliberate pause, not a workaround.
2. **The privacyIDEA enckey bootstrap** — must happen while the pod is
live (time-sensitive window). The agent can detect when the pod is
ready and run the step automatically, but must verify the human is
present or at least that the operation succeeded.
3. **Initial age keypair generation** — if no age key exists yet, the
agent generates it and the private key is the first thing that goes
into the emergency bundle.
---
## Tasks
### T01 — Redesign creds-state.yaml for agent mode
```task
id: NK-WP-0005-T01
status: done
priority: high
state_hub_task_id: "6748cf8d-a7c7-47a2-b32a-2e26e05c4cba"
```
Replace the human-confirmation model with an agent-progress model.
**New schema** (`sso-mfa/bootstrap/creds-state.yaml`):
```yaml
# Credential state — net-kingdom SSO/MFA stack
# Safe to commit. Contains no secrets. Updated by agent.
schema_version: 2
agent_mode: true # NK-WP-0005: fully automated
# Phase tracking
age_key_present: false # ~/.config/sops/age/key.txt exists
secrets_generated: false # gen-secrets.sh ran successfully
ops_bundle_created: false # age-encrypted bundle created
ops_bundle_location: null # path or storage hint
# Emergency bundle
emergency_bundle_delivered: false # human confirmed receipt
emergency_bundle_delivered_at: null
# Cluster injection (per-component)
secrets_applied:
postgres: false
lldap: false
authelia: false
privacyidea: false
keycape: false
# Post-apply bootstrap (agent-run when pod is Ready)
enckey_bootstrapped: false
pi_admin_created: false
# Derived: all true → bootstrap complete
bootstrap_complete: false
```
Remove `keepass_confirmed` and `generated_at` (now in git history).
Add `schema_version: 2` so scripts can detect which model they are running.
---
### T02 — Agent bootstrap script: `creds-bootstrap-agent.sh`
```task
id: NK-WP-0005-T02
status: done
priority: high
state_hub_task_id: "22940c39-8645-40e1-b947-17e85ea6d902"
```
Create `sso-mfa/bootstrap/creds-bootstrap-agent.sh` — the single
entrypoint for fully automated credential bootstrap.
**Flow:**
```
1. pre-flight
├── check kubectl / KUBECONFIG reachable
├── check SOPS age key (generate if missing → add to emergency bundle)
└── check cluster is healthy (kubectl get nodes)
2. generate
└── run gen-secrets.sh ./secrets
3. encrypt + commit
├── sops --encrypt each secrets env file → sso-mfa/encrypted/
└── git add + git commit "chore(creds): encrypted secrets [agent]"
4. inject
└── run creds-apply.sh (existing — postgres → lldap → authelia → privacyidea)
5. verify
└── run creds-verify.sh — exit if any K8s secret missing
6. post-apply bootstrap (waits for pods)
├── wait for privacyIDEA pod Ready (max 5 min)
├── run enckey-bootstrap.sh
├── run bootstrap-admin.sh → capture PI_ADMIN_TOKEN
├── inject keycape secrets (now PI_ADMIN_TOKEN is known)
└── run creds-verify.sh again — all components now
7. ops bundle
└── run pack-bundle.sh ./secrets <age-pub-key>
8. emergency bundle
├── assemble emergency-bundle.txt (see T03)
├── display to terminal with clear formatting
└── prompt: "Have you stored this? [y/N]"
9. cleanup
├── shred secrets/ plaintext
└── write creds-state.yaml: bootstrap_complete: true
10. update state
└── state-hub: mark NK-WP-0005 tasks done via API
```
**Error handling:** Each phase updates `creds-state.yaml` so a restart
resumes from where it left off (idempotent re-runs skip completed phases).
---
### T03 — Emergency bundle format and delivery
```task
id: NK-WP-0005-T03
status: done
priority: high
state_hub_task_id: "42ce1486-5322-4cf2-9c71-1c1c61db5f46"
```
Create `sso-mfa/bootstrap/emergency-bundle.sh` that assembles and
displays the emergency credential bundle.
**Bundle contents:**
```
╔══════════════════════════════════════════════════════════════════╗
║ NET-KINGDOM EMERGENCY CREDENTIAL BUNDLE ║
║ Generated: <ISO-date> Store this. Nothing else. ║
╠══════════════════════════════════════════════════════════════════╣
║ AGE PRIVATE KEY (decrypt all SOPS secrets from git) ║
║ ────────────────────────────────────────────────────────────── ║
║ AGE-SECRET-KEY-1... ║
╠══════════════════════════════════════════════════════════════════╣
║ BREAK-GLASS PASSWORDS (direct service access, cluster-bypass) ║
║ ────────────────────────────────────────────────────────────── ║
║ privacyIDEA admin : <pi-admin password> ║
║ LLDAP admin : <lldap admin password> ║
║ PostgreSQL root : <postgres root password> ║
║ break-glass user : <lldap break-glass password> ║
╠══════════════════════════════════════════════════════════════════╣
║ OPS BUNDLE (age-encrypted point-in-time secret snapshot) ║
║ ────────────────────────────────────────────────────────────── ║
║ Location : <path/url to ops bundle> ║
║ Decrypt : age -d -i <age-key-path> ops-bundle-<date>.tar.age ║
╠══════════════════════════════════════════════════════════════════╣
║ RECOVERY INSTRUCTIONS ║
║ ────────────────────────────────────────────────────────────── ║
║ 1. Restore age key to ~/.config/sops/age/key.txt ║
║ 2. Clone net-kingdom repo + run: make creds-apply ║
║ 3. Use break-glass passwords for direct service access if needed ║
╚══════════════════════════════════════════════════════════════════╝
```
**Delivery:**
1. Print to terminal (operator copies manually into personal password
manager — 1Password, Bitwarden, KeePassXC, paper — agent does not
care which)
2. Optionally write to `~/emergency-bundle-<date>.txt` for 60 seconds,
then shred automatically
**Confirmation gate:** After display, prompt:
```
Store the above in your personal password manager now.
Press Enter when done (this will clear the screen and shred any temp file):
```
Only after Enter does the script continue and mark
`emergency_bundle_delivered: true`.
---
### T04 — `/creds-init` Claude Code skill (autonomous)
```task
id: NK-WP-0005-T04
status: done
priority: medium
state_hub_task_id: "ca713ce7-6f2c-4f0c-8b6c-88fc6e559190"
```
Replace the guided `/creds-bootstrap` skill with a fully autonomous
`/creds-init` skill.
**Behaviour:**
1. Read `creds-state.yaml` to determine current phase
2. If `bootstrap_complete: true` → report status and exit
3. If `emergency_bundle_delivered: false` and some phases done → resume
from where the state file says
4. Otherwise → run `creds-bootstrap-agent.sh` end-to-end
5. On completion → log progress event to state-hub
**Skill definition:**
```yaml
---
description: >
Fully automated net-kingdom credential bootstrap. Generates all service
secrets, encrypts and commits via SOPS, injects into cluster, and delivers
a minimal emergency bundle for your personal password manager. No manual
steps required.
argument-hint: "[--dry-run] [--resume]"
allowed-tools:
- Bash(make creds-*)
- Bash(bash sso-mfa/bootstrap/creds-bootstrap-agent.sh*)
- Bash(kubectl get*)
- Bash(git status*)
- Read
---
```
The old `/creds-bootstrap` skill can be archived or updated to delegate
to `/creds-init`.
---
### T05 — Makefile: `creds-agent-init` target
```task
id: NK-WP-0005-T05
status: done
priority: medium
state_hub_task_id: "ac5d887e-c499-4cf6-91e7-90e2e0e78d4a"
```
Add to the existing Makefile:
```makefile
## Fully automated credential bootstrap (NK-WP-0005)
## Generates, encrypts, injects, and delivers emergency bundle.
## Resumes automatically if interrupted.
creds-agent-init:
@bash sso-mfa/bootstrap/creds-bootstrap-agent.sh
## Show current bootstrap state
creds-agent-status:
@bash sso-mha/bootstrap/creds-status.sh --v2
## Re-deliver emergency bundle (if lost/stolen — generates new bundle, rotates nothing)
creds-emergency-reprint:
@bash sso-mfa/bootstrap/emergency-bundle.sh --reprint
```
---
### T06 — Agent-driven rotation
```task
id: NK-WP-0005-T06
status: done
priority: low
state_hub_task_id: "2f0782f7-db5d-4b8a-920b-582548c4591f"
```
Extend `creds-rotate.sh` to run non-interactively when called from the
agent (currently it is interactive/guided).
Add `--secret <name> --non-interactive` flags:
- Generates new value
- Applies the atomic update sequence for that secret type
- Re-encrypts SOPS file
- Commits
- Verifies
- Updates `creds-state.yaml` with `last_rotated_<secret>: <ISO-date>`
- Does NOT require human confirmation (agent is the operator)
The emergency bundle is **not** reprinted on routine rotation — only on
full re-bootstrap or explicit `creds-emergency-reprint`.
Exception: if the age private key is rotated, a new emergency bundle
MUST be delivered before the old one is revoked.
---
### T07 — Update credential management standard
```task
id: NK-WP-0005-T07
status: done
priority: low
state_hub_task_id: "42ac193d-7b56-48f7-8eba-757a6dad2fba"
```
Update `canon/standards/credential-management_v0.1.md` to v0.2,
reflecting the agent-driven model:
- Section 2 (Trust Hierarchy): remove KeePassXC from operational path;
add it as optional personal store for the emergency bundle
- Section 3 Phase 0: replace manual steps with `make creds-agent-init`
- New Section: Emergency Bundle — what it contains, how it is delivered,
when to use it
- Remove Section 4 (KeePassXC group structure) or demote to appendix
(still useful if user chooses KeePassXC for their personal store)
- Update Section 6 (Ops Bundle): bundle creation is now automated
- Update Section 7 (Prohibited Patterns): add "agent MUST NOT skip the
`emergency_bundle_delivered` gate, even in non-interactive runs"
---
## Done Criteria
- [ ] `make creds-agent-init` runs from scratch on a clean workstation
without any human input until the emergency bundle prompt
- [ ] Emergency bundle is displayed clearly; confirmation gate works
- [ ] All K8s secrets verified live after bootstrap
- [ ] `creds-state.yaml` shows `bootstrap_complete: true` after run
- [ ] `/creds-init` skill in Claude Code runs the full flow autonomously
- [ ] Rotation works non-interactively via `--non-interactive` flag
- [ ] Credential management standard updated to v0.2
- [ ] NK-WP-0003 unblocked (T01 was already unblocked by NK-WP-0004;
this workplan makes T01 actually safe to run without human ops)