generated from coulomb/repo-seed
Compare commits
22 Commits
efbdab4652
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d34c6891b3 | ||
| db12130c22 | |||
|
|
1b5fab9bcd | ||
|
|
02cb63c8ab | ||
| 9767f7230f | |||
| d2c7473fb2 | |||
| 85a781b7a4 | |||
| 60b9d7037d | |||
| 951ba07c30 | |||
| 67b4677cea | |||
| abde6b1fd4 | |||
| 60142241a3 | |||
| 764c3cfd6d | |||
| a770a6a11d | |||
| 9a7d10f840 | |||
| 93e465525f | |||
| 30c647ff5b | |||
| 53c015b97b | |||
| 366574a7b1 | |||
| 3764ea03ed | |||
| 3875d546bc | |||
| 2056eee862 |
50
.claude/rules/credential-routing.md
Normal file
50
.claude/rules/credential-routing.md
Normal 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`
|
||||||
@@ -1,37 +1,41 @@
|
|||||||
## First Session Protocol
|
## 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.
|
The project is registered but work has not yet been structured.
|
||||||
|
|
||||||
**Step 1 — Read, don't write**
|
**Step 1 — Read, don't write**
|
||||||
- `~/the-custodian/canon/projects/netkingdom/project_charter_v0.1.md` — purpose, scope
|
- `~/the-custodian/canon/projects/infotech/project_charter_v0.1.md` — purpose, scope
|
||||||
- `~/the-custodian/canon/projects/netkingdom/roadmap_v0.1.md` — planned phases
|
- `~/the-custodian/canon/projects/infotech/roadmap_v0.1.md` — planned phases
|
||||||
- Scan repo root: README, directory structure, existing code or docs
|
- Scan repo root: README, directory structure, existing code or docs
|
||||||
|
|
||||||
**Step 2 — Survey in-progress work**
|
**Step 2 — Survey in-progress work**
|
||||||
Look for TODOs, open branches, half-finished files. Note done vs. started but incomplete.
|
Look for TODOs, open branches, half-finished files. Note done vs. started but incomplete.
|
||||||
|
|
||||||
**Step 3 — Propose workstreams to Bernd**
|
**Step 3 — Propose workplans to Bernd**
|
||||||
Propose 1–3 workstreams — each a coherent strand, weeks to months, anchored to a
|
Propose 1–3 workplans — each a coherent strand, weeks to months, anchored to a
|
||||||
roadmap phase. **Wait for approval before creating.**
|
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:
|
Then register by running the consistency check — do **not** call
|
||||||
```
|
`create_workplan`/`create_task` (or legacy `create_workstream`) yourself;
|
||||||
create_workstream(topic_id="a6c6e745-bf54-4465-9340-1534a2be493e", title="...", owner="...", description="...")
|
manual registration duplicates what C-06 creates from the file:
|
||||||
create_task(workstream_id="<id>", title="...", priority="high|medium|low")
|
```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**
|
**Step 5 — Record the setup**
|
||||||
```
|
```
|
||||||
add_progress_event(
|
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",
|
event_type="milestone",
|
||||||
topic_id="a6c6e745-bf54-4465-9340-1534a2be493e",
|
topic_id="cee7bedf-2b48-46ef-8601-006474f2ad7a",
|
||||||
detail={"workstreams": [...], "tasks_created": M}
|
detail={"workplans": [...], "tasks_created": M}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
**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
|
**Repo slug:** net-kingdom
|
||||||
**Topic ID:** a6c6e745-bf54-4465-9340-1534a2be493e
|
**Topic ID:** cee7bedf-2b48-46ef-8601-006474f2ad7a
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
## Session Protocol
|
## 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**
|
**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:
|
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:
|
If MCP tools are unavailable in the current agent session, use the REST API:
|
||||||
```bash
|
```bash
|
||||||
@@ -39,11 +40,11 @@ curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
|
|||||||
ls workplans/
|
ls workplans/
|
||||||
```
|
```
|
||||||
For each file with `status: ready`, `active`, or `blocked`, note pending
|
For each file with `status: ready`, `active`, or `blocked`, note pending
|
||||||
`todo`/`in_progress` tasks.
|
`wait`/`todo`/`progress` tasks.
|
||||||
|
|
||||||
**Step 4 — Present brief**
|
**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
|
2. **Pending tasks** from `workplans/` + any `[repo:net-kingdom]` hub tasks
|
||||||
3. **Goal guidance** — if `goal_guidance` in summary:
|
3. **Goal guidance** — if `goal_guidance` in summary:
|
||||||
- `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"*
|
- `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
|
4. **Suggested next action** — highest-priority open item
|
||||||
5. **SBOM status** — flag if `last_sbom_at` is unset for this repo
|
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()`
|
**During work:** `record_decision()` · `add_progress_event()` · `resolve_decision()`
|
||||||
|
|
||||||
> State Hub is a *read model*. Bootstrap tools (`create_workstream`, `create_task`)
|
> State Hub is a *read model*. **Never register workplans or tasks by hand**
|
||||||
> are First Session Protocol only. Work structure belongs in repo files (ADR-001).
|
> (`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:**
|
**Session close:**
|
||||||
With MCP tools:
|
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:
|
Without MCP tools:
|
||||||
```bash
|
```bash
|
||||||
curl -s -X POST http://127.0.0.1:8000/progress/ \
|
curl -s -X POST http://127.0.0.1:8000/progress/ \
|
||||||
-H "Content-Type: application/json" \
|
-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
|
```bash
|
||||||
git -C <repo_path> pull --ff-only
|
git pull --ff-only
|
||||||
cd ~/state-hub && make fix-consistency REPO=net-kingdom
|
statehub fix-consistency
|
||||||
```
|
```
|
||||||
For repos where implementation runs on a remote machine (e.g. CoulombCore),
|
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
|
```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
|
**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
|
will sync the file to match DB. **C-16** (repo behind remote) blocks all writes
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
<!-- TODO: Fill in language, frameworks, and key dependencies -->
|
- **Language:** Kubernetes manifests, Bash Make targets, SOPS-encrypted secret custody
|
||||||
- **Language:**
|
- **Key deps:** Keycloak (SSO/MFA), age/SOPS, KeePassXC-based credential custody, repo-local git hooks
|
||||||
- **Key deps:**
|
|
||||||
|
|
||||||
## Dev Commands
|
## Dev Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# TODO: Fill in the standard commands for this repo
|
make help # list all targets
|
||||||
|
make hooks && make hooks-test # secrets-guard git hooks
|
||||||
# Install dependencies
|
make check-secrets # fail if anything under secrets/ is unencrypted
|
||||||
|
make sops-edit FILE=secrets/foo.yaml # edit encrypted file
|
||||||
# Run tests
|
make sops-custody-check # validate custody age key without writing to disk
|
||||||
|
make sops-custody-run COMMAND='...' # run one command with temporary custody key
|
||||||
# Lint / type check
|
make creds-init # one-time credential custody setup
|
||||||
|
make creds-generate # generate service secrets + KeePassXC guide
|
||||||
# Build / package (if applicable)
|
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.
|
||||||
|
|||||||
@@ -1,28 +1,45 @@
|
|||||||
## Workplan Convention (ADR-001)
|
## Workplan Convention (ADR-001)
|
||||||
|
|
||||||
File location: `workplans/net-kingdom-WP-NNNN-<slug>.md`
|
File location: `workplans/NK-WP-NNNN-<slug>.md`
|
||||||
ID prefix: `NET-WP`
|
ID prefix: `NK-WP-`
|
||||||
|
|
||||||
Work items originate as files in this repo **before** being registered in the hub.
|
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`.
|
`proposed`, `ready`, `active`, `blocked`, `backlog`, `finished`, `archived`.
|
||||||
Use `proposed` for a newly drafted plan, `ready` after review against current
|
Use `proposed` for a newly drafted plan, `ready` after review against current
|
||||||
repo state, and `finished` when implementation is complete. `stalled` and
|
repo state, and `finished` when implementation is complete. `stalled` and
|
||||||
`needs_review` are derived health labels, not stored statuses.
|
`needs_review` are derived health labels, not stored statuses.
|
||||||
|
|
||||||
Closed workplans may be moved to `workplans/archived/` with a completion-date
|
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.
|
unchanged; the prefix is only for quick visual reference.
|
||||||
|
|
||||||
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
|
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
|
`ADHOC-YYYY-MM-DD-T01`, `T02`, etc. Use adhocs only for low-risk work completed
|
||||||
directly. Promote anything requiring analysis, design, approval, dependencies, or
|
directly. Promote anything requiring analysis, design, approval, dependencies, or
|
||||||
multiple planned phases into a normal workplan.
|
multiple planned phases into a normal workplan.
|
||||||
|
|
||||||
Ecosystem todos from other agents arrive as `[repo:net-kingdom]` hub tasks —
|
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
|
visible at session start. Pick one up by creating the workplan file, committing,
|
||||||
the workstream.
|
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 -->
|
<!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here -->
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
|
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
|
||||||
# Custodian Brief — net-kingdom
|
# Custodian Brief — net-kingdom
|
||||||
|
|
||||||
**Domain:** netkingdom
|
**Domain:** communication
|
||||||
**Last synced:** 2026-06-05 07:48 UTC
|
**Last synced:** 2026-07-08 10:33 UTC
|
||||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||||
|
|
||||||
## Active Workstreams
|
## Active Workstreams
|
||||||
|
|
||||||
*(none — repo may need first-session setup)*
|
*(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)
|
## MCP Orientation (when available)
|
||||||
|
|
||||||
If the state-hub MCP server is reachable, call:
|
If the state-hub MCP server is reachable, call:
|
||||||
`get_domain_summary("netkingdom")`
|
`get_domain_summary("communication")`
|
||||||
This provides richer cross-domain context.
|
This provides richer cross-domain context.
|
||||||
If the MCP call fails, use this file as your orientation source.
|
If the MCP call fails, use this file as your orientation source.
|
||||||
|
|||||||
24
.repo-classification.yaml
Normal file
24
.repo-classification.yaml
Normal 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.
|
||||||
99
AGENTS.md
99
AGENTS.md
@@ -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.
|
**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
|
**Repo slug:** net-kingdom
|
||||||
**Topic ID:** `a6c6e745-bf54-4465-9340-1534a2be493e`
|
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
|
||||||
**Workplan prefix:** `NET-WP-`
|
**Workplan prefix:** `NK-WP-`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -20,6 +20,12 @@ there is no MCP server for Codex agents.
|
|||||||
|---------|-----|
|
|---------|-----|
|
||||||
| Local workstation | `http://127.0.0.1:8000` |
|
| Local workstation | `http://127.0.0.1:8000` |
|
||||||
| Remote via tunnel | `http://127.0.0.1:18000` |
|
| 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
|
### Orient at session start
|
||||||
|
|
||||||
@@ -27,8 +33,8 @@ there is no MCP server for Codex agents.
|
|||||||
# Offline brief — works without hub connection
|
# Offline brief — works without hub connection
|
||||||
cat .custodian-brief.md
|
cat .custodian-brief.md
|
||||||
|
|
||||||
# Active workstreams for this domain
|
# Active workplans for this domain
|
||||||
curl -s "http://127.0.0.1:8000/workstreams/?topic_id=a6c6e745-bf54-4465-9340-1534a2be493e&status=active" \
|
curl -s "http://127.0.0.1:8000/workplans/?topic_id=cee7bedf-2b48-46ef-8601-006474f2ad7a&status=active" \
|
||||||
| python3 -m json.tool
|
| python3 -m json.tool
|
||||||
|
|
||||||
# Check inbox
|
# Check inbox
|
||||||
@@ -51,20 +57,20 @@ curl -s -X POST http://127.0.0.1:8000/progress/ \
|
|||||||
"summary": "what was done",
|
"summary": "what was done",
|
||||||
"event_type": "note",
|
"event_type": "note",
|
||||||
"author": "codex",
|
"author": "codex",
|
||||||
"workstream_id": "<uuid>",
|
"workplan_id": "<uuid>",
|
||||||
"task_id": "<uuid>"
|
"task_id": "<uuid>"
|
||||||
}'
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
Omit `workstream_id` / `task_id` when not applicable.
|
Omit `workplan_id` / `task_id` when not applicable.
|
||||||
|
|
||||||
### Update task status
|
### Update task status
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"status": "in_progress"}'
|
-d '{"status": "progress"}'
|
||||||
# values: todo | in_progress | done | blocked
|
# values: wait | todo | progress | done | cancel
|
||||||
```
|
```
|
||||||
|
|
||||||
### Flag a task for human review
|
### 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
|
## Session Protocol
|
||||||
|
|
||||||
**Start:**
|
**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
|
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
|
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:**
|
**During work:**
|
||||||
- Update task statuses in workplan files as tasks progress
|
- 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:**
|
**Close:**
|
||||||
1. Update workplan file task statuses to reflect progress
|
1. Update workplan file task statuses to reflect progress
|
||||||
2. Log: `POST /progress/` with a summary of what changed
|
2. Log: `POST /progress/` with a summary of what changed
|
||||||
3. Note for the custodian operator: after workplan file changes, run from
|
3. After workplan file changes, run:
|
||||||
`~/state-hub`:
|
|
||||||
```bash
|
```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
|
id: NET-WP-NNNN
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "..."
|
title: "..."
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
repo: net-kingdom
|
repo: net-kingdom
|
||||||
status: proposed | ready | active | blocked | backlog | finished | archived
|
status: proposed | ready | active | blocked | backlog | finished | archived
|
||||||
owner: codex
|
owner: codex
|
||||||
@@ -146,7 +209,7 @@ derived health labels, not frontmatter statuses.
|
|||||||
|
|
||||||
` ` `task
|
` ` `task
|
||||||
id: NET-WP-NNNN-T01
|
id: NET-WP-NNNN-T01
|
||||||
status: todo | in_progress | done | blocked
|
status: wait | todo | progress | done | cancel
|
||||||
priority: high | medium | low
|
priority: high | medium | low
|
||||||
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
|
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.
|
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:
|
To create a new workplan:
|
||||||
1. Write the file following the format above
|
1. Write the file following the format above
|
||||||
|
|||||||
@@ -8,4 +8,5 @@
|
|||||||
@.claude/rules/stack-and-commands.md
|
@.claude/rules/stack-and-commands.md
|
||||||
@.claude/rules/architecture.md
|
@.claude/rules/architecture.md
|
||||||
@.claude/rules/repo-boundary.md
|
@.claude/rules/repo-boundary.md
|
||||||
|
@.claude/rules/credential-routing.md
|
||||||
@.claude/rules/agents.md
|
@.claude/rules/agents.md
|
||||||
|
|||||||
28
Makefile
28
Makefile
@@ -106,7 +106,7 @@ check-secrets: ## Fail if any file under secrets/ is not SOPS-encrypted
|
|||||||
fi; \
|
fi; \
|
||||||
case "$$f" in *.age|*.gpg) continue ;; esac; \
|
case "$$f" in *.age|*.gpg) continue ;; esac; \
|
||||||
echo " UNENCRYPTED: $$f"; bad=1; \
|
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 \
|
if [[ "$$bad" -ne 0 ]]; then \
|
||||||
echo ""; \
|
echo ""; \
|
||||||
echo "ERROR: Unencrypted secret(s) detected. Encrypt with: sops --encrypt --in-place <file>"; \
|
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)
|
creds-emergency-reprint: ## Re-deliver emergency bundle (if lost/stolen — reprints, rotates nothing)
|
||||||
@bash sso-mfa/bootstrap/emergency-bundle.sh --reprint
|
@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
|
iam-profile-conformance-test: ## Run IAM Profile v0.2 conformance fixture tests
|
||||||
python3 -m pytest tools/iam-profile-conformance/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 \
|
select-openbao-unseal-custody-model \
|
||||||
--model "$(if $(MODEL),$(MODEL),sops-held-automation)"
|
--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
|
security-bootstrap-metadata-init: ## Create durable local non-secret bootstrap metadata if missing
|
||||||
@mkdir -p "$$(dirname "$(SECURITY_BOOTSTRAP_METADATA)")"
|
@mkdir -p "$$(dirname "$(SECURITY_BOOTSTRAP_METADATA)")"
|
||||||
@if [[ -f "$(SECURITY_BOOTSTRAP_METADATA)" ]]; then \
|
@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 \
|
check-secrets creds-init creds-generate creds-bundle creds-apply creds-verify \
|
||||||
creds-status creds-rotate \
|
creds-status creds-rotate \
|
||||||
creds-agent-init creds-agent-status creds-emergency-reprint \
|
creds-agent-init creds-agent-status creds-emergency-reprint \
|
||||||
|
openbao-init-unseal openbao-init-unseal-dry-run \
|
||||||
iam-profile-conformance-test playbook-contract-test \
|
iam-profile-conformance-test playbook-contract-test \
|
||||||
security-bootstrap-console-test security-bootstrap-scripts-syntax \
|
security-bootstrap-console-test security-bootstrap-scripts-syntax \
|
||||||
security-bootstrap-console security-bootstrap-king-kit \
|
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-sign-custody-roster \
|
||||||
security-bootstrap-approve-custody \
|
security-bootstrap-approve-custody \
|
||||||
security-bootstrap-custody-packet security-bootstrap-openbao-preflight \
|
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-metadata-init security-bootstrap-ui \
|
||||||
security-bootstrap-console-test security-bootstrap-scripts-syntax \
|
security-bootstrap-console-test security-bootstrap-scripts-syntax \
|
||||||
security-bootstrap-validate-keycape-client
|
security-bootstrap-validate-keycape-client
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
# NetKingdom
|
# NetKingdom
|
||||||
|
|
||||||
NetKingdom provides a dynamic self optimizing full circle security-platform for kubernetes deployed IT-infrastructures.
|
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.
|
||||||
|
|||||||
49
SCOPE.md
49
SCOPE.md
@@ -22,13 +22,21 @@ NetKingdom is a self-optimizing security platform for Kubernetes-based IT infras
|
|||||||
|
|
||||||
- NetKingdom IAM Profile specification (versioned OIDC/PKCE contract;
|
- NetKingdom IAM Profile specification (versioned OIDC/PKCE contract;
|
||||||
canonical spec: `canon/standards/iam-profile_v0.2.md`)
|
canonical spec: `canon/standards/iam-profile_v0.2.md`)
|
||||||
- SSO/MFA Platform: Keycloak with LDAP/Entra federation, enterprise identity (NK-WP-0001)
|
- 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)
|
- Local Identity: file-based user store + minimal OIDC server for bootstrap phase (NK-WP-0002, finished)
|
||||||
- User Engine Boundary Contract: source-of-truth, membership,
|
- User Engine Boundary Contract: source-of-truth, membership,
|
||||||
application-onboarding, projection, authorization, and audit contracts for
|
application-onboarding, projection, authorization, and audit contracts for
|
||||||
`user-engine` integration (`canon/standards/user-engine-boundary-contract_v0.1.md`)
|
`user-engine` integration (`canon/standards/user-engine-boundary-contract_v0.1.md`)
|
||||||
- Security bootstrapping: credential management, SOPS/age integration,
|
- Security bootstrapping: credential management, SOPS/age integration,
|
||||||
platform-root custody, OpenBao runtime secret authority
|
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
|
- 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
|
## Current State
|
||||||
|
|
||||||
- Status: active (design phase complete, implementation ongoing)
|
- Status: active — core identity and bootstrap phases delivered; follow-on work proposed
|
||||||
- Implementation: emerging — NK-WP-0001 (SSO/MFA) and NK-WP-0002 (local identity) both in active development
|
- Implementation: NK-WP-0001 (SSO/MFA), NK-WP-0002 (local identity), the
|
||||||
- Stability: evolving
|
security bootstrap arc (NET-WP-0015–0017, 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
|
- 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]
|
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
|
```capability
|
||||||
type: security
|
type: security
|
||||||
title: Bootstrap local identity service
|
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 D1–D5)
|
- Start with: `wiki/` (specifications and decisions), `DECISIONS.md` (key architectural choices D1–D5)
|
||||||
- Key files / directories: `docs/platform-root-custody.md`, `sso-mfa/`
|
- Key files / directories: `docs/platform-root-custody.md`, `sso-mfa/`
|
||||||
(NK-WP-0001 active workplan), `local-identity/` (NK-WP-0002),
|
(SSO/MFA platform + bootstrap scripts), `local-identity/`,
|
||||||
`workplans/`
|
`tools/security-bootstrap-console/`, `workplans/` (finished plans in
|
||||||
- Entry points: `workplans/NK-WP-0001-sso-mfa-platform.md` and `NK-WP-0002-local-identity.md` for current work
|
`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:
|
- User-domain boundary contract:
|
||||||
`canon/standards/user-engine-boundary-contract_v0.1.md`
|
`canon/standards/user-engine-boundary-contract_v0.1.md`
|
||||||
- User-engine integration assessment (intent/scope fit, gaps, and recommendations):
|
- 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:
|
- Bootstrap/custody entry points:
|
||||||
`docs/platform-root-custody.md`,
|
`docs/platform-root-custody.md`,
|
||||||
`docs/security-bootstrap-use-cases.md`,
|
`docs/security-bootstrap-use-cases.md`,
|
||||||
`workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md`,
|
`docs/openbao-unseal-custody-models.md` (three custody models + deployment
|
||||||
and `workplans/NET-WP-0016-guided-security-bootstrap-experience.md`
|
profile), and `docs/openbao-attended-ceremony-runbook.md` (production
|
||||||
|
ceremony); history of the custody/bootstrap arc in `workplans/archived/`
|
||||||
|
(NET-WP-0015–0017, 0019) and
|
||||||
|
`workplans/NET-WP-0020-openbao-unseal-custody-and-ssh-automation.md`
|
||||||
|
|||||||
82
docs/openbao-attended-ceremony-runbook.md
Normal file
82
docs/openbao-attended-ceremony-runbook.md
Normal 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
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
# OpenBao Unseal Custody Models
|
# OpenBao Unseal Custody Models
|
||||||
|
|
||||||
Date: 2026-06-17
|
Date: 2026-06-17 (updated 2026-07-02)
|
||||||
Status: framework — automation path active; production paths planned
|
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.
|
NetKingdom bootstrap must support **three** OpenBao init/unseal custody models.
|
||||||
Development starts with **maximum automation** for fast test cycles, then adds
|
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 |
|
| Model ID | Label | Custody strength | Automation | Status |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| `sops-held-automation` | SOPS-held unseal | Lab / fast iteration | High | **Implemented** (console + creds agent path) |
|
| `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 | Planned |
|
| `attended-ceremony` | Attended ceremony | Production | Low | **Implemented** (runbook + ceremony-record validator) |
|
||||||
| `auto-unseal-transit` | Auto-unseal (transit/KMS) | Production HA | High | Planned |
|
| `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)
|
### `sops-held-automation` (default for greenfield dev)
|
||||||
|
|
||||||
- Init/unseal material lives in **SOPS/age** custody bundle (not Git plaintext).
|
- 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`
|
- Applied by `sso-mfa/bootstrap/openbao-init-unseal.sh` (`make
|
||||||
tooling after cluster + OpenBao pod exist.
|
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.
|
- Enables **unattended rebuild test cycles** on a 3-node slate.
|
||||||
- **Not** production trust posture — use to prove S1→S3→SSH engine automation,
|
- **Not** production trust posture — use to prove S1→S3→SSH engine automation,
|
||||||
then graduate to stronger models.
|
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
|
- 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-0015–0017).
|
- Matches first successful NetKingdom bootstrap (NET-WP-0015–0017).
|
||||||
- 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.
|
- OpenBao seal configuration uses **transit** or cloud KMS auto-unseal.
|
||||||
- Pod restart without manual unseal threshold ceremony.
|
- 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
|
The console metadata carries a `deployment_profile` (`lab` default,
|
||||||
1. Implement automation path (sops-held-automation)
|
`production`). The **production profile blocks `sops-held-automation`** — its
|
||||||
→ SSH engine, warden sign, host CA trust, 3-node rebuild loops
|
SOPS bundle holds root token and unseal shares together, which is lab posture
|
||||||
2. Add attended-ceremony gates (block automation defaults in production profile)
|
only. Selection, status gates, and `openbao-init-unseal.sh` all enforce the
|
||||||
3. Add auto-unseal-transit for HA ThreePhoenix rebuilds
|
block:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make security-bootstrap-select-deployment-profile PROFILE=production
|
||||||
```
|
```
|
||||||
|
|
||||||
Each model is selectable in the **security bootstrap console**. Unimplemented
|
Each model is selectable in the **security bootstrap console**; gates express
|
||||||
models are **blocked** with a hint pointing to the active automation path.
|
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 |
|
| S1 OS baseline | railiance-infra | 3 nodes |
|
||||||
| S2 k3s HA | railiance-cluster | ThreePhoenix |
|
| S2 k3s HA | railiance-cluster | ThreePhoenix |
|
||||||
| S3 OpenBao deploy | railiance-platform | `make openbao-deploy` |
|
| 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` |
|
| Platform config | railiance-platform | `openbao-configure-initial` |
|
||||||
| SSH engine | railiance-platform | `openbao-configure-ssh` (planned) |
|
| SSH engine | railiance-platform | `openbao-configure-ssh` (planned) |
|
||||||
| Host CA trust | railiance-infra | `bootstrap-ssh-ca` (planned) |
|
| Host CA trust | railiance-infra | `bootstrap-ssh-ca` (planned) |
|
||||||
|
|||||||
@@ -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 |
|
| **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 |
|
| **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
|
### `key-cape` — identity
|
||||||
|
|
||||||
| | |
|
| | |
|
||||||
|
|||||||
363
docs/secrets-engine-security-infrastructure-boundary.md
Normal file
363
docs/secrets-engine-security-infrastructure-boundary.md
Normal 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.
|
||||||
40
history/2026-07-02-openbao-greenfield-init-unseal-proof.md
Normal file
40
history/2026-07-02-openbao-greenfield-init-unseal-proof.md
Normal 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).
|
||||||
130
registry/capabilities/capability.security.iam-tooling-suite.md
Normal file
130
registry/capabilities/capability.security.iam-tooling-suite.md
Normal 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`
|
||||||
@@ -1,4 +1,22 @@
|
|||||||
version: 1
|
version: 1
|
||||||
updated: '2026-06-16'
|
updated: '2026-07-06'
|
||||||
domain: helix_forge
|
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
|
||||||
|
|||||||
@@ -88,10 +88,19 @@ if [[ ! -f "$AGE_KEY" ]]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
AGE_PUBKEY=$(grep 'public key:' "$AGE_KEY" | awk '{print $NF}')
|
if [[ -f "$AGE_KEY" ]]; then
|
||||||
[[ -z "$AGE_PUBKEY" ]] && die "could not read public key from $AGE_KEY"
|
AGE_PUBKEY=$(grep 'public key:' "$AGE_KEY" | awk '{print $NF}')
|
||||||
ok "age key ready: ${AGE_PUBKEY:0:20}…"
|
[[ -z "$AGE_PUBKEY" ]] && die "could not read public key from $AGE_KEY"
|
||||||
state_set "age_key_present" "true"
|
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
|
# Cluster reachability
|
||||||
if ! kubectl cluster-info &>/dev/null; then
|
if ! kubectl cluster-info &>/dev/null; then
|
||||||
@@ -298,6 +307,45 @@ else
|
|||||||
echo " [dry-run] would run: bash creds-verify.sh"
|
echo " [dry-run] would run: bash creds-verify.sh"
|
||||||
fi
|
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 ────────────────────────────────────────────────────────
|
# ── Phase 8: Ops bundle ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
step "8 — Create ops bundle (age-encrypted snapshot)"
|
step "8 — Create ops bundle (age-encrypted snapshot)"
|
||||||
|
|||||||
@@ -28,5 +28,12 @@ secrets_applied:
|
|||||||
enckey_bootstrapped: true
|
enckey_bootstrapped: true
|
||||||
pi_admin_created: 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-0015–0017), 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
|
# Derived: all true → bootstrap complete
|
||||||
bootstrap_complete: true
|
bootstrap_complete: true
|
||||||
|
|||||||
204
sso-mfa/bootstrap/openbao-init-unseal.sh
Executable file
204
sso-mfa/bootstrap/openbao-init-unseal.sh
Executable 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
|
||||||
@@ -73,7 +73,8 @@ ROLE_JSON
|
|||||||
default_role="platform-admin"
|
default_role="platform-admin"
|
||||||
|
|
||||||
bao write "auth/${mount}/role/platform-admin" @/tmp/openbao-platform-admin-role.json
|
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
|
done
|
||||||
|
|
||||||
rm -f /tmp/openbao-platform-admin-role.json /tmp/openbao-*-auth-enable.out /tmp/openbao-*-auth-enable.err
|
rm -f /tmp/openbao-platform-admin-role.json /tmp/openbao-*-auth-enable.out /tmp/openbao-*-auth-enable.err
|
||||||
|
|||||||
@@ -64,30 +64,28 @@ OPENBAO_UNSEAL_CUSTODY_MODEL_SPECS: dict[str, dict[str, str]] = {
|
|||||||
},
|
},
|
||||||
"attended-ceremony": {
|
"attended-ceremony": {
|
||||||
"label": "Attended ceremony (production custody)",
|
"label": "Attended ceremony (production custody)",
|
||||||
"implementation": "planned",
|
"implementation": "implemented",
|
||||||
"summary": (
|
"summary": (
|
||||||
"Human-attended init, out-of-band unseal escrow, root retirement — "
|
"Human-attended init, out-of-band unseal escrow, root retirement — "
|
||||||
"railiance-platform/docs/openbao.md ceremony."
|
"railiance-platform/docs/openbao.md ceremony. Console never runs init; "
|
||||||
),
|
"runbook + evidence validation only."
|
||||||
"blocked_hint": (
|
|
||||||
"Not yet implemented in the automation path. Use sops-held-automation for "
|
|
||||||
"fast bootstrap test cycles; attended ceremony will gate production trust."
|
|
||||||
),
|
),
|
||||||
|
"runbook_entry": "docs/openbao-attended-ceremony-runbook.md",
|
||||||
"custody_strength": "production",
|
"custody_strength": "production",
|
||||||
},
|
},
|
||||||
"auto-unseal-transit": {
|
"auto-unseal-transit": {
|
||||||
"label": "Auto-unseal (transit/KMS seal)",
|
"label": "Auto-unseal (transit/KMS seal)",
|
||||||
"implementation": "planned",
|
"implementation": "implemented",
|
||||||
"summary": (
|
"summary": (
|
||||||
"Seal config uses transit or cloud KMS; pod restart without manual unseal."
|
"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."
|
||||||
"blocked_hint": (
|
|
||||||
"Not yet implemented. Requires railiance-platform Helm seal stanza and "
|
|
||||||
"KMS/transit provisioning. Use sops-held-automation until available."
|
|
||||||
),
|
),
|
||||||
|
"config_entry": "railiance-platform/helm/openbao-values.yaml (seal stanza)",
|
||||||
"custody_strength": "production-ha",
|
"custody_strength": "production-ha",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
VALID_DEPLOYMENT_PROFILES = ("lab", "production")
|
||||||
|
DEFAULT_DEPLOYMENT_PROFILE = "lab"
|
||||||
VALID_OPENBAO_UNSEAL_CUSTODY_MODELS = frozenset(OPENBAO_UNSEAL_CUSTODY_MODEL_SPECS)
|
VALID_OPENBAO_UNSEAL_CUSTODY_MODELS = frozenset(OPENBAO_UNSEAL_CUSTODY_MODEL_SPECS)
|
||||||
DEFAULT_OPENBAO_UNSEAL_CUSTODY_MODEL = "sops-held-automation"
|
DEFAULT_OPENBAO_UNSEAL_CUSTODY_MODEL = "sops-held-automation"
|
||||||
IMPLEMENTED_OPENBAO_UNSEAL_CUSTODY_MODELS = frozenset(
|
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
|
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:
|
def openbao_unseal_custody_model_gate(data: dict[str, Any]) -> Gate:
|
||||||
model = resolve_openbao_unseal_custody_model(data)
|
model = resolve_openbao_unseal_custody_model(data)
|
||||||
spec = OPENBAO_UNSEAL_CUSTODY_MODEL_SPECS[model]
|
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):
|
if openbao_unseal_custody_model_implemented(model):
|
||||||
return Gate(
|
return Gate(
|
||||||
"OpenBao unseal custody model",
|
"OpenBao unseal custody model",
|
||||||
"done",
|
"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(
|
return Gate(
|
||||||
"OpenBao unseal custody model",
|
"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."),
|
spec.get("blocked_hint", "Selected unseal custody model is not implemented."),
|
||||||
)
|
)
|
||||||
if model == "sops-held-automation":
|
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", "")
|
entry = OPENBAO_UNSEAL_CUSTODY_MODEL_SPECS[model].get("automation_entry", "")
|
||||||
return Gate(
|
return Gate(
|
||||||
"OpenBao init ceremony",
|
"OpenBao init ceremony",
|
||||||
@@ -498,10 +526,44 @@ def openbao_init_ceremony_gate(data: dict[str, Any]) -> Gate:
|
|||||||
f"({entry}). Console will not run init."
|
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(
|
return Gate(
|
||||||
"OpenBao init ceremony",
|
"OpenBao init ceremony",
|
||||||
"human",
|
"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":
|
if gate.name == "OpenBao preflight":
|
||||||
return "Run OpenBao preflight"
|
return "Run OpenBao preflight"
|
||||||
if gate.name == "OpenBao unseal custody model":
|
if gate.name == "OpenBao unseal custody model":
|
||||||
return (
|
if data and resolve_deployment_profile(data) == "production":
|
||||||
"Select openbao-unseal-custody-model sops-held-automation "
|
return (
|
||||||
"(other models not yet implemented)"
|
"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":
|
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":
|
if gate.name == "KeyCape OpenBao client definition":
|
||||||
return "Ship KeyCape OpenBao client definition"
|
return "Ship KeyCape OpenBao client definition"
|
||||||
if gate.name == "KeyCape OpenBao client deployed":
|
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("17. select-openbao-unseal-custody-model")
|
||||||
print("18. web-ui")
|
print("18. web-ui")
|
||||||
print("19. validate-keycape-client (T08: example of validator-driven gate in UI state model)")
|
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("")
|
||||||
print("Refusal boundary")
|
print("Refusal boundary")
|
||||||
print("This console will not run bao operator init or collect secret values.")
|
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": "",
|
"progress_scope": "",
|
||||||
"openbao_unseal_custody_model": DEFAULT_OPENBAO_UNSEAL_CUSTODY_MODEL,
|
"openbao_unseal_custody_model": DEFAULT_OPENBAO_UNSEAL_CUSTODY_MODEL,
|
||||||
"openbao_unseal_custody_model_selected_at": "",
|
"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_preflight_passed": False,
|
||||||
"openbao_init_output_produced": False,
|
"openbao_init_output_produced": False,
|
||||||
"openbao_initialized": False,
|
"openbao_initialized": False,
|
||||||
@@ -2002,7 +2073,7 @@ def print_openbao_unseal_custody_models() -> int:
|
|||||||
if status != "implemented":
|
if status != "implemented":
|
||||||
print(f" blocked_hint: {spec.get('blocked_hint', '')}")
|
print(f" blocked_hint: {spec.get('blocked_hint', '')}")
|
||||||
else:
|
else:
|
||||||
print(f" automation_entry: {spec.get('automation_entry', '')}")
|
print(f" entry: {openbao_unseal_custody_model_entry(spec)}")
|
||||||
print("")
|
print("")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -2031,6 +2102,18 @@ def print_select_openbao_unseal_custody_model(args: argparse.Namespace, data: di
|
|||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
return 2
|
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 = metadata_template()
|
||||||
merged.update(data)
|
merged.update(data)
|
||||||
merged["openbao_unseal_custody_model"] = model
|
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"Metadata: {args.metadata}")
|
||||||
print(f"Model: {model}")
|
print(f"Model: {model}")
|
||||||
print(f"Label: {spec.get('label', '')}")
|
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
|
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]:
|
def gate_payload(gate: Gate) -> dict[str, str]:
|
||||||
return {
|
return {
|
||||||
"name": gate.name,
|
"name": gate.name,
|
||||||
@@ -4974,6 +5147,29 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
default=DEFAULT_OPENBAO_UNSEAL_CUSTODY_MODEL,
|
default=DEFAULT_OPENBAO_UNSEAL_CUSTODY_MODEL,
|
||||||
help="Unseal custody model id.",
|
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.")
|
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 = 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.")
|
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",
|
"validate-cleanup",
|
||||||
"approve-custody-mode",
|
"approve-custody-mode",
|
||||||
"select-openbao-unseal-custody-model",
|
"select-openbao-unseal-custody-model",
|
||||||
|
"select-deployment-profile",
|
||||||
"web-ui",
|
"web-ui",
|
||||||
}
|
}
|
||||||
if args.command in metadata_commands and args.metadata is None:
|
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)
|
return print_openbao_preflight(args)
|
||||||
if args.command == "openbao-unseal-custody-models":
|
if args.command == "openbao-unseal-custody-models":
|
||||||
return print_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":
|
if args.command == "select-openbao-unseal-custody-model":
|
||||||
return print_select_openbao_unseal_custody_model(args, data)
|
return print_select_openbao_unseal_custody_model(args, data)
|
||||||
if args.command == "web-ui":
|
if args.command == "web-ui":
|
||||||
|
|||||||
@@ -36,15 +36,82 @@ def test_openbao_unseal_custody_model_gate_automation_default():
|
|||||||
assert init_gate.status == "automation"
|
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"):
|
for model in ("attended-ceremony", "auto-unseal-transit"):
|
||||||
data = console.metadata_template()
|
data = console.metadata_template()
|
||||||
data["openbao_unseal_custody_model"] = model
|
data["openbao_unseal_custody_model"] = model
|
||||||
gate = console.openbao_unseal_custody_model_gate(data)
|
gate = console.openbao_unseal_custody_model_gate(data)
|
||||||
assert gate.status == "blocked"
|
assert gate.status == "done"
|
||||||
assert "not yet implemented" in gate.reason.lower()
|
|
||||||
init_gate = console.openbao_init_ceremony_gate(data)
|
|
||||||
assert init_gate.status == "blocked"
|
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():
|
def test_onboarding_dry_run_template_has_required_fields():
|
||||||
tmpl = console.onboarding_dry_run_template()
|
tmpl = console.onboarding_dry_run_template()
|
||||||
|
|||||||
50
workplans/ADHOC-2026-07-02.md
Normal file
50
workplans/ADHOC-2026-07-02.md
Normal 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.
|
||||||
@@ -2,13 +2,14 @@
|
|||||||
id: NET-WP-0020
|
id: NET-WP-0020
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "OpenBao Unseal Custody Models and SSH Automation Path"
|
title: "OpenBao Unseal Custody Models and SSH Automation Path"
|
||||||
domain: net-kingdom
|
domain: infotech
|
||||||
repo: net-kingdom
|
repo: net-kingdom
|
||||||
status: active
|
status: finished
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: net-kingdom
|
topic_slug: net-kingdom
|
||||||
created: "2026-06-17"
|
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
|
# NET-WP-0020 — OpenBao Unseal Custody Models and SSH Automation Path
|
||||||
@@ -31,6 +32,7 @@ production trust increases.
|
|||||||
id: NET-WP-0020-T01
|
id: NET-WP-0020-T01
|
||||||
status: done
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
|
state_hub_task_id: "7040f347-d54a-42ba-a14f-5b0a7e691786"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [x] `docs/openbao-unseal-custody-models.md`
|
- [x] `docs/openbao-unseal-custody-models.md`
|
||||||
@@ -42,37 +44,86 @@ priority: high
|
|||||||
|
|
||||||
```task
|
```task
|
||||||
id: NET-WP-0020-T02
|
id: NET-WP-0020-T02
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
|
state_hub_task_id: "65407eb1-9d89-4158-aed5-4987badd83fc"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] Extend `creds-bootstrap-agent.sh` for OpenBao init/unseal when sealed
|
- [x] SOPS-held init/unseal automation: `sso-mfa/bootstrap/openbao-init-unseal.sh`
|
||||||
- [ ] Non-secret evidence flags: `openbao_initialized`, `openbao_post_unseal_verified`
|
(`make openbao-init-unseal` / `openbao-init-unseal-dry-run`)
|
||||||
- [ ] Integrate with `make openbao-configure-initial` post-unseal
|
- [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
|
### T3 — Attended ceremony automation profile
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: NET-WP-0020-T03
|
id: NET-WP-0020-T03
|
||||||
status: wait
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
|
state_hub_task_id: "34f3d979-a040-49ca-bfcb-35cf17473a06"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] Implement `attended-ceremony` selection path (runbooks + evidence validators)
|
- [x] Implement `attended-ceremony` selection path (runbooks + evidence validators)
|
||||||
- [ ] Production profile blocks `sops-held-automation` default
|
- [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
|
### T4 — Auto-unseal transit profile
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: NET-WP-0020-T04
|
id: NET-WP-0020-T04
|
||||||
status: wait
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
|
state_hub_task_id: "54ab6505-c13b-4f63-8c94-07dd202de90a"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] `railiance-platform` Helm seal stanza for transit/KMS
|
- [x] `railiance-platform` Helm seal stanza for transit/KMS
|
||||||
- [ ] Console gate + evidence for `auto-unseal-transit`
|
- [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)
|
### T5 — SSH engine + host CA automation (cross-repo)
|
||||||
|
|
||||||
@@ -80,6 +131,7 @@ priority: medium
|
|||||||
id: NET-WP-0020-T05
|
id: NET-WP-0020-T05
|
||||||
status: done
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
|
state_hub_task_id: "399e82ca-6551-4020-8db5-c78076e75cfc"
|
||||||
```
|
```
|
||||||
|
|
||||||
- [x] `railiance-platform`: `openbao-configure-ssh` declarative script + Makefile targets
|
- [x] `railiance-platform`: `openbao-configure-ssh` declarative script + Makefile targets
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
id: NK-WP-0009
|
id: NK-WP-0009
|
||||||
type: workplan
|
type: workplan
|
||||||
title: NetKingdom Security Pattern Tutorials
|
title: NetKingdom Security Pattern Tutorials
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
repo: net-kingdom
|
repo: net-kingdom
|
||||||
status: proposed
|
status: backlog
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: netkingdom
|
topic_slug: netkingdom
|
||||||
planning_priority: medium
|
planning_priority: medium
|
||||||
planning_order: 9
|
planning_order: 9
|
||||||
created: 2026-05-17
|
created: 2026-05-17
|
||||||
updated: 2026-05-17
|
updated: 2026-07-08
|
||||||
depends_on:
|
depends_on:
|
||||||
- NK-WP-0008
|
- NK-WP-0008
|
||||||
state_hub_workstream_id: "66c9f1e9-6b2f-454b-a6d4-04e5fe42385a"
|
state_hub_workstream_id: "66c9f1e9-6b2f-454b-a6d4-04e5fe42385a"
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
id: NK-WP-0011
|
id: NK-WP-0011
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Enterprise Federation & SAML — Expanded-Mode Keycloak Identity Broker"
|
title: "Enterprise Federation & SAML — Expanded-Mode Keycloak Identity Broker"
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
repo: net-kingdom
|
repo: net-kingdom
|
||||||
status: proposed
|
status: backlog
|
||||||
owner: worsch
|
owner: worsch
|
||||||
topic_slug: netkingdom
|
topic_slug: netkingdom
|
||||||
created: "2026-05-20"
|
created: "2026-05-20"
|
||||||
updated: "2026-05-20"
|
updated: "2026-07-08"
|
||||||
state_hub_workstream_id: a44beef8-c18b-4ae7-b7fe-a178cc4fcdf0
|
state_hub_workstream_id: a44beef8-c18b-4ae7-b7fe-a178cc4fcdf0
|
||||||
depends_on:
|
depends_on:
|
||||||
- NK-WP-0003
|
- NK-WP-0003
|
||||||
@@ -18,8 +18,7 @@ supersedes_tasks:
|
|||||||
- NK-WP-0001-T05
|
- NK-WP-0001-T05
|
||||||
- NK-WP-0001-T06
|
- NK-WP-0001-T06
|
||||||
- NK-WP-0001-T07
|
- NK-WP-0001-T07
|
||||||
- NK-WP-0001-T08
|
- NK-WP-0001-T08---
|
||||||
---
|
|
||||||
|
|
||||||
# NK-WP-0011 — Enterprise Federation & SAML (Expanded-Mode Keycloak)
|
# NK-WP-0011 — Enterprise Federation & SAML (Expanded-Mode Keycloak)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ owner: codex
|
|||||||
topic_slug: netkingdom
|
topic_slug: netkingdom
|
||||||
created: "2026-06-14"
|
created: "2026-06-14"
|
||||||
updated: "2026-06-14"
|
updated: "2026-06-14"
|
||||||
|
state_hub_workstream_id: "c6f3d6bf-4916-490d-96ce-092d2776d7e7"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Ad hoc NetKingdom operator usability fixes
|
# Ad hoc NetKingdom operator usability fixes
|
||||||
@@ -19,6 +20,7 @@ updated: "2026-06-14"
|
|||||||
id: ADHOC-2026-06-14-T01
|
id: ADHOC-2026-06-14-T01
|
||||||
status: done
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
|
state_hub_task_id: "bb5973d8-8e61-48f6-b627-a662f8a34ad1"
|
||||||
```
|
```
|
||||||
|
|
||||||
Added a custody unlock helper for SOPS/age operations so drills and incident
|
Added a custody unlock helper for SOPS/age operations so drills and incident
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
id: NET-WP-0017
|
id: NET-WP-0017
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "IT Security Readiness For User Onboarding"
|
title: "IT Security Readiness For User Onboarding"
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
repo: net-kingdom
|
repo: net-kingdom
|
||||||
status: finished
|
status: finished
|
||||||
owner: codex
|
owner: codex
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
id: NET-WP-0019
|
id: NET-WP-0019
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "T06-adjacent Polish: Non-Root User Lifecycle Dry-Run Automation And Control Surface Improvements"
|
title: "T06-adjacent Polish: Non-Root User Lifecycle Dry-Run Automation And Control Surface Improvements"
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
repo: net-kingdom
|
repo: net-kingdom
|
||||||
status: finished
|
status: finished
|
||||||
owner: codex
|
owner: codex
|
||||||
@@ -14,8 +14,7 @@ depends_on:
|
|||||||
- NET-WP-0018
|
- NET-WP-0018
|
||||||
state_hub_workstream_id: "75d388b6-7ec1-4e1b-8c87-6ff44f953210"
|
state_hub_workstream_id: "75d388b6-7ec1-4e1b-8c87-6ff44f953210"
|
||||||
related:
|
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
|
# NET-WP-0019 - T06-adjacent Polish: Non-Root User Lifecycle Dry-Run Automation And Control Surface Improvements
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
id: NK-WP-0001
|
id: NK-WP-0001
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "SSO & MFA Platform — Keycloak + privacyIDEA on Kubernetes"
|
title: "SSO & MFA Platform — Keycloak + privacyIDEA on Kubernetes"
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
status: archived
|
status: archived
|
||||||
owner: worsch
|
owner: worsch
|
||||||
topic_slug: netkingdom
|
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
|
deployed in-cluster, secrets migrated, ESO operational and injecting secrets
|
||||||
into at least one test workload (0b). Encrypted ops bundle exported and
|
into at least one test workload (0b). Encrypted ops bundle exported and
|
||||||
stored offsite.
|
stored offsite.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### T02 — Phase 1: K8s foundations (namespaces, NetworkPolicies, cert-manager)
|
### T02 — Phase 1: K8s foundations (namespaces, NetworkPolicies, cert-manager)
|
||||||
@@ -214,7 +213,7 @@ restore drill passed.
|
|||||||
```task
|
```task
|
||||||
id: NK-WP-0001-T04
|
id: NK-WP-0001-T04
|
||||||
state_hub_task_id: 6ad1296a-a488-4031-b665-f77030e971ed
|
state_hub_task_id: 6ad1296a-a488-4031-b665-f77030e971ed
|
||||||
status: cancelled
|
status: cancel
|
||||||
priority: high
|
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.
|
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
|
```task
|
||||||
id: NK-WP-0001-T05
|
id: NK-WP-0001-T05
|
||||||
state_hub_task_id: b9f73aa6-9035-4643-9905-64e73a29b298
|
state_hub_task_id: b9f73aa6-9035-4643-9905-64e73a29b298
|
||||||
status: cancelled
|
status: cancel
|
||||||
priority: high
|
priority: high
|
||||||
note: Migrated to NK-WP-0011 (enterprise federation / SAML). Refined there against the deployed KeyCape stack and the OpenBao/flex-auth architecture.
|
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
|
```task
|
||||||
id: NK-WP-0001-T06
|
id: NK-WP-0001-T06
|
||||||
state_hub_task_id: 3b6379a4-a27b-4d25-82be-bc600879f036
|
state_hub_task_id: 3b6379a4-a27b-4d25-82be-bc600879f036
|
||||||
status: cancelled
|
status: cancel
|
||||||
priority: medium
|
priority: medium
|
||||||
note: Migrated to NK-WP-0011 (enterprise federation / SAML).
|
note: Migrated to NK-WP-0011 (enterprise federation / SAML).
|
||||||
```
|
```
|
||||||
@@ -330,7 +329,7 @@ modes handled gracefully.
|
|||||||
```task
|
```task
|
||||||
id: NK-WP-0001-T07
|
id: NK-WP-0001-T07
|
||||||
state_hub_task_id: c7cf902a-b480-4545-a536-293070945206
|
state_hub_task_id: c7cf902a-b480-4545-a536-293070945206
|
||||||
status: cancelled
|
status: cancel
|
||||||
priority: medium
|
priority: medium
|
||||||
note: Migrated to NK-WP-0011 (enterprise federation / SAML).
|
note: Migrated to NK-WP-0011 (enterprise federation / SAML).
|
||||||
```
|
```
|
||||||
@@ -373,7 +372,7 @@ audit logs flowing, Keycloak resolver configured.
|
|||||||
```task
|
```task
|
||||||
id: NK-WP-0001-T08
|
id: NK-WP-0001-T08
|
||||||
state_hub_task_id: 9cbd1d89-b5bf-491e-9d16-b1c7d57076fb
|
state_hub_task_id: 9cbd1d89-b5bf-491e-9d16-b1c7d57076fb
|
||||||
status: cancelled
|
status: cancel
|
||||||
priority: medium
|
priority: medium
|
||||||
note: Migrated to NK-WP-0011 (enterprise federation / SAML).
|
note: Migrated to NK-WP-0011 (enterprise federation / SAML).
|
||||||
```
|
```
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
id: NK-WP-0002
|
id: NK-WP-0002
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Local Identity — Bootstrap User Store & Minimal OIDC"
|
title: "Local Identity — Bootstrap User Store & Minimal OIDC"
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
status: completed
|
status: finished
|
||||||
owner: worsch
|
owner: worsch
|
||||||
topic_slug: netkingdom
|
topic_slug: netkingdom
|
||||||
state_hub_workstream_id: 7c9021b1-319c-4b4a-a8be-0642239a1893
|
state_hub_workstream_id: 7c9021b1-319c-4b4a-a8be-0642239a1893
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
id: NK-WP-0006
|
id: NK-WP-0006
|
||||||
type: workplan
|
type: workplan
|
||||||
title: Recursive platform identity and security architecture
|
title: Recursive platform identity and security architecture
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
repo: net-kingdom
|
repo: net-kingdom
|
||||||
status: done
|
status: finished
|
||||||
owner: Bernd Worsch
|
owner: Bernd Worsch
|
||||||
topic_slug: netkingdom
|
topic_slug: netkingdom
|
||||||
created: 2026-05-17
|
created: 2026-05-17
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
id: NK-WP-0007
|
id: NK-WP-0007
|
||||||
type: workplan
|
type: workplan
|
||||||
title: Object Storage STS Credential Vending
|
title: Object Storage STS Credential Vending
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
repo: net-kingdom
|
repo: net-kingdom
|
||||||
status: done
|
status: finished
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: netkingdom
|
topic_slug: netkingdom
|
||||||
planning_priority: high
|
planning_priority: high
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
id: NK-WP-0008
|
id: NK-WP-0008
|
||||||
type: workplan
|
type: workplan
|
||||||
title: IT Security Architecture Patterns Infospace
|
title: IT Security Architecture Patterns Infospace
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
repo: net-kingdom
|
repo: net-kingdom
|
||||||
status: done
|
status: done
|
||||||
owner: codex
|
owner: codex
|
||||||
@@ -15,8 +15,7 @@ depends_on:
|
|||||||
- NK-WP-0006
|
- NK-WP-0006
|
||||||
state_hub_workstream_id: "053c6d96-9396-40c9-a2e5-c36531e7810d"
|
state_hub_workstream_id: "053c6d96-9396-40c9-a2e5-c36531e7810d"
|
||||||
execution_repo: infospace-bench
|
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
|
# NK-WP-0008 - IT Security Architecture Patterns Infospace
|
||||||
|
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
id: NK-WP-0010
|
id: NK-WP-0010
|
||||||
type: workplan
|
type: workplan
|
||||||
title: Genesis Security Pattern Completion
|
title: Genesis Security Pattern Completion
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
repo: net-kingdom
|
repo: net-kingdom
|
||||||
status: done
|
status: finished
|
||||||
owner: codex
|
owner: codex
|
||||||
topic_slug: netkingdom
|
topic_slug: netkingdom
|
||||||
planning_priority: medium
|
planning_priority: medium
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
id: NK-WP-0012
|
id: NK-WP-0012
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "NetKingdom IAM Profile Specification"
|
title: "NetKingdom IAM Profile Specification"
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
repo: net-kingdom
|
repo: net-kingdom
|
||||||
status: finished
|
status: finished
|
||||||
owner: worsch
|
owner: worsch
|
||||||
@@ -15,8 +15,7 @@ depends_on:
|
|||||||
- NK-WP-0006
|
- NK-WP-0006
|
||||||
state_hub_workstream_id: 9b8e4afc-eb71-47d9-8750-799a082b320a
|
state_hub_workstream_id: 9b8e4afc-eb71-47d9-8750-799a082b320a
|
||||||
enables:
|
enables:
|
||||||
- NK-WP-0011
|
- NK-WP-0011---
|
||||||
---
|
|
||||||
|
|
||||||
# NK-WP-0012 — NetKingdom IAM Profile Specification
|
# NK-WP-0012 — NetKingdom IAM Profile Specification
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
id: NK-WP-0013
|
id: NK-WP-0013
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "Playbook Capability Contract"
|
title: "Playbook Capability Contract"
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
repo: net-kingdom
|
repo: net-kingdom
|
||||||
status: finished
|
status: finished
|
||||||
owner: worsch
|
owner: worsch
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
id: NK-WP-0014
|
id: NK-WP-0014
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "User Engine Preparation And Boundary Contracts"
|
title: "User Engine Preparation And Boundary Contracts"
|
||||||
domain: netkingdom
|
domain: infotech
|
||||||
repo: net-kingdom
|
repo: net-kingdom
|
||||||
status: finished
|
status: finished
|
||||||
owner: codex
|
owner: codex
|
||||||
Reference in New Issue
Block a user