Compare commits

...

22 Commits

Author SHA1 Message Date
custodian-sync
d34c6891b3 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-08:
  - update .custodian-brief.md for net-kingdom
2026-07-08 12:33:01 +02:00
db12130c22 Postpone NK-WP-0009 and NK-WP-0011 to backlog
Move security pattern tutorials and enterprise federation SAML workplans
from proposed to backlog status.
2026-07-08 12:31:39 +02:00
custodian-sync
1b5fab9bcd chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-07:
  - update .custodian-brief.md for net-kingdom
2026-07-07 16:48:58 +00:00
custodian-sync
02cb63c8ab chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-07:
  - update .custodian-brief.md for net-kingdom
2026-07-07 16:34:53 +00:00
9767f7230f Draft capability entry (reuse-surface REUSE-WP-0017-T04, cohort 2)
Honest first-pass maturity vector grounded in README/docs/tests present
in this repo; no invented evidence. Flagged for human review before
publish. See reuse-surface history/2026-07-06-coverage-classification.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 19:41:37 +02:00
d2c7473fb2 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-02:
  - update .custodian-brief.md for net-kingdom
2026-07-02 22:09:52 +02:00
85a781b7a4 NET-WP-0020 finished: attended-ceremony + auto-unseal-transit profiles, greenfield init/unseal proof
T2: greenfield live proof against a fresh uninitialized OpenBao 2.5.5 —
caught and fixed 'bao operator unseal -' not reading stdin (now
'bao write sys/unseal key=-'); init and reseal-replay paths proven.
T3: attended-ceremony selectable — runbook, non-secret ceremony-record
template + validator, and a lab/production deployment profile that blocks
sops-held-automation in console selection, gates, and the init script.
T4: console gate + evidence flags for auto-unseal-transit (Helm seal stanza
prepared in railiance-platform).
Also: SCOPE.md refreshed to current repo state; adhoc fix for the broken
check-secrets Make target (unescaped $).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 22:08:33 +02:00
60b9d7037d chore(consistency): commit ADHOC-2026-07-02 task-id writeback 2026-07-02 19:13:29 +02:00
951ba07c30 adhoc: creds-bootstrap-agent dry-run no longer dies without age key
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:08:33 +02:00
67b4677cea NET-WP-0020-T02: wire OpenBao init/unseal as Phase 7b in creds-bootstrap-agent (operator-reviewed)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 13:32:58 +02:00
abde6b1fd4 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-02:
  - update .custodian-brief.md for net-kingdom
2026-07-02 11:02:40 +02:00
60142241a3 NET-WP-0020-T02: SOPS-held OpenBao init/unseal automation helper
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 11:01:34 +02:00
764c3cfd6d Regenerate agent instructions: workstream -> workplan terminology
Registration guidance now prescribes file-first + fix-consistency (C-06)
instead of manual create_workplan/create_workstream calls; progress-event
examples use workplan_id; legacy field names annotated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 01:47:46 +02:00
a770a6a11d Archive closed workplans to workplans/archived/ (ADR-001)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 00:25:42 +02:00
9a7d10f840 Repo hygiene: fill stack-and-commands, normalize workplan statuses
- Fill .claude/rules/stack-and-commands.md (was an empty TODO template)
- Normalize workplan frontmatter statuses to canonical vocabulary
  (completed/done -> finished) per ADR-001
- Repair glued frontmatter delimiter in NK-WP-0001 (superseded_by line)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 00:21:49 +02:00
93e465525f Normalize agent instructions and workplan frontmatter (STATE-WP-0067)
- Align agent files with on-disk workplan prefixes (infer from workplan ids)
- Set workplan domain to registered domain_slug; add topic_slug where applicable
- Repair frontmatter delimiter formatting; migrate legacy task status literals
- Regenerate AGENTS.md, CLAUDE.md, and .claude/rules from State Hub templates
2026-06-22 23:16:27 +02:00
30c647ff5b Updated and fixed workplan 2026-06-22 18:42:46 +02:00
53c015b97b Human-review .repo-classification.yaml (CUST-WP-0050 follow-up) 2026-06-22 17:56:17 +02:00
366574a7b1 Add .repo-classification.yaml (CUST-WP-0050 T11 agent first-pass) 2026-06-22 17:47:38 +02:00
3764ea03ed chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-06-22:
  - update .custodian-brief.md for net-kingdom
2026-06-22 12:40:25 +02:00
3875d546bc Expose OIDC auth mounts to unauthenticated OpenBao UI listing
Set listing_visibility=unauth on netkingdom and keycape during OIDC configure
so the browser login mask can select KeyCape instead of falling back to token.
2026-06-19 21:04:31 +02:00
2056eee862 Add credential routing instructions for all agent runtimes
Propagate shared credential-routing section (Codex, Claude, Grok, llm-connect)
from state-hub template via scripts/propagate_credential_routing.py.
2026-06-18 22:48:38 +02:00
42 changed files with 1714 additions and 184 deletions

View File

@@ -0,0 +1,50 @@
# Credential and access routing
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
for inference. Run this check **before** requesting secrets, API keys, SSH access,
login tokens, or database passwords — in any repo, not only `ops-warden`.
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
other credential need belongs to another subsystem. **Do not** message
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
### Lookup (do this first)
```bash
warden route find "<describe your need>" --json
warden route show <catalog-id> --json
```
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
| Agent runtime | How to orient |
| --- | --- |
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=net-kingdom` is for coordination, not secret vending |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workplans; **still** use `warden route` for credential ownership |
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
### Quick routing table
| I need… | Owner | ops-warden executes? |
| --- | --- | --- |
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes**`warden sign` |
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
| Authorization decision | flex-auth | No — route only |
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
### Anti-patterns (do not do these)
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
- Pasting secrets into Git, State Hub, workplans, logs, or chat
### Other capabilities (reuse-surface)
Non-credential capabilities are usually discovered through **reuse-surface** federation
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
every repo's agent instructions because it is high-frequency, high-risk, and easy to
get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`

View File

@@ -1,37 +1,41 @@
## First Session Protocol
Triggered when `get_domain_summary("netkingdom")` shows **no workstreams**.
Triggered when `get_domain_summary("infotech")` shows **no workplans**.
The project is registered but work has not yet been structured.
**Step 1 — Read, don't write**
- `~/the-custodian/canon/projects/netkingdom/project_charter_v0.1.md` — purpose, scope
- `~/the-custodian/canon/projects/netkingdom/roadmap_v0.1.md` — planned phases
- `~/the-custodian/canon/projects/infotech/project_charter_v0.1.md` — purpose, scope
- `~/the-custodian/canon/projects/infotech/roadmap_v0.1.md` — planned phases
- Scan repo root: README, directory structure, existing code or docs
**Step 2 — Survey in-progress work**
Look for TODOs, open branches, half-finished files. Note done vs. started but incomplete.
**Step 3 — Propose workstreams to Bernd**
Propose 13 workstreams — each a coherent strand, weeks to months, anchored to a
**Step 3 — Propose workplans to Bernd**
Propose 13 workplans — each a coherent strand, weeks to months, anchored to a
roadmap phase. **Wait for approval before creating.**
**Step 4 — Create workplan file first, then DB record (ADR-001)**
**Step 4 — Write the workplan file; fix-consistency registers it (ADR-001)**
```
workplans/net-kingdom-WP-NNNN-<slug>.md ← write this first
workplans/NK-WP-NNNN-<slug>.md ← write this, commit it
```
Then register in the hub:
```
create_workstream(topic_id="a6c6e745-bf54-4465-9340-1534a2be493e", title="...", owner="...", description="...")
create_task(workstream_id="<id>", title="...", priority="high|medium|low")
Then register by running the consistency check — do **not** call
`create_workplan`/`create_task` (or legacy `create_workstream`) yourself;
manual registration duplicates what C-06 creates from the file:
```bash
statehub fix-consistency --repo net-kingdom
```
C-06 creates the hub workplan + tasks and writes `state_hub_workstream_id` /
`state_hub_task_id` back into the file (legacy field names, kept for
compatibility — they hold workplan/task IDs).
**Step 5 — Record the setup**
```
add_progress_event(
summary="First session: structured netkingdom into N workstreams, M tasks",
summary="First session: structured infotech into N workplans, M tasks",
event_type="milestone",
topic_id="a6c6e745-bf54-4465-9340-1534a2be493e",
detail={"workstreams": [...], "tasks_created": M}
topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a",
detail={"workplans": [...], "tasks_created": M}
)
```

View File

@@ -1,5 +1,5 @@
**Purpose:** NetKingdom infrastructure and IAM platform — orchestrates SSO/MFA, multi-user onboarding, and the Keycape/Keycloak IAM profile across the NetKingdom environment.
**Domain:** netkingdom
**Domain:** infotech
**Repo slug:** net-kingdom
**Topic ID:** a6c6e745-bf54-4465-9340-1534a2be493e
**Topic ID:** cee7bedf-2b48-46ef-8601-006474f2ad7a

View File

@@ -1,6 +1,7 @@
## Session Protocol
State Hub: http://127.0.0.1:8000
Dev Hub (State Hub API): http://127.0.0.1:8000
MCP server name in `~/.claude.json`: `dev-hub`
**Step 1 — Orient**
@@ -10,7 +11,7 @@ cat .custodian-brief.md
```
Then call the MCP tool for richer cross-domain context when MCP tools are exposed:
```
get_domain_summary("netkingdom")
get_domain_summary("infotech")
```
If MCP tools are unavailable in the current agent session, use the REST API:
```bash
@@ -39,11 +40,11 @@ curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
ls workplans/
```
For each file with `status: ready`, `active`, or `blocked`, note pending
`todo`/`in_progress` tasks.
`wait`/`todo`/`progress` tasks.
**Step 4 — Present brief**
1. **Active workstreams** for `netkingdom` — title, task counts, blocking decisions
1. **Active workplans** for `infotech` — title, task counts, blocking decisions
2. **Pending tasks** from `workplans/` + any `[repo:net-kingdom]` hub tasks
3. **Goal guidance** — if `goal_guidance` in summary:
- `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"*
@@ -51,33 +52,42 @@ For each file with `status: ready`, `active`, or `blocked`, note pending
4. **Suggested next action** — highest-priority open item
5. **SBOM status** — flag if `last_sbom_at` is unset for this repo
If no workstreams: follow First Session Protocol (`first-session.md`).
If no workplans: follow First Session Protocol (`first-session.md`).
**During work:** `record_decision()` · `add_progress_event()` · `resolve_decision()`
> State Hub is a *read model*. Bootstrap tools (`create_workstream`, `create_task`)
> are First Session Protocol only. Work structure belongs in repo files (ADR-001).
> State Hub is a *read model*. **Never register workplans or tasks by hand**
> (`create_workplan`, `create_task`, or the legacy `create_workstream`) — write
> the workplan file in `workplans/` and run `fix-consistency`; its C-06 check
> registers the workplan and its tasks in the hub and writes the IDs back into
> the file. Manual registration creates duplicates the moment fix-consistency
> runs. Work structure belongs in repo files (ADR-001).
>
> Terminology: "workstream" is the legacy name for workplan. Some API/frontmatter
> field names keep it for compatibility (`state_hub_workstream_id`,
> `workstream_id` params) — treat them as workplan IDs.
**Session close:**
With MCP tools:
```
add_progress_event(summary="...", topic_id="a6c6e745-bf54-4465-9340-1534a2be493e", workstream_id="<uuid>")
add_progress_event(summary="...", topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a", workplan_id="<uuid>")
```
Without MCP tools:
```bash
curl -s -X POST http://127.0.0.1:8000/progress/ \
-H "Content-Type: application/json" \
-d '{"topic_id":"a6c6e745-bf54-4465-9340-1534a2be493e","workstream_id":"<uuid>","event_type":"note","summary":"what changed","author":"codex"}'
-d '{"topic_id":"cee7bedf-2b48-46ef-8601-006474f2ad7a","workplan_id":"<uuid>","event_type":"note","summary":"what changed","author":"codex"}'
```
If workplan files were modified, ensure the local copy is up to date first:
If workplan files were modified, ensure the local copy is up to date first,
then sync from the repo checkout:
```bash
git -C <repo_path> pull --ff-only
cd ~/state-hub && make fix-consistency REPO=net-kingdom
git pull --ff-only
statehub fix-consistency
```
For repos where implementation runs on a remote machine (e.g. CoulombCore),
use the combined target which pulls before fixing:
use the pull-before-fix mode from any shell with the State Hub CLI:
```bash
cd ~/state-hub && make fix-consistency-remote REPO=net-kingdom
statehub fix-consistency --repo net-kingdom --remote
```
**C-15** (DB task ahead of file) is normal in multi-machine workflows — writeback
will sync the file to match DB. **C-16** (repo behind remote) blocks all writes

View File

@@ -1,19 +1,22 @@
## Stack
<!-- TODO: Fill in language, frameworks, and key dependencies -->
- **Language:**
- **Key deps:**
- **Language:** Kubernetes manifests, Bash Make targets, SOPS-encrypted secret custody
- **Key deps:** Keycloak (SSO/MFA), age/SOPS, KeePassXC-based credential custody, repo-local git hooks
## Dev Commands
```bash
# TODO: Fill in the standard commands for this repo
# Install dependencies
# Run tests
# Lint / type check
# Build / package (if applicable)
make help # list all targets
make hooks && make hooks-test # secrets-guard git hooks
make check-secrets # fail if anything under secrets/ is unencrypted
make sops-edit FILE=secrets/foo.yaml # edit encrypted file
make sops-custody-check # validate custody age key without writing to disk
make sops-custody-run COMMAND='...' # run one command with temporary custody key
make creds-init # one-time credential custody setup
make creds-generate # generate service secrets + KeePassXC guide
make creds-bundle # age-encrypt ops bundle for offsite storage
```
Credential material never lands in Git, State Hub, or logs — the hooks and
check-secrets enforce this. Deployment of identity services runs through the
S2/S5 railiance repos, not from here.

View File

@@ -1,28 +1,45 @@
## Workplan Convention (ADR-001)
File location: `workplans/net-kingdom-WP-NNNN-<slug>.md`
ID prefix: `NET-WP`
File location: `workplans/NK-WP-NNNN-<slug>.md`
ID prefix: `NK-WP-`
Work items originate as files in this repo **before** being registered in the hub.
Canonical workplan/workstream frontmatter statuses are:
Canonical workplan frontmatter statuses are:
`proposed`, `ready`, `active`, `blocked`, `backlog`, `finished`, `archived`.
Use `proposed` for a newly drafted plan, `ready` after review against current
repo state, and `finished` when implementation is complete. `stalled` and
`needs_review` are derived health labels, not stored statuses.
Closed workplans may be moved to `workplans/archived/` with a completion-date
prefix: `YYMMDD-net-kingdom-WP-NNNN-<slug>.md`. The frontmatter id remains
prefix: `YYMMDD-NK-WP-NNNN-<slug>.md`. The frontmatter id remains
unchanged; the prefix is only for quick visual reference.
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
`workplans/ADHOC-YYYY-MM-DD.md`, workstream slug `adhoc-YYYY-MM-DD`, and task ids
`workplans/ADHOC-YYYY-MM-DD.md`, workplan slug `adhoc-YYYY-MM-DD`, and task ids
`ADHOC-YYYY-MM-DD-T01`, `T02`, etc. Use adhocs only for low-risk work completed
directly. Promote anything requiring analysis, design, approval, dependencies, or
multiple planned phases into a normal workplan.
Ecosystem todos from other agents arrive as `[repo:net-kingdom]` hub tasks —
visible at session start. Pick one up by creating the workplan file, then registering
the workstream.
visible at session start. Pick one up by creating the workplan file, committing,
and running `statehub fix-consistency` — C-06 registers the workplan in the hub.
Never register by hand with `create_workplan`/`create_workstream`.
Task blocks use this shape:
```task
id: NK-WP-NNNN-T01
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
```
Status progression is `todo``progress``done`; use `wait` for waiting or
blocked work and `cancel` for stopped work.
Workplan frontmatter carries `state_hub_workstream_id` — a legacy field name
kept for compatibility ("workstream" is the old term for workplan); it holds
the hub workplan id and is written by fix-consistency. Do not edit or rename it.
<!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here -->

View File

@@ -1,18 +1,23 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — net-kingdom
**Domain:** netkingdom
**Last synced:** 2026-06-05 07:48 UTC
**Domain:** communication
**Last synced:** 2026-07-08 10:33 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
*(none — repo may need first-session setup)*
## Inbox Hygiene
**Stale unread:** 2 message(s) older than 3 day(s) — triage at session start.
**Missing thread_id:** 2 unread message(s) lack supersession chains.
---
## MCP Orientation (when available)
If the state-hub MCP server is reachable, call:
`get_domain_summary("netkingdom")`
`get_domain_summary("communication")`
This provides richer cross-domain context.
If the MCP call fails, use this file as your orientation source.

24
.repo-classification.yaml Normal file
View File

@@ -0,0 +1,24 @@
repo_classification:
standard: Repo Classification Standard
version: '1.0'
classified_at: '2026-06-22'
classified_by: human
category: product
domain: infotech
secondary_domains: []
capability_tags:
- security
- identity
- platform
- operations
- access-control
business_stake:
- technology
- operations
- legal
- automation
business_mechanics:
- control
- operation
- adaptation
notes: NetKingdom security/identity platform; standard §13.4 — human confirmed.

View File

@@ -4,10 +4,10 @@
**Purpose:** NetKingdom infrastructure and IAM platform — orchestrates SSO/MFA, multi-user onboarding, and the Keycape/Keycloak IAM profile across the NetKingdom environment.
**Domain:** netkingdom
**Domain:** infotech
**Repo slug:** net-kingdom
**Topic ID:** `a6c6e745-bf54-4465-9340-1534a2be493e`
**Workplan prefix:** `NET-WP-`
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
**Workplan prefix:** `NK-WP-`
---
@@ -20,6 +20,12 @@ there is no MCP server for Codex agents.
|---------|-----|
| Local workstation | `http://127.0.0.1:8000` |
| Remote via tunnel | `http://127.0.0.1:18000` |
| Optional local edge relay | http://127.0.0.1:18080 |
When an operator has enabled the edge relay, set API_BASE to the relay URL.
Queueable writes return an explicit queued receipt if the central hub is
unreachable. Treat that as pending local evidence, then ask the operator to run
statehub outbox status/replay after connectivity returns.
### Orient at session start
@@ -27,8 +33,8 @@ there is no MCP server for Codex agents.
# Offline brief — works without hub connection
cat .custodian-brief.md
# Active workstreams for this domain
curl -s "http://127.0.0.1:8000/workstreams/?topic_id=a6c6e745-bf54-4465-9340-1534a2be493e&status=active" \
# Active workplans for this domain
curl -s "http://127.0.0.1:8000/workplans/?topic_id=cee7bedf-2b48-46ef-8601-006474f2ad7a&status=active" \
| python3 -m json.tool
# Check inbox
@@ -51,20 +57,20 @@ curl -s -X POST http://127.0.0.1:8000/progress/ \
"summary": "what was done",
"event_type": "note",
"author": "codex",
"workstream_id": "<uuid>",
"workplan_id": "<uuid>",
"task_id": "<uuid>"
}'
```
Omit `workstream_id` / `task_id` when not applicable.
Omit `workplan_id` / `task_id` when not applicable.
### Update task status
```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \
-d '{"status": "in_progress"}'
# values: todo | in_progress | done | blocked
-d '{"status": "progress"}'
# values: wait | todo | progress | done | cancel
```
### Flag a task for human review
@@ -80,10 +86,10 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
## Session Protocol
**Start:**
1. `cat .custodian-brief.md` — domain goal and open workstreams (offline-safe)
1. `cat .custodian-brief.md` — domain goal and open workplans (offline-safe)
2. Check inbox: `GET /messages/?to_agent=net-kingdom&unread_only=true`; mark read
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
4. Check blocked tasks: `GET /tasks/?needs_human=true`
4. Check human-needed tasks: `GET /tasks/?needs_human=true`
**During work:**
- Update task statuses in workplan files as tasks progress
@@ -92,12 +98,69 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
**Close:**
1. Update workplan file task statuses to reflect progress
2. Log: `POST /progress/` with a summary of what changed
3. Note for the custodian operator: after workplan file changes, run from
`~/state-hub`:
3. After workplan file changes, run:
```bash
make fix-consistency REPO=net-kingdom
statehub fix-consistency
```
This syncs task status from files into the hub DB.
Coding agents should run this directly; ask the operator only if the CLI or
State Hub API is unavailable. This syncs task status from files into the hub DB.
---
## Credential and access routing
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
for inference. Run this check **before** requesting secrets, API keys, SSH access,
login tokens, or database passwords — in any repo, not only `ops-warden`.
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
other credential need belongs to another subsystem. **Do not** message
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
### Lookup (do this first)
```bash
warden route find "<describe your need>" --json
warden route show <catalog-id> --json
```
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
| Agent runtime | How to orient |
| --- | --- |
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=net-kingdom` is for coordination, not secret vending |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workplans; **still** use `warden route` for credential ownership |
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
### Quick routing table
| I need… | Owner | ops-warden executes? |
| --- | --- | --- |
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
| Authorization decision | flex-auth | No — route only |
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
### Anti-patterns (do not do these)
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
- Pasting secrets into Git, State Hub, workplans, logs, or chat
### Other capabilities (reuse-surface)
Non-credential capabilities are usually discovered through **reuse-surface** federation
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
every repo's agent instructions because it is high-frequency, high-risk, and easy to
get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
<!-- REPO-AGENTS-EXTENSIONS -->
<!-- Append repo-specific agent instructions below this marker.
The state-hub template sync preserves content after this line. -->
---
@@ -124,7 +187,7 @@ anything needing analysis, design, approval, dependencies, or multiple phases.
id: NET-WP-NNNN
type: workplan
title: "..."
domain: netkingdom
domain: infotech
repo: net-kingdom
status: proposed | ready | active | blocked | backlog | finished | archived
owner: codex
@@ -146,7 +209,7 @@ derived health labels, not frontmatter statuses.
` ` `task
id: NET-WP-NNNN-T01
status: todo | in_progress | done | blocked
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
` ` `
@@ -154,7 +217,7 @@ state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
Task description text.
```
Status progression: `todo` → `in_progress` → `done` (or `blocked`)
Status progression: `todo` → `progress` → `done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
To create a new workplan:
1. Write the file following the format above

View File

@@ -8,4 +8,5 @@
@.claude/rules/stack-and-commands.md
@.claude/rules/architecture.md
@.claude/rules/repo-boundary.md
@.claude/rules/credential-routing.md
@.claude/rules/agents.md

View File

@@ -106,7 +106,7 @@ check-secrets: ## Fail if any file under secrets/ is not SOPS-encrypted
fi; \
case "$$f" in *.age|*.gpg) continue ;; esac; \
echo " UNENCRYPTED: $$f"; bad=1; \
done < <(git ls-files --others --cached 'secrets' 2>/dev/null | grep -v '/$'); \
done < <(git ls-files --others --cached 'secrets' 2>/dev/null | grep -v '/$$'); \
if [[ "$$bad" -ne 0 ]]; then \
echo ""; \
echo "ERROR: Unencrypted secret(s) detected. Encrypt with: sops --encrypt --in-place <file>"; \
@@ -179,6 +179,12 @@ creds-agent-status: ## Show current v2 bootstrap state (agent mode)
creds-emergency-reprint: ## Re-deliver emergency bundle (if lost/stolen — reprints, rotates nothing)
@bash sso-mfa/bootstrap/emergency-bundle.sh --reprint
openbao-init-unseal: ## SOPS-held OpenBao init/unseal (NET-WP-0020 T2; requires sops-held-automation model selected)
@bash sso-mfa/bootstrap/openbao-init-unseal.sh
openbao-init-unseal-dry-run: ## Dry-run the SOPS-held OpenBao init/unseal path
@bash sso-mfa/bootstrap/openbao-init-unseal.sh --dry-run
iam-profile-conformance-test: ## Run IAM Profile v0.2 conformance fixture tests
python3 -m pytest tools/iam-profile-conformance/tests
@@ -305,6 +311,20 @@ security-bootstrap-select-openbao-unseal-custody-model: security-bootstrap-metad
select-openbao-unseal-custody-model \
--model "$(if $(MODEL),$(MODEL),sops-held-automation)"
security-bootstrap-select-deployment-profile: security-bootstrap-metadata-init ## Select deployment profile (production blocks sops-held-automation): make ... PROFILE=production
@[[ -n "$(PROFILE)" ]] || (echo "Usage: make security-bootstrap-select-deployment-profile PROFILE=lab|production"; exit 1)
python3 tools/security-bootstrap-console/security_bootstrap_console.py \
--metadata "$(SECURITY_BOOTSTRAP_METADATA)" \
select-deployment-profile --profile "$(PROFILE)"
security-bootstrap-openbao-ceremony-record-template: ## Print non-secret attended OpenBao ceremony record template
python3 tools/security-bootstrap-console/security_bootstrap_console.py openbao-ceremony-record-template
security-bootstrap-validate-openbao-ceremony-record: ## Validate non-secret attended ceremony record: make ... [EVIDENCE=.local/openbao-ceremony-record.json]
python3 tools/security-bootstrap-console/security_bootstrap_console.py \
validate-openbao-ceremony-record \
--evidence "$(if $(EVIDENCE),$(EVIDENCE),.local/openbao-ceremony-record.json)"
security-bootstrap-metadata-init: ## Create durable local non-secret bootstrap metadata if missing
@mkdir -p "$$(dirname "$(SECURITY_BOOTSTRAP_METADATA)")"
@if [[ -f "$(SECURITY_BOOTSTRAP_METADATA)" ]]; then \
@@ -329,6 +349,7 @@ security-bootstrap-ui: security-bootstrap-metadata-init ## Serve local custody a
check-secrets creds-init creds-generate creds-bundle creds-apply creds-verify \
creds-status creds-rotate \
creds-agent-init creds-agent-status creds-emergency-reprint \
openbao-init-unseal openbao-init-unseal-dry-run \
iam-profile-conformance-test playbook-contract-test \
security-bootstrap-console-test security-bootstrap-scripts-syntax \
security-bootstrap-console security-bootstrap-king-kit \
@@ -348,6 +369,11 @@ security-bootstrap-ui: security-bootstrap-metadata-init ## Serve local custody a
security-bootstrap-sign-custody-roster \
security-bootstrap-approve-custody \
security-bootstrap-custody-packet security-bootstrap-openbao-preflight \
security-bootstrap-openbao-unseal-custody-models \
security-bootstrap-select-openbao-unseal-custody-model \
security-bootstrap-select-deployment-profile \
security-bootstrap-openbao-ceremony-record-template \
security-bootstrap-validate-openbao-ceremony-record \
security-bootstrap-metadata-init security-bootstrap-ui \
security-bootstrap-console-test security-bootstrap-scripts-syntax \
security-bootstrap-validate-keycape-client

View File

@@ -1,3 +1,10 @@
# NetKingdom
NetKingdom provides a dynamic self optimizing full circle security-platform for kubernetes deployed IT-infrastructures.
## Security Infrastructure Documents
- [secrets-engine security infrastructure boundary](docs/secrets-engine-security-infrastructure-boundary.md)
defines how secrets-engine participates in the NetKingdom security
infrastructure and how it interacts with OpenBao, flex-auth, user-engine,
ops-warden, ops-bridge, info-tech-canon, State Hub, and agents.

View File

@@ -22,13 +22,21 @@ NetKingdom is a self-optimizing security platform for Kubernetes-based IT infras
- NetKingdom IAM Profile specification (versioned OIDC/PKCE contract;
canonical spec: `canon/standards/iam-profile_v0.2.md`)
- SSO/MFA Platform: Keycloak with LDAP/Entra federation, enterprise identity (NK-WP-0001)
- Local Identity: file-based user store + minimal OIDC server for bootstrap phase (NK-WP-0002)
- SSO/MFA Platform: Keycloak with LDAP/Entra federation, enterprise identity (NK-WP-0001, finished)
- Local Identity: file-based user store + minimal OIDC server for bootstrap phase (NK-WP-0002, finished)
- User Engine Boundary Contract: source-of-truth, membership,
application-onboarding, projection, authorization, and audit contracts for
`user-engine` integration (`canon/standards/user-engine-boundary-contract_v0.1.md`)
- Security bootstrapping: credential management, SOPS/age integration,
platform-root custody, OpenBao runtime secret authority
- OpenBao init/unseal custody models (NET-WP-0020): `sops-held-automation`
(lab, unattended greenfield rebuilds via `creds-bootstrap-agent` Phase 7b),
`attended-ceremony` (production, runbook + non-secret evidence records), and
`auto-unseal-transit` (production HA; seal stanza lives in
railiance-platform) — all gated by the security bootstrap console and a
lab/production deployment profile
- Security bootstrap console (`tools/security-bootstrap-console/`): custody
gates, roster, evidence validators, refuse-live-init boundary
- Architectural decisions (DECISIONS.md): identity source, secrets, GitOps, bootstrap user store
---
@@ -62,9 +70,17 @@ NetKingdom is a self-optimizing security platform for Kubernetes-based IT infras
## Current State
- Status: active (design phase complete, implementation ongoing)
- Implementation: emerging — NK-WP-0001 (SSO/MFA) and NK-WP-0002 (local identity) both in active development
- Stability: evolving
- Status: active — core identity and bootstrap phases delivered; follow-on work proposed
- Implementation: NK-WP-0001 (SSO/MFA), NK-WP-0002 (local identity), the
security bootstrap arc (NET-WP-00150017, 0019), the IAM Profile spec
(NK-WP-0012), user-engine boundary contracts (NK-WP-0014), and OpenBao
unseal custody + SSH automation (NET-WP-0020) are all finished — see
`workplans/archived/`
- Open: NK-WP-0009 (security pattern tutorials) and NK-WP-0011 (enterprise
federation / SAML) are proposed, not yet started
- Stability: stabilizing — bootstrap/custody tooling is live-proven (greenfield
OpenBao init/unseal proof 2026-07-02); production custody models are gated
by evidence
- Usage: foundational authentication layer for all NetKingdom deployments
---
@@ -108,6 +124,13 @@ description: Enterprise-grade Keycloak-based SSO with LDAP/Entra federation, MFA
keywords: [sso, mfa, keycloak, ldap, entra, federation, oidc, enterprise]
```
```capability
type: security
title: OpenBao unseal custody models and bootstrap automation
description: Three gated init/unseal custody models — SOPS-held automation for unattended lab rebuilds (greenfield-proven), attended ceremony with non-secret evidence records for production, and transit/KMS auto-unseal for production HA — enforced by the security bootstrap console and a lab/production deployment profile.
keywords: [openbao, unseal, custody, bootstrap, sops, age, ceremony, transit, auto-unseal, console]
```
```capability
type: security
title: Bootstrap local identity service
@@ -121,9 +144,12 @@ keywords: [bootstrap, local-identity, oidc, minimal, dev, sandbox]
- Start with: `wiki/` (specifications and decisions), `DECISIONS.md` (key architectural choices D1D5)
- Key files / directories: `docs/platform-root-custody.md`, `sso-mfa/`
(NK-WP-0001 active workplan), `local-identity/` (NK-WP-0002),
`workplans/`
- Entry points: `workplans/NK-WP-0001-sso-mfa-platform.md` and `NK-WP-0002-local-identity.md` for current work
(SSO/MFA platform + bootstrap scripts), `local-identity/`,
`tools/security-bootstrap-console/`, `workplans/` (finished plans in
`workplans/archived/`)
- Entry points: `workplans/NK-WP-0009-netkingdom-security-pattern-tutorials.md`
and `workplans/NK-WP-0011-enterprise-federation-saml.md` (proposed next
work); finished context in `workplans/archived/`
- User-domain boundary contract:
`canon/standards/user-engine-boundary-contract_v0.1.md`
- User-engine integration assessment (intent/scope fit, gaps, and recommendations):
@@ -131,5 +157,8 @@ keywords: [bootstrap, local-identity, oidc, minimal, dev, sandbox]
- Bootstrap/custody entry points:
`docs/platform-root-custody.md`,
`docs/security-bootstrap-use-cases.md`,
`workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md`,
and `workplans/NET-WP-0016-guided-security-bootstrap-experience.md`
`docs/openbao-unseal-custody-models.md` (three custody models + deployment
profile), and `docs/openbao-attended-ceremony-runbook.md` (production
ceremony); history of the custody/bootstrap arc in `workplans/archived/`
(NET-WP-00150017, 0019) and
`workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md`

View File

@@ -0,0 +1,82 @@
# OpenBao Attended Ceremony Runbook
Date: 2026-07-02
Status: active — production custody model (`attended-ceremony`, NET-WP-0020 T3)
Human-attended OpenBao init/unseal for production trust posture. The security
bootstrap console **never runs `bao operator init`** (refuse-live-init
boundary) — this runbook is executed by the `openbao-ceremony-operator` role
and evidenced by a **non-secret** ceremony record.
Contrast with `sops-held-automation` (lab posture): there, root token and
unseal shares live together in one SOPS bundle for unattended rebuild loops.
The attended ceremony keeps unseal shares **out of band** and retires the root
token, so no single artifact can open the platform.
---
## Preconditions
1. Console gates green up to the init ceremony:
`make security-bootstrap-console` — custody strategy approved, preflight
passed.
2. Select profile and model (production blocks the lab default):
```bash
python3 tools/security-bootstrap-console/security_bootstrap_console.py \
--metadata .local/security-bootstrap.json \
select-deployment-profile --profile production
python3 tools/security-bootstrap-console/security_bootstrap_console.py \
--metadata .local/security-bootstrap.json \
select-openbao-unseal-custody-model --model attended-ceremony
```
3. OpenBao deployed and reachable:
`make -C ../railiance-platform openbao-status` (expect uninitialized/sealed
on greenfield).
4. Two people present (operator + witness) where the custody roster requires
it; unseal-share escrow destinations agreed per
`docs/platform-root-custody.md` (signed custody roster).
## Ceremony
Follow `railiance-platform/docs/openbao.md` for the exact commands. Outline:
1. **Init** — operator runs `bao operator init` (3 shares, threshold 2)
directly against the pod from a terminal. Output goes only to the
operator's screen — never into the console, chat, State Hub, or files
inside a Git checkout.
2. **Escrow** — each unseal share is transcribed to its escrow destination
(offline packet / password safe per roster). No two shares in the same
custody location.
3. **Unseal** — replay threshold shares; verify
`initialized=true sealed=false`.
4. **Root retirement** — use the root token only for the initial
configuration handoff (`make -C ../railiance-platform
openbao-configure-initial`), then revoke it (`bao token revoke -self`) or
escrow it per roster; record the disposition.
## Evidence
Record the ceremony in a non-secret JSON record and validate it:
```bash
python3 tools/security-bootstrap-console/security_bootstrap_console.py \
openbao-ceremony-record-template > .local/openbao-ceremony-record.json
# edit: dispositions, dates, roles — NEVER shares, tokens, or key material
make security-bootstrap-validate-openbao-ceremony-record
```
The validator refuses records containing secret-looking markers (tokens, key
blocks, otpauth URIs) or leftover template placeholders. After a valid
record, set the console flags (`openbao_initialized`,
`openbao_post_unseal_verified`) in the metadata so the gates advance.
## Related
- `docs/openbao-unseal-custody-models.md` — model framework
- `docs/platform-root-custody.md` — custody roster and share holders
- `docs/security-bootstrap-openbao-ceremony-ux.md` — operator UX notes
- `railiance-platform/docs/openbao.md` — deploy + command-level ceremony

View File

@@ -1,7 +1,8 @@
# OpenBao Unseal Custody Models
Date: 2026-06-17
Status: framework — automation path active; production paths planned
Date: 2026-06-17 (updated 2026-07-02)
Status: all three models implemented — automation path proven greenfield;
production models gated by deployment profile and evidence
NetKingdom bootstrap must support **three** OpenBao init/unseal custody models.
Development starts with **maximum automation** for fast test cycles, then adds
@@ -18,45 +19,61 @@ during bootstrap and rebuild.
| Model ID | Label | Custody strength | Automation | Status |
| --- | --- | --- | --- | --- |
| `sops-held-automation` | SOPS-held unseal | Lab / fast iteration | High | **Implemented** (console + creds agent path) |
| `attended-ceremony` | Attended ceremony | Production | Low | Planned |
| `auto-unseal-transit` | Auto-unseal (transit/KMS) | Production HA | High | Planned |
| `sops-held-automation` | SOPS-held unseal | Lab / fast iteration | High | **Implemented** (console + creds agent path; greenfield-proven 2026-07-02) |
| `attended-ceremony` | Attended ceremony | Production | Low | **Implemented** (runbook + ceremony-record validator) |
| `auto-unseal-transit` | Auto-unseal (transit/KMS) | Production HA | High | **Implemented** (Helm seal stanza prepared; gate blocked until seal configured + verified) |
### `sops-held-automation` (default for greenfield dev)
- Init/unseal material lives in **SOPS/age** custody bundle (not Git plaintext).
- Applied by `sso-mfa/bootstrap/creds-bootstrap-agent.sh` and related `creds-apply`
tooling after cluster + OpenBao pod exist.
- Applied by `sso-mfa/bootstrap/openbao-init-unseal.sh` (`make
openbao-init-unseal`, NET-WP-0020 T2) after cluster + OpenBao pod exist. The
helper enforces the console custody-model gate, initializes only when
uninitialized (init JSON written straight into the age-custody secrets dir),
replays unseal shares stdin-to-stdin, verifies post-unseal state, and emits
non-secret `openbao_initialized` / `openbao_post_unseal_verified` evidence.
Set `OPENBAO_RUN_CONFIGURE_INITIAL=1` to chain `railiance-platform: make
openbao-configure-initial`.
- Enables **unattended rebuild test cycles** on a 3-node slate.
- **Not** production trust posture — use to prove S1→S3→SSH engine automation,
then graduate to stronger models.
### `attended-ceremony` (production target)
### `attended-ceremony` (production)
- Human-attended `bao operator init`, out-of-band unseal share escrow, root token
retirement — per `railiance-platform/docs/openbao.md`.
retirement — runbook: `docs/openbao-attended-ceremony-runbook.md`
(command detail in `railiance-platform/docs/openbao.md`).
- Matches first successful NetKingdom bootstrap (NET-WP-00150017).
- Console keeps **refuse-live-init** boundary; ceremony runbooks only.
- Console keeps the **refuse-live-init** boundary; the ceremony is evidenced by
a non-secret record validated with `validate-openbao-ceremony-record`
(`make security-bootstrap-validate-openbao-ceremony-record`).
### `auto-unseal-transit` (production HA target)
### `auto-unseal-transit` (production HA)
- OpenBao seal configuration uses **transit** or cloud KMS auto-unseal.
- Pod restart without manual unseal threshold ceremony.
- Requires `railiance-platform` Helm seal stanza + KMS provisioning.
- Seal stanza prepared (disabled by default) in
`railiance-platform/helm/openbao-values.yaml`; enable + migrate per
`railiance-platform/docs/openbao.md` "Auto-Unseal via Transit Seal".
- Console init gate stays **blocked** until `openbao_transit_seal_configured`
and `openbao_auto_unseal_verified` are set in the non-secret metadata.
---
## Development strategy
## Deployment profile
```text
1. Implement automation path (sops-held-automation)
→ SSH engine, warden sign, host CA trust, 3-node rebuild loops
2. Add attended-ceremony gates (block automation defaults in production profile)
3. Add auto-unseal-transit for HA ThreePhoenix rebuilds
The console metadata carries a `deployment_profile` (`lab` default,
`production`). The **production profile blocks `sops-held-automation`** — its
SOPS bundle holds root token and unseal shares together, which is lab posture
only. Selection, status gates, and `openbao-init-unseal.sh` all enforce the
block:
```bash
make security-bootstrap-select-deployment-profile PROFILE=production
```
Each model is selectable in the **security bootstrap console**. Unimplemented
models are **blocked** with a hint pointing to the active automation path.
Each model is selectable in the **security bootstrap console**; gates express
what evidence is still missing for the selected model.
---
@@ -88,7 +105,7 @@ Metadata field: `openbao_unseal_custody_model`
| S1 OS baseline | railiance-infra | 3 nodes |
| S2 k3s HA | railiance-cluster | ThreePhoenix |
| S3 OpenBao deploy | railiance-platform | `make openbao-deploy` |
| Init/unseal apply | net-kingdom | `creds-bootstrap-agent.sh` (sops-held) |
| Init/unseal apply | net-kingdom | `make openbao-init-unseal` (sops-held) |
| Platform config | railiance-platform | `openbao-configure-initial` |
| SSH engine | railiance-platform | `openbao-configure-ssh` (planned) |
| Host CA trust | railiance-infra | `bootstrap-ssh-ca` (planned) |

View File

@@ -76,6 +76,17 @@ and what NetKingdom is responsible for (meta-orchestration).
| **Repo owns** | operating these stateful services to durability/recovery guarantees behind stable interfaces |
| **NetKingdom orchestrates** | which platform services a scenario needs; runtime secret-authority boundaries (platform-root vs tenant) and policies; backup/DR requirements; the identity-integration point; runtime-secret-trust readiness |
### `secrets-engine` — secret workflow and delivery orchestration
| | |
| --- | --- |
| **Resources held** | non-secret secret-lane catalog, stage policy model, delivery-mode contracts, OpenBao apply/delivery plans, readiness and evidence records |
| **Repo owns** | workflow and automation mechanics for approved secret establishment, provisioning, verification, delivery, rotation, revocation, and deactivation |
| **NetKingdom orchestrates** | secret organization concepts; build/test/production stage boundaries; which systems participate in decisions and delivery; OpenBao/flex-auth/user-engine/ops-warden/ops-bridge/info-tech-canon interaction boundaries; the rule that OpenBao remains custody and audit backend |
See `docs/secrets-engine-security-infrastructure-boundary.md` for the canonical
cross-system boundary and interaction model.
### `key-cape` — identity
| | |

View File

@@ -0,0 +1,363 @@
# NetKingdom Security Infrastructure Boundary
## Purpose
This document defines how `secrets-engine` participates in the NetKingdom
security infrastructure. It is the first place agents and operators should read
when they need to understand responsibility boundaries, system interactions,
secret organization, and stage-specific policy concepts.
`secrets-engine` is the workflow and interaction layer for approved secret work.
OpenBao remains the custody, policy, lease, and audit backend.
## Core Responsibility
`secrets-engine` turns an approved secret request into a safe, reviewable,
auditable sequence of actions:
1. Resolve the catalog entry and stage.
2. Verify the decision or approval state.
3. Render a plan for OpenBao metadata, value provisioning, verification, and
delivery.
4. Apply only allowed OpenBao changes through the minimal stage role.
5. Provision or confirm the value without exposing it in coordination systems.
6. Deliver the secret to a workload or command without printing it.
7. Record non-secret evidence for audit, troubleshooting, and readiness.
8. Rotate, revoke, deactivate, or mark compromised secrets through the same
catalog and decision model.
## Boundary Map
| System | Owns | secrets-engine interaction | Must not do |
| --- | --- | --- | --- |
| OpenBao | Secret custody, ACL policy enforcement, leases, response wrapping, audit logs | Use OpenBao through constrained build, test, and production roles; write policies, auth roles, metadata, and values only through validated plans | Treat OpenBao UI/CLI as the routine operator interface; bypass decision checks; store raw values outside OpenBao |
| flex-auth | Authorization decisions and policy reasoning | Ask whether an actor may establish, access, rotate, or deactivate a cataloged secret for a purpose | Store or deliver secret values; become the vault |
| user-engine / key-cape | Human and service identity, user lifecycle, claims, groups, MFA/OIDC context | Consume stable identity claims and service identities for OpenBao auth bindings and decision checks | Own secret lifecycle policy; mint provider tokens |
| ops-warden | SSH certificate issuance and credential routing front door | Route non-SSH credential requests to secrets-engine; show catalog readiness and safe next commands | Vend API keys, provider tokens, database passwords, or OpenBao tokens |
| ops-bridge | Remote execution, tunnels, transport, and operator connectivity | Consume secrets through `secrets-engine exec` or scoped delivery when a remote operation needs credentials | Persist secrets in tunnel configs, logs, or reusable shell state |
| info-tech-canon | Canonical terminology, governance patterns, stage taxonomy, naming conventions, documentation profiles | Reuse canon terms for stage, catalog, approval, delivery, evidence, rotation, and deactivation concepts | Act as runtime authority or store secret values |
| State Hub | Non-secret workstreams, decisions, progress, review comments, and evidence pointers | Read decisions and write non-secret progress/evidence | Store raw secret values, tokens, wrapped tokens, passwords, or private keys |
| llm-connect / agents | Reasoning and task execution | Receive non-secret plans and use exec-time delivery only when needed | Place secrets in prompts, chat, model context, or logs |
## System-of-Systems Position
`secrets-engine` sits between request/decision systems and OpenBao operations.
It is not the source of identity, authorization, or custody. It is the place
where those systems are composed into an operator- and agent-usable workflow.
```text
request / ops-warden route
|
v
State Hub decision <---- flex-auth authorization
|
v
secrets-engine catalog + plan + policy guard
|
v
OpenBao role/policy/value operations
|
v
verification + evidence + safe delivery
|
v
workload, CI job, operator command, or ops-bridge task
```
## Core Concepts
### Secret Lane
A secret lane is the governed path for one secret use case. It joins a catalog
id, owner, stage, OpenBao location, fields, consumer binding, approval model,
delivery modes, verification checks, and revocation behavior.
Example catalog id:
```text
whynot-design-npm-publish
```
### Catalog Entry
A catalog entry is non-secret metadata. It is the primary object agents and
operators should reference. It must never contain raw secret values.
Required concepts:
- catalog id;
- stage;
- owner domain, repo, workload, or service;
- OpenBao backend mount/path and fields;
- allowed consumers and identity claims;
- allowed delivery modes;
- approval/decision requirement;
- verification requirements;
- rotation, revocation, and compromised/deactivated behavior;
- evidence expectations.
### Stage
A stage is a security boundary. The initial stages are:
- `build`;
- `test`;
- `production`.
Short aliases such as `prod` may exist in CLI flags, but catalog and policy
documents should normalize to the canonical names above.
### Decision
A decision is the non-secret approval state that allows a secret lane to be
created, changed, delivered, rotated, or deactivated. Decisions live outside the
vault, usually in State Hub and/or flex-auth. A production action must fail
closed without an approved decision, except for explicit break-glass flows.
### Delivery Mode
A delivery mode defines how a secret reaches a consumer without becoming visible
in normal coordination surfaces.
Initial allowed modes:
- exec-time environment injection;
- exec-time temporary config file;
- response-wrapped handoff;
- local bootstrap file import for setup only;
- non-production generated test value.
Denied modes:
- chat;
- prompts or model context;
- Git;
- State Hub message body;
- command-line argument containing the raw value;
- normal logs;
- long-lived untracked local files.
### Evidence
Evidence is non-secret proof that an action happened and what its result was.
Evidence may include catalog id, actor, stage, decision id, OpenBao path, field
names, lease accessor, timestamps, audit request ids, and pass/fail status. It
must not include raw secret values.
## Stage Policy Model
### Build
Purpose:
- local development;
- disposable integration experiments;
- generated or synthetic values;
- quick iteration before stronger policy is justified.
Allowed direction:
- manage build-stage metadata and values under build prefixes;
- generate fake or low-impact values;
- use lightweight decisions or repo-owner approval;
- support fast `exec` flows for development commands.
Restrictions:
- no production mounts or paths;
- no broad OpenBao sys/auth administration;
- no reuse of build values in test or production;
- short TTLs by default.
### Test
Purpose:
- staging and integration verification;
- proving delivery, rotation, and negative checks;
- rehearsing production policy with lower impact.
Allowed direction:
- manage approved test-stage metadata and values;
- run positive and negative access verification;
- use provider tokens or service credentials with test-only scope;
- record evidence before a similar production lane is considered ready.
Restrictions:
- no production values;
- no production auth roles;
- stricter identity binding than build;
- explicit denial checks for unrelated consumers.
### Production
Purpose:
- real workload secrets;
- real provider tokens;
- auditable delivery to production-capable consumers.
Allowed direction:
- apply approved production metadata such as ACL policies and auth roles;
- provision values only through approved custody modes;
- deliver values through exec-time or workload-scoped mechanisms;
- record evidence and readiness state.
Restrictions:
- no action without approved decision, except explicit break-glass;
- no broad `platform-root`, `platform-admin`, or wildcard admin semantics;
- no raw value printing;
- no default raw reads by agents;
- rotation, revocation, and compromised/deactivated states must be expressible.
## OpenBao Role Layers
The initial OpenBao-facing roles are:
| Role | Stage | Main capability |
| --- | --- | --- |
| `secrets-engine-build` | build | Manage approved build-stage metadata and values under build prefixes. |
| `secrets-engine-test` | test | Manage approved test-stage metadata and values and run verification. |
| `secrets-engine-prod` | production | Apply approved production metadata and perform constrained delivery/provisioning actions. |
A temporary bootstrap credential may be used to create these roles and policies.
Bootstrap credentials must be local-only, outside repositories, mode 0600,
revocable, and tracked as temporary setup material.
## Secret Organization
The catalog is the stable interface. Physical OpenBao paths may evolve, but
catalog entries must keep enough structure to enforce stage and ownership.
Recommended logical dimensions:
```text
stage / tenant-or-org / workload-or-service / bundle / field
```
Recommended OpenBao path shape when a shared mount is used:
```text
<mount>/workloads/<stage>/<tenant-or-org>/<workload-or-service>/<bundle>
```
If a mount is already stage-specific, the stage may be represented by the mount
instead:
```text
<stage-mount>/workloads/<tenant-or-org>/<workload-or-service>/<bundle>
```
The catalog must make the resolved stage explicit in either case. Existing
legacy paths should be represented by catalog metadata before being normalized.
## Policy Families
The engine should distinguish policy families rather than treating every
OpenBao policy as a free-form string:
| Family | Purpose |
| --- | --- |
| catalog-read | Read non-secret catalog metadata and readiness. |
| stage-applier | Apply metadata inside one stage boundary. |
| value-provisioner | Write or import secret values under approved custody. |
| delivery-runner | Fetch and inject a value only into a child process or workload-specific config. |
| verifier | Run positive/negative checks without printing values. |
| revoker | Revoke leases, deactivate lanes, or mark compromised state. |
| auditor | Produce non-secret reports and evidence summaries. |
Production policy families should be split so that metadata apply, value
provisioning, and value delivery do not automatically imply each other.
## Main Interaction Flows
### Establish a Secret Lane
1. Actor requests a cataloged secret lane.
2. flex-auth and/or State Hub records an approval decision.
3. secrets-engine renders the OpenBao policy/auth/value plan.
4. Operator or agent reviews the plan.
5. secrets-engine applies allowed metadata through the minimal stage role.
6. Secret value is provisioned by approved custody mode.
7. Positive and negative verification run.
8. Catalog readiness becomes resolvable for ops-warden and consumers.
### Use a Secret
1. Consumer asks ops-warden or secrets-engine for a catalog id.
2. secrets-engine checks catalog, stage, readiness, identity, and decision state.
3. secrets-engine fetches the value through OpenBao using a delivery role.
4. The value is injected only into the child process or temporary config.
5. Temporary material is removed.
6. Non-secret evidence is recorded.
Target shape:
```bash
secrets-engine exec --catalog whynot-design-npm-publish -- npm publish
```
### Rotate a Secret
1. Rotation request is linked to the existing catalog entry.
2. New value is provisioned under approved custody.
3. Consumers are switched or verified.
4. Old value is revoked or deactivated.
5. Evidence records old/new version identifiers without raw values.
### Mark Compromised or Deactivated
1. Actor marks the lane compromised or requests deactivation.
2. secrets-engine blocks delivery immediately where possible.
3. OpenBao leases/tokens are revoked.
4. Related policies or auth roles are disabled if required.
5. ops-warden readiness becomes not resolvable.
6. Evidence and follow-up tasks are recorded.
## Explicit Non-responsibilities
secrets-engine must not:
- replace OpenBao;
- make identity or authorization decisions by itself;
- issue SSH certificates;
- establish network tunnels;
- persist raw secret values outside OpenBao as normal operation;
- put secrets into prompts, chat, State Hub, Git, or logs;
- make production changes without an approved decision;
- normalize insecure bootstrap shortcuts into permanent operations.
## First Pilot
The first pilot lane is:
```text
catalog id: whynot-design-npm-publish
field: NPM_AUTH_TOKEN
consumer: whynot-design npm publish workflow
```
The pilot should prove:
- a catalog entry can describe the lane without a raw value;
- an approved decision can drive OpenBao policy and auth role creation;
- provisioning can happen without secret disclosure;
- positive and negative verification can be recorded;
- `secrets-engine exec` can deliver the token to `npm publish` safely;
- ops-warden can route to secrets-engine and report readiness.
## Canonicalization Tasks
These concepts should be aligned with info-tech-canon as the implementation
hardens:
- stage names and aliases;
- catalog entry schema terms;
- decision states and evidence vocabulary;
- delivery mode names;
- compromised, deactivated, rotated, ready, and resolvable states;
- standard OpenBao path and policy naming patterns.

View File

@@ -0,0 +1,40 @@
# OpenBao greenfield init/unseal proof (NET-WP-0020 T2)
Date: 2026-07-02
Scope: `sso-mfa/bootstrap/openbao-init-unseal.sh` proven live against a
genuinely uninitialized OpenBao 2.5.5 instance.
## Method
A fresh local `bao server` (file storage, throwaway scratchpad dir) was started
uninitialized and sealed. A `kubectl` shim forwarded the script's
`kubectl -n openbao exec openbao-0 -- bao …` calls to the local instance, so
the script ran byte-for-byte unmodified logic against real OpenBao. Console
metadata lived in a scratch file with `sops-held-automation` selected.
This substitutes for the "rebuild slate" run: it exercises the exact
init/unseal/verify code paths on a greenfield instance. The first 3-node
rebuild will re-run the same script through `creds-bootstrap-agent.sh`
Phase 7b.
## Results
1. **Custody gate refusal** — with no model selected, the script refused
(`unseal custody model is 'unselected'`). Matches earlier negative tests.
2. **Bug found and fixed**`bao operator unseal -` does **not** read the
share from stdin (OpenBao treats `-` as the literal key: *"'key' must be a
valid hex or base64 string"*; without an argument it demands a TTY). The
live cluster never hit this because it was already unsealed. Fixed to
`bao write sys/unseal key=-`, which reads the value from stdin — shares
still never touch argv or logs.
3. **Greenfield init path** — uninitialized → `operator init` (3 shares,
threshold 2) → init.json written 0600 into the age-custody secrets dir →
share replay stopped at threshold (share 2 of 3) → post-unseal verified.
Evidence: `openbao_did_init_this_run=true`, `openbao_initialized=true`,
`openbao_post_unseal_verified=true`.
4. **Restart/reseal path** — server restarted (initialized + sealed) →
script skipped init, replayed SOPS-held shares to threshold → verified.
Evidence: `openbao_did_init_this_run=false`, both flags true.
Test server, storage, and init material were destroyed after the proof
(init.json shredded).

View File

@@ -0,0 +1,130 @@
---
id: capability.security.iam-tooling-suite
name: NetKingdom Security/IAM Tooling Suite
summary: Dynamic, self-optimizing security platform for Kubernetes-deployed IT infrastructure; owns canonical
IAM/security standards and executable conformance tooling (IAM profile conformance, playbook capability
contract validation, security bootstrap console).
owner: net-kingdom
status: draft
domain: infotech
tags:
- security
- iam
- kubernetes
- conformance
maturity:
discovery:
current: D3
target: D5
confidence: medium
rationale: README plus docs/secrets-engine-security-infrastructure-boundary.md describe an explicit
integration boundary with OpenBao, flex-auth, user-engine, ops-warden, ops-bridge, info-tech-canon,
and State Hub; canon/standards/ holds versioned standards (iam-profile_v0.2.md, playbook-capability-contract_v0.1.md)
that key-cape and other repos implement against.
availability:
current: A2
target: A3
confidence: medium
rationale: 'No top-level package manifest, but tools/ holds three real, independently documented and
runnable tools: iam-profile-conformance (executable checks, pytest fixtures), playbook-capability-contract
(executable validator), and security-bootstrap-console (local console + localhost web UI).'
external_evidence:
completeness:
level: C1
confidence: low
basis: scope_vs_intent_and_consumer_expectations
satisfied_expectations:
- versioned canon standards already implemented by a sibling repo (key-cape)
- three documented, runnable conformance/bootstrap tools under tools/
broken_expectations: []
out_of_scope_expectations: []
reliability:
level: R1
confidence: low
basis: consumer_quality_signals
known_reliability_risks:
- no top-level packaging; each tool under tools/ has its own runtime dependencies, no unified install
path yet
discovery:
intent: Own the canonical IAM/security standards for the Coulomb ecosystem and provide executable conformance
tooling so implementers (like key-cape) can verify against the standard rather than guessing.
includes:
- canon/standards/ versioned IAM and playbook-capability-contract standards
- IAM profile conformance checker
- playbook capability contract validator
- security bootstrap console (local, non-secret-collecting)
excludes:
- concrete IAM implementations themselves (see key-cape for lightweight mode)
- live secret value handling (bootstrap console explicitly refuses live OpenBao initialization)
assumptions: []
use_cases: []
research_memos: []
availability:
current_level: A2
target_level: A3
current_artifacts:
- tools/iam-profile-conformance
- tools/playbook-capability-contract
- tools/security-bootstrap-console
target_artifacts: []
consumption_modes:
- cli
- local web ui
relations:
depends_on: []
supports: []
related_to: []
evidence:
documentation:
- README.md
- docs/secrets-engine-security-infrastructure-boundary.md
- tools/*/README.md
tests:
- tools/iam-profile-conformance (pytest fixtures)
consumer_feedback: []
bug_reports: []
incidents: []
consumer_guidance:
recommended_for:
- implementers needing to verify IAM/security conformance against Coulomb's canonical standards
not_recommended_for:
- needs for a packaged, single-install security suite (currently three separate tools)
known_limitations:
- no unified top-level packaging across the three tools
promotion_history: []
---
# NetKingdom Security/IAM Tooling Suite
## Overview
`net-kingdom` provides a dynamic, self-optimizing security platform for Kubernetes-deployed infrastructure. It owns the canonical IAM and security standards (implemented by sibling repos like `key-cape`) and ships three executable conformance/bootstrap tools under `tools/`: IAM profile conformance checks, a playbook capability contract validator, and a non-secret-collecting security bootstrap console.
## Assessment notes
### Discovery
README plus docs/secrets-engine-security-infrastructure-boundary.md describe an explicit integration boundary with OpenBao, flex-auth, user-engine, ops-warden, ops-bridge, info-tech-canon, and State Hub; canon/standards/ holds versioned standards (iam-profile_v0.2.md, playbook-capability-contract_v0.1.md) that key-cape and other repos implement against.
### Availability
No top-level package manifest, but tools/ holds three real, independently documented and runnable tools: iam-profile-conformance (executable checks, pytest fixtures), playbook-capability-contract (executable validator), and security-bootstrap-console (local console + localhost web UI).
### Completeness
First-pass honest assessment from the REUSE-WP-0017 coverage campaign
(reuse-surface). No external consumer feedback exists yet; levels reflect
scope-vs-intent documentation quality, not internal code quality.
### Reliability
No production consumer telemetry exists yet; reliability level is
intentionally conservative pending REUSE-WP-0019 reuse-telemetry evidence.
## Promotion checklist
- [x] ID follows `capability.<domain>.<name>` pattern
- [x] Maturity enums match `specs/CapabilityMaturityStandard.md`
- [x] `external_evidence` is populated separately from `maturity`
- [ ] Relations reference valid capability IDs (none yet)
- [x] Index entry added in `registry/indexes/capabilities.yaml`

View File

@@ -1,4 +1,22 @@
version: 1
updated: '2026-06-16'
updated: '2026-07-06'
domain: helix_forge
capabilities: []
capabilities:
- id: capability.security.iam-tooling-suite
name: NetKingdom Security/IAM Tooling Suite
summary: Dynamic, self-optimizing security platform for Kubernetes-deployed IT infrastructure; owns
canonical IAM/security standards and executable conformance tooling (IAM profile conformance, playbook
capability contract validation, security bootstrap console).
vector: D3 / A2 / C1 / R1
domain: infotech
status: draft
owner: net-kingdom
path: registry/capabilities/capability.security.iam-tooling-suite.md
tags:
- security
- iam
- kubernetes
- conformance
consumption_modes:
- cli
- local web ui

View File

@@ -88,10 +88,19 @@ if [[ ! -f "$AGE_KEY" ]]; then
fi
fi
AGE_PUBKEY=$(grep 'public key:' "$AGE_KEY" | awk '{print $NF}')
[[ -z "$AGE_PUBKEY" ]] && die "could not read public key from $AGE_KEY"
ok "age key ready: ${AGE_PUBKEY:0:20}"
state_set "age_key_present" "true"
if [[ -f "$AGE_KEY" ]]; then
AGE_PUBKEY=$(grep 'public key:' "$AGE_KEY" | awk '{print $NF}')
[[ -z "$AGE_PUBKEY" ]] && die "could not read public key from $AGE_KEY"
ok "age key ready: ${AGE_PUBKEY:0:20}"
state_set "age_key_present" "true"
elif [[ "$DRY_RUN" == true ]]; then
# Dry-run on a machine without the age key (key generation was skipped
# above): continue with a placeholder recipient so later phases can render.
AGE_PUBKEY="age1dryrunplaceholderrecipient"
ok "age key absent — dry-run continues with placeholder recipient"
else
die "could not read public key from $AGE_KEY"
fi
# Cluster reachability
if ! kubectl cluster-info &>/dev/null; then
@@ -298,6 +307,45 @@ else
echo " [dry-run] would run: bash creds-verify.sh"
fi
# ── Phase 7b: OpenBao init/unseal (sops-held-automation, optional) ───────────
step "7b — OpenBao init/unseal (sops-held-automation, optional)"
# NET-WP-0020 T2: greenfield-rebuild hook. Runs only when the openbao
# namespace exists AND the console has selected sops-held-automation
# (the helper enforces that gate itself and refuses attended-ceremony /
# auto-unseal-transit). Skipped silently on clusters without OpenBao.
if kubectl get namespace openbao &>/dev/null; then
if [[ "$(state_get openbao_post_unseal_verified)" == "true" ]]; then
ok "OpenBao already verified — skipping"
elif [[ "$DRY_RUN" == false ]]; then
if (cd "$SCRIPT_DIR" && bash openbao-init-unseal.sh "$SECRETS_DIR"); then
state_set "openbao_initialized" "true"
state_set "openbao_post_unseal_verified" "true"
ok "OpenBao initialized/unsealed and verified"
# New init material must reach age custody before cleanup.
if [[ -d "$SECRETS_DIR/openbao" ]]; then
log "encrypting OpenBao init material → secrets.enc/ ..."
(cd "$SCRIPT_DIR" && bash encrypt-secrets.sh \
"$SECRETS_DIR" "$AGE_KEY" --no-shred)
cd "$REPO_ROOT"
git add sso-mfa/bootstrap/secrets.enc/ \
sso-mfa/bootstrap/creds-state.yaml
git diff --cached --quiet || git commit -m \
"chore(creds): encrypted OpenBao init material [agent]"
fi
else
warn "OpenBao init/unseal did not complete — see output"
warn "(gate unselected or pod not ready; bootstrap continues)"
fi
else
echo " [dry-run] would run: openbao-init-unseal.sh $SECRETS_DIR"
fi
else
ok "no openbao namespace in this cluster — skipping"
fi
# ── Phase 8: Ops bundle ────────────────────────────────────────────────────────
step "8 — Create ops bundle (age-encrypted snapshot)"

View File

@@ -28,5 +28,12 @@ secrets_applied:
enckey_bootstrapped: true
pi_admin_created: true
# OpenBao init/unseal (NET-WP-0020 T2, sops-held-automation lane only).
# false here because the current cluster's OpenBao was initialized via the
# attended ceremony (NET-WP-00150017), not this automation path. These flip
# to true only when Phase 7b runs on a greenfield rebuild.
openbao_initialized: false
openbao_post_unseal_verified: false
# Derived: all true → bootstrap complete
bootstrap_complete: true

View File

@@ -0,0 +1,204 @@
#!/usr/bin/env bash
# openbao-init-unseal.sh — SOPS-held OpenBao init/unseal automation (NET-WP-0020 T2)
#
# Usage:
# bash sso-mfa/bootstrap/openbao-init-unseal.sh [--dry-run] [SECRETS_DIR]
#
# Implements the `sops-held-automation` unseal custody model from
# docs/openbao-unseal-custody-models.md:
# - refuses to run unless the security bootstrap console has selected
# `sops-held-automation` (attended-ceremony/auto-unseal-transit are never
# automated here);
# - if OpenBao is uninitialized: runs `bao operator init` inside the pod and
# writes the init material to SECRETS_DIR/openbao/ (age/SOPS custody via
# encrypt-secrets.sh — never Git plaintext, never stdout);
# - if OpenBao is sealed: replays the SOPS-held unseal shares via stdin until
# the threshold is met;
# - verifies post-unseal state and emits a NON-SECRET JSON evidence line
# (flags only: openbao_initialized, openbao_post_unseal_verified).
#
# Post-unseal platform configuration stays owned by railiance-platform:
# make -C ../railiance-platform openbao-configure-initial
# It is invoked automatically only when OPENBAO_RUN_CONFIGURE_INITIAL=1.
#
# Environment:
# OPENBAO_NAMESPACE k8s namespace (default: openbao)
# OPENBAO_RELEASE helm release / pod prefix (default: openbao)
# OPENBAO_KEY_SHARES init key shares (default: 3)
# OPENBAO_KEY_THRESHOLD init key threshold (default: 2)
# OPENBAO_CONSOLE_METADATA console metadata file (default: <repo>/.local/security-bootstrap.json)
# OPENBAO_RUN_CONFIGURE_INITIAL 1 = run platform configure-initial after unseal
# RAILIANCE_PLATFORM_DIR path to railiance-platform checkout
# (default: sibling of this repo)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
DRY_RUN=false
SECRETS_DIR="$SCRIPT_DIR/secrets"
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=true ;;
*) SECRETS_DIR="$arg" ;;
esac
done
NAMESPACE="${OPENBAO_NAMESPACE:-openbao}"
RELEASE="${OPENBAO_RELEASE:-openbao}"
POD="${RELEASE}-0"
KEY_SHARES="${OPENBAO_KEY_SHARES:-3}"
KEY_THRESHOLD="${OPENBAO_KEY_THRESHOLD:-2}"
CONSOLE_METADATA="${OPENBAO_CONSOLE_METADATA:-$REPO_ROOT/.local/security-bootstrap.json}"
RAILIANCE_PLATFORM_DIR="${RAILIANCE_PLATFORM_DIR:-$(dirname "$REPO_ROOT")/railiance-platform}"
OPENBAO_SECRETS="$SECRETS_DIR/openbao"
INIT_FILE="$OPENBAO_SECRETS/init.json"
log() { echo " [openbao] $*"; }
die() { echo " [openbao] ERROR: $*" >&2; exit 1; }
evidence() {
# NON-SECRET evidence only: booleans, counts, model, namespace.
python3 - "$@" <<'PY'
import json, sys
keys = [a.split("=", 1) for a in sys.argv[1:]]
def coerce(v):
if v in ("true", "false"):
return v == "true"
try:
return int(v)
except ValueError:
return v
print("EVIDENCE " + json.dumps({k: coerce(v) for k, v in keys}, sort_keys=True))
PY
}
# ── Custody model gate ────────────────────────────────────────────────────────
# Only sops-held-automation may be automated. attended-ceremony and
# auto-unseal-transit must never reach `bao operator init/unseal` from here.
MODEL=""
PROFILE=""
if [[ -f "$CONSOLE_METADATA" ]]; then
MODEL=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("openbao_unseal_custody_model") or "")' "$CONSOLE_METADATA")
PROFILE=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("deployment_profile") or "")' "$CONSOLE_METADATA")
fi
if [[ "$PROFILE" == "production" ]]; then
die "deployment profile is 'production' — sops-held automation is blocked.
Use the attended ceremony (docs/openbao-attended-ceremony-runbook.md) or
auto-unseal-transit, or reselect: select-deployment-profile --profile lab"
fi
if [[ "$MODEL" != "sops-held-automation" ]]; then
die "unseal custody model is '${MODEL:-unselected}' — automation requires 'sops-held-automation'.
Select it first (see docs/openbao-unseal-custody-models.md):
python3 tools/security-bootstrap-console/security_bootstrap_console.py \\
select-openbao-unseal-custody-model --model sops-held-automation \\
--metadata $CONSOLE_METADATA"
fi
log "custody model gate passed: $MODEL"
command -v kubectl >/dev/null 2>&1 || die "kubectl not found"
command -v python3 >/dev/null 2>&1 || die "python3 not found"
kubectl get namespace "$NAMESPACE" >/dev/null 2>&1 \
|| die "namespace '$NAMESPACE' not found — deploy OpenBao first (railiance-platform: make openbao-deploy)"
kubectl -n "$NAMESPACE" get pod "$POD" >/dev/null 2>&1 \
|| die "pod '$POD' not found in namespace '$NAMESPACE'"
# ── Status probe ──────────────────────────────────────────────────────────────
# `bao status` exits 2 when sealed — capture output regardless of exit code.
bao_status() {
kubectl -n "$NAMESPACE" exec "$POD" -- bao status -format=json 2>/dev/null || true
}
STATUS_JSON="$(bao_status)"
[[ -n "$STATUS_JSON" ]] || die "could not read bao status from $NAMESPACE/$POD"
INITIALIZED=$(python3 -c 'import json,sys; print(str(json.loads(sys.stdin.read()).get("initialized", False)).lower())' <<<"$STATUS_JSON")
SEALED=$(python3 -c 'import json,sys; print(str(json.loads(sys.stdin.read()).get("sealed", True)).lower())' <<<"$STATUS_JSON")
log "status: initialized=$INITIALIZED sealed=$SEALED"
# ── Init (only when uninitialized) ────────────────────────────────────────────
DID_INIT=false
if [[ "$INITIALIZED" != "true" ]]; then
if [[ "$DRY_RUN" == true ]]; then
log "[dry-run] would run: bao operator init -key-shares=$KEY_SHARES -key-threshold=$KEY_THRESHOLD"
else
log "initializing OpenBao ($KEY_SHARES shares, threshold $KEY_THRESHOLD)..."
mkdir -p "$OPENBAO_SECRETS"
chmod 700 "$OPENBAO_SECRETS"
umask 077
kubectl -n "$NAMESPACE" exec "$POD" -- \
bao operator init -key-shares="$KEY_SHARES" -key-threshold="$KEY_THRESHOLD" -format=json \
> "$INIT_FILE"
chmod 600 "$INIT_FILE"
DID_INIT=true
log "init material written to $INIT_FILE (plaintext — encrypt + shred via encrypt-secrets.sh)"
fi
STATUS_JSON="$(bao_status)"
SEALED=$(python3 -c 'import json,sys; print(str(json.loads(sys.stdin.read()).get("sealed", True)).lower())' <<<"$STATUS_JSON")
fi
# ── Unseal (when sealed) ──────────────────────────────────────────────────────
if [[ "$SEALED" == "true" ]]; then
if [[ ! -f "$INIT_FILE" ]]; then
die "OpenBao is sealed but $INIT_FILE is missing.
Decrypt SOPS-held custody first: bash decrypt-secrets.sh $SECRETS_DIR"
fi
if [[ "$DRY_RUN" == true ]]; then
log "[dry-run] would replay $KEY_THRESHOLD unseal share(s) from $INIT_FILE via stdin"
else
SHARE_COUNT=$(python3 -c 'import json,sys; print(len(json.load(open(sys.argv[1])).get("unseal_keys_b64") or []))' "$INIT_FILE")
[[ "$SHARE_COUNT" -gt 0 ]] || die "no unseal shares found in $INIT_FILE"
log "replaying unseal shares (have $SHARE_COUNT)..."
for idx in $(seq 0 $((SHARE_COUNT - 1))); do
# Share travels stdin→stdin; never argv, never logs.
# `bao operator unseal -` does not read stdin (needs a TTY or the
# share in argv), so use the sys/unseal API with key=- instead.
python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["unseal_keys_b64"][int(sys.argv[2])])' "$INIT_FILE" "$idx" \
| kubectl -n "$NAMESPACE" exec -i "$POD" -- bao write sys/unseal key=- >/dev/null
STATUS_JSON="$(bao_status)"
SEALED=$(python3 -c 'import json,sys; print(str(json.loads(sys.stdin.read()).get("sealed", True)).lower())' <<<"$STATUS_JSON")
log "unseal share $((idx + 1)) applied (sealed=$SEALED)"
[[ "$SEALED" == "false" ]] && break
done
fi
fi
# ── Post-unseal verification ──────────────────────────────────────────────────
VERIFIED=false
if [[ "$DRY_RUN" == true ]]; then
log "[dry-run] would verify post-unseal status"
else
STATUS_JSON="$(bao_status)"
INITIALIZED=$(python3 -c 'import json,sys; print(str(json.loads(sys.stdin.read()).get("initialized", False)).lower())' <<<"$STATUS_JSON")
SEALED=$(python3 -c 'import json,sys; print(str(json.loads(sys.stdin.read()).get("sealed", True)).lower())' <<<"$STATUS_JSON")
if [[ "$INITIALIZED" == "true" && "$SEALED" == "false" ]]; then
VERIFIED=true
log "post-unseal verified: initialized, unsealed"
else
die "post-unseal verification failed: initialized=$INITIALIZED sealed=$SEALED"
fi
fi
evidence \
"custody_model=$MODEL" \
"namespace=$NAMESPACE" \
"pod=$POD" \
"openbao_initialized=$([[ "$INITIALIZED" == "true" || "$DID_INIT" == "true" ]] && echo true || echo false)" \
"openbao_did_init_this_run=$DID_INIT" \
"openbao_post_unseal_verified=$VERIFIED" \
"dry_run=$DRY_RUN"
# ── Platform handoff (railiance-platform owns post-unseal configuration) ─────
if [[ "${OPENBAO_RUN_CONFIGURE_INITIAL:-0}" == "1" && "$DRY_RUN" == false ]]; then
[[ -d "$RAILIANCE_PLATFORM_DIR" ]] || die "railiance-platform not found at $RAILIANCE_PLATFORM_DIR"
log "running railiance-platform post-unseal configuration..."
make -C "$RAILIANCE_PLATFORM_DIR" openbao-configure-initial
else
log "next (railiance-platform): make -C $RAILIANCE_PLATFORM_DIR openbao-configure-initial"
fi
if [[ "$DID_INIT" == true ]]; then
log "REMINDER: encrypt + shred the new init material now:"
log " bash $SCRIPT_DIR/encrypt-secrets.sh $SECRETS_DIR <age-recipient> && commit secrets.enc/"
fi

View File

@@ -73,7 +73,8 @@ ROLE_JSON
default_role="platform-admin"
bao write "auth/${mount}/role/platform-admin" @/tmp/openbao-platform-admin-role.json
printf "configured auth/%s/role/platform-admin\n" "$mount" >&2
bao write "sys/auth/${mount}/tune" listing_visibility=unauth
printf "configured auth/%s/role/platform-admin and listing_visibility=unauth\n" "$mount" >&2
done
rm -f /tmp/openbao-platform-admin-role.json /tmp/openbao-*-auth-enable.out /tmp/openbao-*-auth-enable.err

View File

@@ -64,30 +64,28 @@ OPENBAO_UNSEAL_CUSTODY_MODEL_SPECS: dict[str, dict[str, str]] = {
},
"attended-ceremony": {
"label": "Attended ceremony (production custody)",
"implementation": "planned",
"implementation": "implemented",
"summary": (
"Human-attended init, out-of-band unseal escrow, root retirement — "
"railiance-platform/docs/openbao.md ceremony."
),
"blocked_hint": (
"Not yet implemented in the automation path. Use sops-held-automation for "
"fast bootstrap test cycles; attended ceremony will gate production trust."
"railiance-platform/docs/openbao.md ceremony. Console never runs init; "
"runbook + evidence validation only."
),
"runbook_entry": "docs/openbao-attended-ceremony-runbook.md",
"custody_strength": "production",
},
"auto-unseal-transit": {
"label": "Auto-unseal (transit/KMS seal)",
"implementation": "planned",
"implementation": "implemented",
"summary": (
"Seal config uses transit or cloud KMS; pod restart without manual unseal."
),
"blocked_hint": (
"Not yet implemented. Requires railiance-platform Helm seal stanza and "
"KMS/transit provisioning. Use sops-held-automation until available."
"Seal config uses transit or cloud KMS; pod restart without manual unseal. "
"Gate stays blocked until the seal stanza is applied and auto-unseal is verified."
),
"config_entry": "railiance-platform/helm/openbao-values.yaml (seal stanza)",
"custody_strength": "production-ha",
},
}
VALID_DEPLOYMENT_PROFILES = ("lab", "production")
DEFAULT_DEPLOYMENT_PROFILE = "lab"
VALID_OPENBAO_UNSEAL_CUSTODY_MODELS = frozenset(OPENBAO_UNSEAL_CUSTODY_MODEL_SPECS)
DEFAULT_OPENBAO_UNSEAL_CUSTODY_MODEL = "sops-held-automation"
IMPLEMENTED_OPENBAO_UNSEAL_CUSTODY_MODELS = frozenset(
@@ -461,14 +459,38 @@ def openbao_unseal_custody_model_implemented(model: str) -> bool:
return model in IMPLEMENTED_OPENBAO_UNSEAL_CUSTODY_MODELS
def resolve_deployment_profile(data: dict[str, Any]) -> str:
profile = str(data.get("deployment_profile") or "").strip()
if profile in VALID_DEPLOYMENT_PROFILES:
return profile
return DEFAULT_DEPLOYMENT_PROFILE
def openbao_unseal_custody_model_entry(spec: dict[str, str]) -> str:
for key in ("automation_entry", "runbook_entry", "config_entry"):
if spec.get(key):
return spec[key]
return "n/a"
def openbao_unseal_custody_model_gate(data: dict[str, Any]) -> Gate:
model = resolve_openbao_unseal_custody_model(data)
spec = OPENBAO_UNSEAL_CUSTODY_MODEL_SPECS[model]
if resolve_deployment_profile(data) == "production" and model == "sops-held-automation":
return Gate(
"OpenBao unseal custody model",
"blocked",
(
"Production profile blocks sops-held-automation (root token and unseal "
"shares in one SOPS bundle is lab posture). Select attended-ceremony or "
"auto-unseal-transit."
),
)
if openbao_unseal_custody_model_implemented(model):
return Gate(
"OpenBao unseal custody model",
"done",
f"{spec['label']} selected — automation entry: {spec.get('automation_entry', 'n/a')}",
f"{spec['label']} selected — entry: {openbao_unseal_custody_model_entry(spec)}",
)
return Gate(
"OpenBao unseal custody model",
@@ -489,6 +511,12 @@ def openbao_init_ceremony_gate(data: dict[str, Any]) -> Gate:
spec.get("blocked_hint", "Selected unseal custody model is not implemented."),
)
if model == "sops-held-automation":
if resolve_deployment_profile(data) == "production":
return Gate(
"OpenBao init ceremony",
"blocked",
"Production profile blocks sops-held-automation. Select a production model.",
)
entry = OPENBAO_UNSEAL_CUSTODY_MODEL_SPECS[model].get("automation_entry", "")
return Gate(
"OpenBao init ceremony",
@@ -498,10 +526,44 @@ def openbao_init_ceremony_gate(data: dict[str, Any]) -> Gate:
f"({entry}). Console will not run init."
),
)
if model == "auto-unseal-transit":
if not yes(data, "openbao_transit_seal_configured"):
return Gate(
"OpenBao init ceremony",
"blocked",
(
"Transit/KMS seal not configured yet — enable the seal stanza in "
"railiance-platform/helm/openbao-values.yaml and provision the "
"transit/KMS backend, then set openbao_transit_seal_configured."
),
)
if not yes(data, "openbao_auto_unseal_verified"):
return Gate(
"OpenBao init ceremony",
"blocked",
(
"Auto-unseal not verified — restart the OpenBao pod, confirm it "
"unseals without shares, then set openbao_auto_unseal_verified."
),
)
return Gate(
"OpenBao init ceremony",
"human",
(
"Transit seal active. Attended `bao operator init` still required once "
"(recovery keys, root retirement) — follow "
"docs/openbao-attended-ceremony-runbook.md. Console will not run init."
),
)
runbook = OPENBAO_UNSEAL_CUSTODY_MODEL_SPECS[model].get("runbook_entry", "")
return Gate(
"OpenBao init ceremony",
"human",
"Human-attended ceremony only. This console will not run init.",
(
"Human-attended ceremony only. This console will not run init. "
f"Follow {runbook} and validate the non-secret record with "
"validate-openbao-ceremony-record."
),
)
@@ -740,12 +802,14 @@ def next_action(
if gate.name == "OpenBao preflight":
return "Run OpenBao preflight"
if gate.name == "OpenBao unseal custody model":
return (
"Select openbao-unseal-custody-model sops-held-automation "
"(other models not yet implemented)"
)
if data and resolve_deployment_profile(data) == "production":
return (
"Select a production unseal custody model "
"(attended-ceremony or auto-unseal-transit)"
)
return "Select an implemented openbao-unseal-custody-model"
if gate.name == "OpenBao init ceremony":
return "Select an implemented unseal custody model first"
return "Resolve the OpenBao init ceremony gate (see gate reason)"
if gate.name == "KeyCape OpenBao client definition":
return "Ship KeyCape OpenBao client definition"
if gate.name == "KeyCape OpenBao client deployed":
@@ -821,6 +885,9 @@ def print_status(data: dict[str, Any]) -> None:
print("17. select-openbao-unseal-custody-model")
print("18. web-ui")
print("19. validate-keycape-client (T08: example of validator-driven gate in UI state model)")
print("20. select-deployment-profile")
print("21. openbao-ceremony-record-template")
print("22. validate-openbao-ceremony-record")
print("")
print("Refusal boundary")
print("This console will not run bao operator init or collect secret values.")
@@ -1934,6 +2001,10 @@ def metadata_template() -> dict[str, Any]:
"progress_scope": "",
"openbao_unseal_custody_model": DEFAULT_OPENBAO_UNSEAL_CUSTODY_MODEL,
"openbao_unseal_custody_model_selected_at": "",
"deployment_profile": DEFAULT_DEPLOYMENT_PROFILE,
"deployment_profile_selected_at": "",
"openbao_transit_seal_configured": False,
"openbao_auto_unseal_verified": False,
"openbao_preflight_passed": False,
"openbao_init_output_produced": False,
"openbao_initialized": False,
@@ -2002,7 +2073,7 @@ def print_openbao_unseal_custody_models() -> int:
if status != "implemented":
print(f" blocked_hint: {spec.get('blocked_hint', '')}")
else:
print(f" automation_entry: {spec.get('automation_entry', '')}")
print(f" entry: {openbao_unseal_custody_model_entry(spec)}")
print("")
return 0
@@ -2031,6 +2102,18 @@ def print_select_openbao_unseal_custody_model(args: argparse.Namespace, data: di
file=sys.stderr,
)
return 2
if model == "sops-held-automation" and resolve_deployment_profile(data) == "production":
print("OPENBAO UNSEAL CUSTODY MODEL NOT SELECTABLE")
print("")
print(f"Model: {model} ({spec.get('label', '')})")
print("Deployment profile: production")
print("")
print(
"Production profile blocks sops-held-automation — root token and unseal "
"shares in one SOPS bundle is lab posture. Select attended-ceremony or "
"auto-unseal-transit, or switch back with: select-deployment-profile --profile lab"
)
return 1
merged = metadata_template()
merged.update(data)
merged["openbao_unseal_custody_model"] = model
@@ -2042,10 +2125,100 @@ def print_select_openbao_unseal_custody_model(args: argparse.Namespace, data: di
print(f"Metadata: {args.metadata}")
print(f"Model: {model}")
print(f"Label: {spec.get('label', '')}")
print(f"Automation entry: {spec.get('automation_entry', '')}")
print(f"Entry: {openbao_unseal_custody_model_entry(spec)}")
return 0
def print_select_deployment_profile(args: argparse.Namespace, data: dict[str, Any]) -> int:
profile = args.profile
if args.metadata is None:
print(
"ERROR: select-deployment-profile requires --metadata /path/to/non-secret.json",
file=sys.stderr,
)
return 2
merged = metadata_template()
merged.update(data)
merged["deployment_profile"] = profile
merged["deployment_profile_selected_at"] = utc_now()
merged["metadata_updated_at"] = utc_now()
write_metadata(args.metadata, merged)
print("DEPLOYMENT PROFILE SELECTED")
print("")
print(f"Metadata: {args.metadata}")
print(f"Profile: {profile}")
if profile == "production":
model = resolve_openbao_unseal_custody_model(merged)
print("")
print("Production profile blocks sops-held-automation for OpenBao init/unseal.")
if model == "sops-held-automation":
print(
f"WARNING: current unseal custody model is {model!r} — now blocked. "
"Select attended-ceremony or auto-unseal-transit."
)
return 0
def openbao_ceremony_record_template() -> dict[str, Any]:
return {
"record_version": "v1",
"evidence_date": "YYYY-MM-DD",
"operator": "openbao-ceremony-operator",
"runbook_reference": "docs/openbao-attended-ceremony-runbook.md",
"ceremony_scope": "Attended OpenBao operator init on <cluster>, unseal share escrow, root token retirement.",
"key_shares": 3,
"key_threshold": 2,
"unseal_share_escrow_disposition": "Describe where each share went (holder role + storage class) — never the shares themselves.",
"root_token_disposition": "revoked-or-escrowed: describe.",
"witness": "role or name of second attendee, or 'none' with justification",
"attended_init_completed": False,
"unseal_shares_escrowed_out_of_band": False,
"root_token_retired_or_escrowed": False,
"post_unseal_verified": False,
"no_secret_material_recorded": False,
}
def print_openbao_ceremony_record_template() -> None:
print(json.dumps(openbao_ceremony_record_template(), indent=2))
def print_validate_openbao_ceremony_record(args: argparse.Namespace) -> int:
evidence_path = resolve_cli_path(args.evidence)
evidence, errors = load_evidence_json(evidence_path, "openbao-ceremony")
if evidence is not None:
errors.extend(
require_evidence_fields(
evidence,
required_strings=(
"evidence_date",
"operator",
"runbook_reference",
"ceremony_scope",
"unseal_share_escrow_disposition",
"root_token_disposition",
"witness",
),
required_true=(
"attended_init_completed",
"unseal_shares_escrowed_out_of_band",
"root_token_retired_or_escrowed",
"post_unseal_verified",
"no_secret_material_recorded",
),
)
)
return print_validation_result(
"OPENBAO ATTENDED CEREMONY RECORD VALIDATION",
errors,
[
f"ceremony record valid: {evidence_path}",
"record is non-secret (no secret-looking markers found)",
"next: set openbao_initialized / openbao_post_unseal_verified in console metadata",
],
)
def gate_payload(gate: Gate) -> dict[str, str]:
return {
"name": gate.name,
@@ -4974,6 +5147,29 @@ def build_parser() -> argparse.ArgumentParser:
default=DEFAULT_OPENBAO_UNSEAL_CUSTODY_MODEL,
help="Unseal custody model id.",
)
select_profile = sub.add_parser(
"select-deployment-profile",
help="Select deployment profile (production blocks sops-held-automation).",
)
select_profile.add_argument(
"--profile",
choices=list(VALID_DEPLOYMENT_PROFILES),
required=True,
help="Deployment profile id.",
)
sub.add_parser(
"openbao-ceremony-record-template",
help="Print non-secret attended OpenBao ceremony record JSON template.",
)
validate_ceremony = sub.add_parser(
"validate-openbao-ceremony-record",
help="Validate a non-secret attended OpenBao ceremony record.",
)
validate_ceremony.add_argument(
"--evidence",
default=".local/openbao-ceremony-record.json",
help="Path to the non-secret ceremony record JSON.",
)
sub.add_parser("refuse-live-init", help="Explain why live OpenBao init is refused.")
web = sub.add_parser("web-ui", help="Serve a local custody approval UI.")
web.add_argument("--host", default="127.0.0.1", help="Bind host. Defaults to localhost.")
@@ -5002,6 +5198,7 @@ def main(argv: list[str] | None = None) -> int:
"validate-cleanup",
"approve-custody-mode",
"select-openbao-unseal-custody-model",
"select-deployment-profile",
"web-ui",
}
if args.command in metadata_commands and args.metadata is None:
@@ -5099,6 +5296,13 @@ def main(argv: list[str] | None = None) -> int:
return print_openbao_preflight(args)
if args.command == "openbao-unseal-custody-models":
return print_openbao_unseal_custody_models()
if args.command == "select-deployment-profile":
return print_select_deployment_profile(args, data)
if args.command == "openbao-ceremony-record-template":
print_openbao_ceremony_record_template()
return 0
if args.command == "validate-openbao-ceremony-record":
return print_validate_openbao_ceremony_record(args)
if args.command == "select-openbao-unseal-custody-model":
return print_select_openbao_unseal_custody_model(args, data)
if args.command == "web-ui":

View File

@@ -36,15 +36,82 @@ def test_openbao_unseal_custody_model_gate_automation_default():
assert init_gate.status == "automation"
def test_openbao_unseal_custody_planned_models_blocked():
def test_openbao_unseal_custody_production_models_selectable():
for model in ("attended-ceremony", "auto-unseal-transit"):
data = console.metadata_template()
data["openbao_unseal_custody_model"] = model
gate = console.openbao_unseal_custody_model_gate(data)
assert gate.status == "blocked"
assert "not yet implemented" in gate.reason.lower()
init_gate = console.openbao_init_ceremony_gate(data)
assert init_gate.status == "blocked"
assert gate.status == "done"
def test_attended_ceremony_init_gate_is_human_with_runbook():
data = console.metadata_template()
data["openbao_unseal_custody_model"] = "attended-ceremony"
init_gate = console.openbao_init_ceremony_gate(data)
assert init_gate.status == "human"
assert "openbao-attended-ceremony-runbook" in init_gate.reason
def test_auto_unseal_transit_init_gate_requires_evidence():
data = console.metadata_template()
data["openbao_unseal_custody_model"] = "auto-unseal-transit"
gate = console.openbao_init_ceremony_gate(data)
assert gate.status == "blocked"
assert "openbao_transit_seal_configured" in gate.reason
data["openbao_transit_seal_configured"] = True
gate = console.openbao_init_ceremony_gate(data)
assert gate.status == "blocked"
assert "openbao_auto_unseal_verified" in gate.reason
data["openbao_auto_unseal_verified"] = True
gate = console.openbao_init_ceremony_gate(data)
assert gate.status == "human"
def test_production_profile_blocks_sops_held_automation():
data = console.metadata_template()
data["deployment_profile"] = "production"
gate = console.openbao_unseal_custody_model_gate(data)
assert gate.status == "blocked"
assert "production profile" in gate.reason.lower()
init_gate = console.openbao_init_ceremony_gate(data)
assert init_gate.status == "blocked"
data["openbao_unseal_custody_model"] = "attended-ceremony"
assert console.openbao_unseal_custody_model_gate(data).status == "done"
def test_openbao_ceremony_record_template_and_validation(tmp_path):
tmpl = console.openbao_ceremony_record_template()
for key in (
"attended_init_completed",
"unseal_shares_escrowed_out_of_band",
"root_token_retired_or_escrowed",
"post_unseal_verified",
"no_secret_material_recorded",
):
assert key in tmpl
# a filled-in, non-secret record validates cleanly
record = dict(tmpl)
record.update(
evidence_date="2026-07-02",
ceremony_scope="Attended init on Railiance ThreePhoenix.",
unseal_share_escrow_disposition="Shares to roster holders, offline packets.",
root_token_disposition="revoked after configure-initial",
witness="recovery-custodian",
attended_init_completed=True,
unseal_shares_escrowed_out_of_band=True,
root_token_retired_or_escrowed=True,
post_unseal_verified=True,
no_secret_material_recorded=True,
)
path = tmp_path / "record.json"
path.write_text(json.dumps(record))
_, errors = console.load_evidence_json(path, "openbao-ceremony")
assert errors == []
# a record leaking a token marker is refused
record["root_token_disposition"] = "hvs.deadbeef"
path.write_text(json.dumps(record))
_, errors = console.load_evidence_json(path, "openbao-ceremony")
assert any("secret-looking" in e for e in errors)
def test_onboarding_dry_run_template_has_required_fields():
tmpl = console.onboarding_dry_run_template()

View File

@@ -0,0 +1,50 @@
---
id: adhoc-2026-07-02
type: workplan
title: "Ad Hoc Tasks — 2026-07-02"
domain: infotech
repo: net-kingdom
status: finished
owner: codex
topic_slug: net-kingdom
created: "2026-07-02"
updated: "2026-07-02"
state_hub_workstream_id: "67c1c7ac-d8b1-41dc-a81a-4e43f1afd068"
---
# Ad Hoc Tasks — 2026-07-02
## Fix creds-bootstrap-agent Phase 0 dry-run on machines without the age key
```task
id: ADHOC-2026-07-02-T01
status: done
priority: low
state_hub_task_id: "b86bf898-7916-4db3-ba67-ba3a3fd8a49f"
```
`--dry-run` previously aborted silently in Phase 0 on any machine without
`~/.config/sops/age/keys.txt`: key generation is correctly skipped in dry-run,
but the subsequent public-key read (`grep` on the missing file) killed the
script under `set -e`, so no later phase could be exercised.
Fix: when the key file is absent in dry-run, continue with a placeholder
recipient and a clear notice instead of dying; live runs without a key still
fail hard. Verified: full `--dry-run` now traverses Phase 0 through Phase 10
including the new Phase 7b OpenBao hook (NET-WP-0020-T02) on a machine with
no age key.
## Fix broken check-secrets Make target (unescaped `$`)
```task
id: ADHOC-2026-07-02-T02
status: done
priority: medium
```
`make check-secrets` failed with a bash parse error ("unexpected EOF while
looking for matching `'`"): the trailing `grep -v '/$'` used a single `$`,
which make expanded before bash saw it. Escaped to `$$`. Verified:
`make check-secrets` passes again ("All secrets/ files appear SOPS-encrypted").
Pre-existing bug, unrelated to NET-WP-0020; found while running the final
checks for that workplan.

View File

@@ -2,13 +2,14 @@
id: NET-WP-0020
type: workplan
title: "OpenBao Unseal Custody Models and SSH Automation Path"
domain: net-kingdom
domain: infotech
repo: net-kingdom
status: active
status: finished
owner: codex
topic_slug: net-kingdom
created: "2026-06-17"
updated: "2026-06-18"
updated: "2026-07-02"
state_hub_workstream_id: "d6338ac9-797d-4009-8203-4b8dd39010af"
---
# NET-WP-0020 — OpenBao Unseal Custody Models and SSH Automation Path
@@ -31,6 +32,7 @@ production trust increases.
id: NET-WP-0020-T01
status: done
priority: high
state_hub_task_id: "7040f347-d54a-42ba-a14f-5b0a7e691786"
```
- [x] `docs/openbao-unseal-custody-models.md`
@@ -42,37 +44,86 @@ priority: high
```task
id: NET-WP-0020-T02
status: todo
status: done
priority: high
state_hub_task_id: "65407eb1-9d89-4158-aed5-4987badd83fc"
```
- [ ] Extend `creds-bootstrap-agent.sh` for OpenBao init/unseal when sealed
- [ ] Non-secret evidence flags: `openbao_initialized`, `openbao_post_unseal_verified`
- [ ] Integrate with `make openbao-configure-initial` post-unseal
- [x] SOPS-held init/unseal automation: `sso-mfa/bootstrap/openbao-init-unseal.sh`
(`make openbao-init-unseal` / `openbao-init-unseal-dry-run`)
- [x] Non-secret evidence flags: `openbao_initialized`, `openbao_post_unseal_verified`
(emitted on the script's `EVIDENCE` JSON line)
- [x] Integrate with `make openbao-configure-initial` post-unseal
(`OPENBAO_RUN_CONFIGURE_INITIAL=1` chains it; default prints the handoff hint)
- [x] Wire the helper as an optional phase inside `creds-bootstrap-agent.sh`
(Phase 7b, reviewed and approved by Bernd 2026-07-02: runs only when the
`openbao` namespace exists, skips when already verified, sets the two
`creds-state.yaml` flags, encrypts + commits new init material, and a
custody-gate refusal warns without aborting the SSO/MFA bootstrap —
dry-run/skip/refusal paths harness-tested)
- [x] Greenfield live proof: full init→unseal→verify path proven 2026-07-02
against a genuinely uninitialized local OpenBao 2.5.5 (kubectl-exec shim,
script logic unmodified) — see
`history/2026-07-02-openbao-greenfield-init-unseal-proof.md`. The proof
caught and fixed a real bug: `bao operator unseal -` does not read stdin;
now `bao write sys/unseal key=-` (shares still never in argv/logs). Restart/
reseal replay path proven too. The first 3-node rebuild slate re-runs the
same script via Phase 7b.
**2026-07-02 (later):** Bernd reviewed the helper design (five safety
properties incl. the root-token-in-bundle caveat of the sops-held model) and
approved the Phase 7b wiring as proposed. Applied, `bash -n` clean, all three
conditional paths verified by harness. Pre-existing note: the agent's Phase 0
cannot dry-run on machines without the age key — unrelated to this change.
Remaining T02 item is only the greenfield live proof.
**2026-07-02:** Helper implemented and smoke-tested: dry-run against the live
cluster passed the custody gate (`sops-held-automation` selected) and read
`initialized=true sealed=false`; negative tests proved refusal for unselected
and attended-ceremony models. Init material is written only into
`secrets/openbao/` for age custody; unseal shares travel stdin-to-stdin and
never appear in argv or logs.
### T3 — Attended ceremony automation profile
```task
id: NET-WP-0020-T03
status: wait
status: done
priority: medium
state_hub_task_id: "34f3d979-a040-49ca-bfcb-35cf17473a06"
```
- [ ] Implement `attended-ceremony` selection path (runbooks + evidence validators)
- [ ] Production profile blocks `sops-held-automation` default
- [x] Implement `attended-ceremony` selection path (runbooks + evidence validators)
- [x] Production profile blocks `sops-held-automation` default
**Blocked until:** T2 automation path proven on greenfield rebuild.
**2026-07-02:** Model selectable in the console; runbook at
`docs/openbao-attended-ceremony-runbook.md`; non-secret ceremony record
template + `validate-openbao-ceremony-record` validator (refuses secret
markers/placeholders). New `deployment_profile` metadata (`lab`/`production`,
`select-deployment-profile`): production blocks `sops-held-automation` in
selection, status gates, and `openbao-init-unseal.sh`. Console refuse-live-init
boundary unchanged. Covered by console test suite (14 passing) + CLI smoke.
### T4 — Auto-unseal transit profile
```task
id: NET-WP-0020-T04
status: wait
status: done
priority: medium
state_hub_task_id: "54ab6505-c13b-4f63-8c94-07dd202de90a"
```
- [ ] `railiance-platform` Helm seal stanza for transit/KMS
- [ ] Console gate + evidence for `auto-unseal-transit`
- [x] `railiance-platform` Helm seal stanza for transit/KMS
- [x] Console gate + evidence for `auto-unseal-transit`
**2026-07-02:** Commented-out `seal "transit"` stanza (disabled by default;
token via `extraSecretEnvironmentVars`, never Git) in
`railiance-platform/helm/openbao-values.yaml` plus an "Auto-Unseal via Transit
Seal" section in `railiance-platform/docs/openbao.md` (enable → `-migrate`
pod-restart proof). Console: model selectable; init gate stays blocked until
`openbao_transit_seal_configured` and `openbao_auto_unseal_verified` are set.
Live transit/KMS provisioning is future ops work on the HA rebuild, gated by
that evidence.
### T5 — SSH engine + host CA automation (cross-repo)
@@ -80,6 +131,7 @@ priority: medium
id: NET-WP-0020-T05
status: done
priority: high
state_hub_task_id: "399e82ca-6551-4020-8db5-c78076e75cfc"
```
- [x] `railiance-platform`: `openbao-configure-ssh` declarative script + Makefile targets

View File

@@ -2,15 +2,15 @@
id: NK-WP-0009
type: workplan
title: NetKingdom Security Pattern Tutorials
domain: netkingdom
domain: infotech
repo: net-kingdom
status: proposed
status: backlog
owner: codex
topic_slug: netkingdom
planning_priority: medium
planning_order: 9
created: 2026-05-17
updated: 2026-05-17
updated: 2026-07-08
depends_on:
- NK-WP-0008
state_hub_workstream_id: "66c9f1e9-6b2f-454b-a6d4-04e5fe42385a"

View File

@@ -2,13 +2,13 @@
id: NK-WP-0011
type: workplan
title: "Enterprise Federation & SAML — Expanded-Mode Keycloak Identity Broker"
domain: netkingdom
domain: infotech
repo: net-kingdom
status: proposed
status: backlog
owner: worsch
topic_slug: netkingdom
created: "2026-05-20"
updated: "2026-05-20"
updated: "2026-07-08"
state_hub_workstream_id: a44beef8-c18b-4ae7-b7fe-a178cc4fcdf0
depends_on:
- NK-WP-0003
@@ -18,8 +18,7 @@ supersedes_tasks:
- NK-WP-0001-T05
- NK-WP-0001-T06
- NK-WP-0001-T07
- NK-WP-0001-T08
---
- NK-WP-0001-T08---
# NK-WP-0011 — Enterprise Federation & SAML (Expanded-Mode Keycloak)

View File

@@ -9,6 +9,7 @@ owner: codex
topic_slug: netkingdom
created: "2026-06-14"
updated: "2026-06-14"
state_hub_workstream_id: "c6f3d6bf-4916-490d-96ce-092d2776d7e7"
---
# Ad hoc NetKingdom operator usability fixes
@@ -19,6 +20,7 @@ updated: "2026-06-14"
id: ADHOC-2026-06-14-T01
status: done
priority: medium
state_hub_task_id: "bb5973d8-8e61-48f6-b627-a662f8a34ad1"
```
Added a custody unlock helper for SOPS/age operations so drills and incident

View File

@@ -2,7 +2,7 @@
id: NET-WP-0017
type: workplan
title: "IT Security Readiness For User Onboarding"
domain: netkingdom
domain: infotech
repo: net-kingdom
status: finished
owner: codex

View File

@@ -2,7 +2,7 @@
id: NET-WP-0019
type: workplan
title: "T06-adjacent Polish: Non-Root User Lifecycle Dry-Run Automation And Control Surface Improvements"
domain: netkingdom
domain: infotech
repo: net-kingdom
status: finished
owner: codex
@@ -14,8 +14,7 @@ depends_on:
- NET-WP-0018
state_hub_workstream_id: "75d388b6-7ec1-4e1b-8c87-6ff44f953210"
related:
- docs/user-engine-netkingdom-integration-assessment.md (broader user-engine vs net-kingdom fit, gaps, and recommendations)
---
- docs/user-engine-netkingdom-integration-assessment.md (broader user-engine vs net-kingdom fit, gaps, and recommendations)---
# NET-WP-0019 - T06-adjacent Polish: Non-Root User Lifecycle Dry-Run Automation And Control Surface Improvements

View File

@@ -2,7 +2,7 @@
id: NK-WP-0001
type: workplan
title: "SSO & MFA Platform — Keycloak + privacyIDEA on Kubernetes"
domain: netkingdom
domain: infotech
status: archived
owner: worsch
topic_slug: netkingdom
@@ -148,7 +148,6 @@ systems that do not connect to the cluster Vault.
deployed in-cluster, secrets migrated, ESO operational and injecting secrets
into at least one test workload (0b). Encrypted ops bundle exported and
stored offsite.
---
### T02 — Phase 1: K8s foundations (namespaces, NetworkPolicies, cert-manager)
@@ -214,7 +213,7 @@ restore drill passed.
```task
id: NK-WP-0001-T04
state_hub_task_id: 6ad1296a-a488-4031-b665-f77030e971ed
status: cancelled
status: cancel
priority: high
note: Cancelled 2026-05-20. privacyIDEA deployment superseded by NK-WP-0003-T04 (privacyIDEA now runs in the live KeyCape stack on RAILIANCE01). This Keycloak-path variant is no longer pursued.
```
@@ -262,7 +261,7 @@ pi-admin enrolled with MFA, trigger-admin created, rate-limiting active.
```task
id: NK-WP-0001-T05
state_hub_task_id: b9f73aa6-9035-4643-9905-64e73a29b298
status: cancelled
status: cancel
priority: high
note: Migrated to NK-WP-0011 (enterprise federation / SAML). Refined there against the deployed KeyCape stack and the OpenBao/flex-auth architecture.
```
@@ -296,7 +295,7 @@ custom image with privacyIDEA JAR deployed and verified.
```task
id: NK-WP-0001-T06
state_hub_task_id: 3b6379a4-a27b-4d25-82be-bc600879f036
status: cancelled
status: cancel
priority: medium
note: Migrated to NK-WP-0011 (enterprise federation / SAML).
```
@@ -330,7 +329,7 @@ modes handled gracefully.
```task
id: NK-WP-0001-T07
state_hub_task_id: c7cf902a-b480-4545-a536-293070945206
status: cancelled
status: cancel
priority: medium
note: Migrated to NK-WP-0011 (enterprise federation / SAML).
```
@@ -373,7 +372,7 @@ audit logs flowing, Keycloak resolver configured.
```task
id: NK-WP-0001-T08
state_hub_task_id: 9cbd1d89-b5bf-491e-9d16-b1c7d57076fb
status: cancelled
status: cancel
priority: medium
note: Migrated to NK-WP-0011 (enterprise federation / SAML).
```

View File

@@ -2,8 +2,8 @@
id: NK-WP-0002
type: workplan
title: "Local Identity — Bootstrap User Store & Minimal OIDC"
domain: netkingdom
status: completed
domain: infotech
status: finished
owner: worsch
topic_slug: netkingdom
state_hub_workstream_id: 7c9021b1-319c-4b4a-a8be-0642239a1893

View File

@@ -2,9 +2,9 @@
id: NK-WP-0006
type: workplan
title: Recursive platform identity and security architecture
domain: netkingdom
domain: infotech
repo: net-kingdom
status: done
status: finished
owner: Bernd Worsch
topic_slug: netkingdom
created: 2026-05-17

View File

@@ -2,9 +2,9 @@
id: NK-WP-0007
type: workplan
title: Object Storage STS Credential Vending
domain: netkingdom
domain: infotech
repo: net-kingdom
status: done
status: finished
owner: codex
topic_slug: netkingdom
planning_priority: high

View File

@@ -2,7 +2,7 @@
id: NK-WP-0008
type: workplan
title: IT Security Architecture Patterns Infospace
domain: netkingdom
domain: infotech
repo: net-kingdom
status: done
owner: codex
@@ -15,8 +15,7 @@ depends_on:
- NK-WP-0006
state_hub_workstream_id: "053c6d96-9396-40c9-a2e5-c36531e7810d"
execution_repo: infospace-bench
infospace_path: infospaces/patterns-of-it-securita-architecture
---
infospace_path: infospaces/patterns-of-it-securita-architecture---
# NK-WP-0008 - IT Security Architecture Patterns Infospace

View File

@@ -2,9 +2,9 @@
id: NK-WP-0010
type: workplan
title: Genesis Security Pattern Completion
domain: netkingdom
domain: infotech
repo: net-kingdom
status: done
status: finished
owner: codex
topic_slug: netkingdom
planning_priority: medium

View File

@@ -2,7 +2,7 @@
id: NK-WP-0012
type: workplan
title: "NetKingdom IAM Profile Specification"
domain: netkingdom
domain: infotech
repo: net-kingdom
status: finished
owner: worsch
@@ -15,8 +15,7 @@ depends_on:
- NK-WP-0006
state_hub_workstream_id: 9b8e4afc-eb71-47d9-8750-799a082b320a
enables:
- NK-WP-0011
---
- NK-WP-0011---
# NK-WP-0012 — NetKingdom IAM Profile Specification

View File

@@ -2,7 +2,7 @@
id: NK-WP-0013
type: workplan
title: "Playbook Capability Contract"
domain: netkingdom
domain: infotech
repo: net-kingdom
status: finished
owner: worsch

View File

@@ -2,7 +2,7 @@
id: NK-WP-0014
type: workplan
title: "User Engine Preparation And Boundary Contracts"
domain: netkingdom
domain: infotech
repo: net-kingdom
status: finished
owner: codex