From 1d0b0e7330ea7386ae468babe1f50f4eb4e850ff Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 24 May 2026 09:26:02 +0200 Subject: [PATCH] openbao king credential bootstrapping --- Makefile | 40 +- SCOPE.md | 13 +- ...platform-identity-security-architecture.md | 29 +- docs/platform-root-custody.md | 186 +++ docs/security-bootstrap-handover-cleanup.md | 103 ++ .../security-bootstrap-king-credential-kit.md | 168 +++ .../security-bootstrap-openbao-ceremony-ux.md | 117 ++ docs/security-bootstrap-operator-journey.md | 192 +++ ...urity-bootstrap-related-workplan-review.md | 56 + docs/security-bootstrap-use-cases.md | 206 +++ docs/security-bootstrap-user-lifecycle.md | 146 ++ .../king-credential-metadata.example.json | 30 + tools/security-bootstrap-console/README.md | 97 ++ .../security_bootstrap_console.py | 1259 +++++++++++++++++ ...-custody-and-openbao-identity-bootstrap.md | 213 +++ ...16-guided-security-bootstrap-experience.md | 202 +++ ...P-0004-credential-management-foundation.md | 14 +- ...-0005-agent-driven-credential-bootstrap.md | 15 +- 18 files changed, 3080 insertions(+), 6 deletions(-) create mode 100644 docs/platform-root-custody.md create mode 100644 docs/security-bootstrap-handover-cleanup.md create mode 100644 docs/security-bootstrap-king-credential-kit.md create mode 100644 docs/security-bootstrap-openbao-ceremony-ux.md create mode 100644 docs/security-bootstrap-operator-journey.md create mode 100644 docs/security-bootstrap-related-workplan-review.md create mode 100644 docs/security-bootstrap-use-cases.md create mode 100644 docs/security-bootstrap-user-lifecycle.md create mode 100644 examples/security-bootstrap/king-credential-metadata.example.json create mode 100644 tools/security-bootstrap-console/README.md create mode 100755 tools/security-bootstrap-console/security_bootstrap_console.py create mode 100644 workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md create mode 100644 workplans/NET-WP-0016-guided-security-bootstrap-experience.md diff --git a/Makefile b/Makefile index aa512d6..58f4585 100644 --- a/Makefile +++ b/Makefile @@ -156,8 +156,46 @@ iam-profile-conformance-test: ## Run IAM Profile v0.2 conformance fixture tests playbook-contract-test: ## Run Playbook Capability Contract fixture tests python3 -m pytest tools/playbook-capability-contract/tests +security-bootstrap-console: ## Show guided security bootstrap status and safe actions + python3 tools/security-bootstrap-console/security_bootstrap_console.py status + +security-bootstrap-king-kit: ## Print the king credential kit checklist + python3 tools/security-bootstrap-console/security_bootstrap_console.py king-kit + +security-bootstrap-validate-kit: ## Validate non-secret king credential metadata: make security-bootstrap-validate-kit METADATA=/tmp/security-bootstrap.json + @[[ -n "$(METADATA)" ]] || (echo "Usage: make security-bootstrap-validate-kit METADATA=/path/to/non-secret.json"; exit 1) + python3 tools/security-bootstrap-console/security_bootstrap_console.py \ + --metadata "$(METADATA)" \ + validate-king-kit + +security-bootstrap-approve-custody: ## Approve custody mode metadata: make security-bootstrap-approve-custody METADATA=/tmp/security-bootstrap.json ARGS="--mfa-enrolled-confirmed --mfa-enrollment-source identity-provider --recovery-confirmed --custody-packet-prepared --no-secret-capture-confirmed" + @[[ -n "$(METADATA)" ]] || (echo "Usage: make security-bootstrap-approve-custody METADATA=/path/to/non-secret.json ARGS='--mfa-enrolled-confirmed --mfa-enrollment-source identity-provider --recovery-confirmed --custody-packet-prepared --no-secret-capture-confirmed'"; exit 1) + python3 tools/security-bootstrap-console/security_bootstrap_console.py \ + --metadata "$(METADATA)" \ + approve-custody-mode \ + --mode "$(if $(MODE),$(MODE),temporary-single-king)" \ + $(ARGS) + +security-bootstrap-custody-packet: ## Print a blank offline custody packet template + python3 tools/security-bootstrap-console/security_bootstrap_console.py custody-packet + +security-bootstrap-openbao-preflight: ## Show safe OpenBao preflight commands + python3 tools/security-bootstrap-console/security_bootstrap_console.py openbao-preflight \ + --railiance-path ../railiance-platform + +security-bootstrap-ui: ## Serve local custody approval UI: make security-bootstrap-ui METADATA=/tmp/security-bootstrap.json PORT=8765 + python3 tools/security-bootstrap-console/security_bootstrap_console.py \ + --metadata "$(if $(METADATA),$(METADATA),/tmp/net-kingdom-security-bootstrap.json)" \ + web-ui \ + --host "$(if $(HOST),$(HOST),127.0.0.1)" \ + --port "$(if $(PORT),$(PORT),8765)" + .PHONY: help hooks hooks-test sops-setup sops-edit sops-encrypt sops-decrypt sops-rotate \ check-secrets creds-init creds-generate creds-bundle creds-apply creds-verify \ creds-status creds-rotate \ creds-agent-init creds-agent-status creds-emergency-reprint \ - iam-profile-conformance-test playbook-contract-test + iam-profile-conformance-test playbook-contract-test \ + security-bootstrap-console security-bootstrap-king-kit \ + security-bootstrap-validate-kit security-bootstrap-approve-custody \ + security-bootstrap-custody-packet security-bootstrap-openbao-preflight \ + security-bootstrap-ui diff --git a/SCOPE.md b/SCOPE.md index 0527e8b..ea833fa 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -27,7 +27,8 @@ NetKingdom is a self-optimizing security platform for Kubernetes-based IT infras - User Engine Boundary Contract: source-of-truth, membership, application-onboarding, projection, authorization, and audit contracts for `user-engine` integration (`canon/standards/user-engine-boundary-contract_v0.1.md`) -- Security bootstrapping: credential management, SOPS/age integration, OpenBao runtime secret authority +- Security bootstrapping: credential management, SOPS/age integration, + platform-root custody, OpenBao runtime secret authority - Architectural decisions (DECISIONS.md): identity source, secrets, GitOps, bootstrap user store --- @@ -44,6 +45,7 @@ NetKingdom is a self-optimizing security platform for Kubernetes-based IT infras ## Relevant When - Setting up identity for a NetKingdom/Railiance deployment +- Designing or using the guided security bootstrap experience - Applications need OIDC authentication; deciding between lightweight (KeyCape) and expanded (Keycloak) modes - Bootstrap scenario: cluster not yet available, need minimal OIDC for dev/test/sandbox - Reviewing IAM Profile specification or architectural identity decisions @@ -118,7 +120,14 @@ keywords: [bootstrap, local-identity, oidc, minimal, dev, sandbox] ## Getting Oriented - Start with: `wiki/` (specifications and decisions), `DECISIONS.md` (key architectural choices D1–D5) -- Key files / directories: `sso-mfa/` (NK-WP-0001 active workplan), `local-identity/` (NK-WP-0002), `workplans/` +- Key files / directories: `docs/platform-root-custody.md`, `sso-mfa/` + (NK-WP-0001 active workplan), `local-identity/` (NK-WP-0002), + `workplans/` - Entry points: `workplans/NK-WP-0001-sso-mfa-platform.md` and `NK-WP-0002-local-identity.md` for current work - User-domain boundary contract: `canon/standards/user-engine-boundary-contract_v0.1.md` +- Bootstrap/custody entry points: + `docs/platform-root-custody.md`, + `docs/security-bootstrap-use-cases.md`, + `workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md`, + and `workplans/NET-WP-0016-guided-security-bootstrap-experience.md` diff --git a/docs/platform-identity-security-architecture.md b/docs/platform-identity-security-architecture.md index 5bee838..6ce2e06 100644 --- a/docs/platform-identity-security-architecture.md +++ b/docs/platform-identity-security-architecture.md @@ -1,7 +1,7 @@ # Platform Identity and Security Architecture Status: implemented architecture baseline for NetKingdom/Railiance/Coulomb -Date: 2026-05-18 +Date: 2026-05-24 ## Purpose @@ -214,6 +214,32 @@ identifies actors and workloads; flex-auth decides whether a credential or secret request is allowed; OpenBao stores, issues, audits, and revokes the resulting secret material. +## Platform Root Custody + +Platform root authority is an accountable custody role, not a tenant admin role +and not a Git account secret. `docs/platform-root-custody.md` records +`tegwick` / `bernd.worsch@gmail.com` as the initial setup operator and contact, +not as the long-term platform root of trust. + +The actual root-of-trust target is a separate king credential: a dedicated, +rarely used platform-root identity independent from day-to-day Gitea and email +accounts. Email may receive notifications, but Git, Gitea, State Hub, chat, +tickets, shell history, and email must never store or transfer unseal keys, +root tokens, private keys, OTP seeds, recovery codes, or screenshots of secret +output. + +Production-ready custody should move toward independent escrow, preferably +two-of-three human or institutional recovery control. Temporary single-operator +king custody is allowed only as a pre-production bootstrap posture with +second-factor protection, encrypted offline storage, and a low-friction upgrade +path to additional custodians. + +The normal admin path should become NetKingdom IAM claims mapped to scoped +OpenBao policies. The initial OpenBao root token remains a bootstrap or +break-glass artifact and must not become the standing operator credential. The +platform must also reset or rotate bootstrap-era credentials and access paths +before live workloads rely on it. + ## Recursive Trust Rule Normal tenant administration must never be sufficient to alter the @@ -444,6 +470,7 @@ an explicit check: | Area | Readiness check | | --- | --- | +| Platform root custody | setup operator, dedicated king credential, second factor, recovery storage, escrow posture, and root-token disposition are recorded without storing secret values | | MFA and identity | key-cape or Keycloak issues IAM Profile v0.2-compatible tokens and passes `tools/iam-profile-conformance/`; privacyIDEA or the selected MFA provider enforces required assurance for privileged actions | | Bootstrap and recovery | age/SOPS material, emergency bundle, and break-glass credentials are present, tested, and separated from tenant administration | | OpenBao runtime secrets | OpenBao is initialized, unsealed or auto-unsealed by the approved mechanism, backed up, audited, and using scoped auth methods and mounts | diff --git a/docs/platform-root-custody.md b/docs/platform-root-custody.md new file mode 100644 index 0000000..3f1b451 --- /dev/null +++ b/docs/platform-root-custody.md @@ -0,0 +1,186 @@ +# Platform Root Custody + +Status: active bootstrap policy +Date: 2026-05-24 + +## Purpose + +This document defines the bootstrap trust model for NetKingdom platform-root +authority and the Railiance OpenBao bootstrap. It deliberately stores only +identity, policy, and procedure. It must never contain passwords, OpenBao +unseal keys, root tokens, recovery keys, one-time codes, private keys, or +screenshots of secret output. + +The design goal is practical security: early infrastructure can be assembled +by a low-trust setup operator, then handed over to a dedicated king credential +when the control plane is ready. After handover, bootstrap-era material is +rotated, reset, or retired before live workloads depend on it. + +## Bootstrap Roles + +| Role | Purpose | Initial assignment | +| --- | --- | --- | +| Setup operator | Runs early MVP/prototype setup, receives notifications, operates Git/Gitea and day-to-day tooling | `tegwick` / `bernd.worsch@gmail.com` / Gitea `tegwick` | +| King credential | Dedicated platform-root break-glass and final custody credential, independent from day-to-day accounts | To be created before OpenBao live bootstrap | +| Future escrow holders | Later independent recovery control, preferably two-of-three custody | To be named after the first safe bootstrap path exists | + +`tegwick` is the initial accountable setup operator and contact. That identity +is useful for notifications, work tracking, Git access, and early operations, +but it is not the desired long-term platform root of trust. + +The king credential must be independent from ordinary Gitea and email access. +Email may receive notifications, but secrets, reset links, root tokens, unseal +shares, private keys, OTP seeds, and recovery codes must not travel through +email. + +## King Credential + +The king credential is the rare platform-root credential used to accept final +custody after the bootstrap substrate exists. It should be represented as a +dedicated identity such as `platform-root` or `king`, not as a personal +day-to-day account. + +Minimum first version: + +- stored in an offline or local password safe controlled by the human operator; +- protected by a separate strong passphrase; +- protected by an OTP factor or hardware-backed factor where the selected IAM + implementation supports it; the QR code or setup key must come from that + verifier, not from the local metadata console; +- not used for normal Git, browsing, chat, or daily administration; +- not recoverable through the day-to-day email account alone; and +- documented only by non-secret metadata in Git/State Hub. + +Preferred later version: + +- two-of-three independent custody or equivalent multisig-style approval; +- at least one independent human or institutional escrow holder; +- documented rotation and recovery drills; and +- break-glass use logged for review without exposing secret values. + +## Custody Meaning + +For this bootstrap, "master credential" means the offline recovery authority +needed to recover or administer the platform before normal identity, +authorization, and secret systems are fully online. It includes OpenBao +unseal/recovery material and, only if retained, the initial OpenBao root token. + +The root token is not the normal admin credential. It is one-time bootstrap or +break-glass material. The preferred end state is: + +- OpenBao is initialized and unsealed. +- Audit devices, mounts, auth methods, and policies are configured. +- A non-root `platform-admin` operator path exists. +- The initial root token is revoked, or is stored offline as sealed + break-glass material under the king credential custody policy. +- Routine access flows through NetKingdom IAM claims and scoped OpenBao + policies. + +## Trust Progression + +The platform moves through explicit trust stages: + +| Stage | Name | Meaning | +| --- | --- | --- | +| S0 | MVP/prototype | Day-to-day accounts and local operator access may have created state. Nothing here is assumed clean enough for live custody. | +| S1 | Low-trust assembly | Setup operator can deploy infrastructure, but does not become platform root. No live secrets are stored. | +| S2 | King credential creation | Dedicated king credential, second factor, and offline recovery storage are created through guided UX. | +| S3 | OpenBao bootstrap | OpenBao is initialized, configured, and moved to non-root admin paths. | +| S4 | Cleanup and hardening | Bootstrap passwords, database credentials, tokens, and access paths are reset or rotated; hosts and workloads are scanned and reviewed. | +| S5 | Reopen under custody | The platform becomes usable under king credential oversight, with day-to-day admins delegated scoped access. | +| S6 | Multi-custodian upgrade | Custody moves to two-of-three or equivalent independent recovery control. | + +This prevents early bootstrap convenience from silently becoming permanent +platform sovereignty. + +## Required Bootstrap Use Cases + +The guided bootstrap experience must cover at least: + +- create or import the king credential; +- verify king credential second factor and recovery storage; +- initialize OpenBao without exposing secret output to unsafe channels; +- onboard a new user; +- temporarily lock a user; +- permanently lock and offboard a user; +- review and rotate user credentials; +- create a new fabric with its own admin; +- transfer a fabric to a new admin; +- perform break-glass access; +- rotate or rekey platform-root material; +- add later two-of-three custody; and +- run cleanup checks before reopening the platform for live use. + +## Target IAM Claims + +When key-cape or Keycloak provisions the first king/admin identity, it should +receive a platform-root identity envelope similar to: + +| Claim or group | Value | +| --- | --- | +| `sub` | stable subject for `platform-root` or the selected king identity | +| `email` | notification address only; no secret transfer | +| `tenant` | `platform` | +| `principal_type` | `human` or `break_glass` depending on IAM support | +| `groups` | `platform-root`, `platform-admin`, `netkingdom-admin`, `railiance-platform-admin` | +| `roles` | `platform-root-custodian`, `openbao-admin`, `identity-admin` | +| `assurance` | MFA-backed for privileged actions | + +The setup operator `tegwick` can receive scoped admin roles for day-to-day +operations later, but those roles must be delegated from the king custody model +and must be revocable without losing recovery authority. + +Tenant administration must remain separate from platform-root claims. Being an +admin for `tenant:coulomb` or another tenant must not imply OpenBao root, +NetKingdom identity-admin, flex-auth platform-policy, or Railiance +platform-service authority. + +## OpenBao Bootstrap Sequence + +The Railiance OpenBao runbook owns the live commands. NetKingdom owns the +identity and custody semantics. + +Before OpenBao initialization: + +1. Use the guided bootstrap UX or checklist to decide the current trust stage. +2. Record `tegwick` as setup operator/contact, not as final root custodian. +3. Create or import the dedicated king credential and verify its second factor. +4. Prepare offline recovery bundle locations. +5. Choose whether this is temporary single-custodian king custody or preferred + independent escrow. +6. Run Railiance `make openbao-status` and `make openbao-verify`. + +During initialization: + +1. Run the OpenBao init command only in the approved maintenance window. +2. Route each unseal share directly to the king credential custody bundle or + approved escrow holder. +3. Use the initial root token only for audit, mounts, auth methods, policies, + and creating the non-root admin path. +4. Do not persist secret output in shell history, Git, State Hub, chat, email, + tickets, screenshots, or issue trackers. + +After initialization: + +1. Create and store a non-root `platform-admin` operator credential. +2. Revoke the initial root token, or seal it offline as break-glass material + under king custody. +3. Reset or rotate bootstrap-era database credentials, admin passwords, + service tokens, and access paths before live use. +4. Run host/workload checks, vulnerability scans where available, and the + OpenBao snapshot plus isolated restore drill. +5. Log non-secret progress with the custody posture and verification outcome. + +## Completion Gates + +The platform-root custody path is ready for live secrets only when: + +- `tegwick` is recorded as setup operator/contact, not root of trust; +- the dedicated king credential exists and is second-factor protected; +- OpenBao init/unseal ceremony is complete; +- routine admin access no longer depends on the initial root token; +- root token disposition is recorded without storing the token value; +- bootstrap-era credentials and databases have been reset or rotated as needed; +- backup, audit, restore, and scan evidence exists; +- NetKingdom IAM claims are ready to become the normal human admin path; and +- two-of-three custody remains an explicit, low-friction upgrade path. diff --git a/docs/security-bootstrap-handover-cleanup.md b/docs/security-bootstrap-handover-cleanup.md new file mode 100644 index 0000000..d9ce70e --- /dev/null +++ b/docs/security-bootstrap-handover-cleanup.md @@ -0,0 +1,103 @@ +# Security Bootstrap Handover And Cleanup + +Status: draft UX contract +Date: 2026-05-24 + +## Purpose + +This document defines the post-king handover cleanup and reopen gates. It is +the product contract for `NET-WP-0016-T07`. + +The platform can be assembled in MVP/prototype mode, but it should not be +treated as clean until bootstrap-era credentials, databases, tokens, and access +paths have been reviewed and reset or rotated. + +## Handover Goal + +The handover proves that: + +- the king credential controls platform-root recovery; +- day-to-day setup access is scoped and revocable; +- OpenBao root-token disposition is known; +- bootstrap-era material has been reset or rotated; +- backups and restore work; and +- the platform can reopen under explicit custody. + +## Cleanup Checklist + +| Area | Required action | +| --- | --- | +| Gitea/admin accounts | Review admins, remove stale accounts, require MFA where available | +| IAM users | Review setup users, platform admins, tenant admins, and reviewers | +| Databases | Reset bootstrap passwords and rotate app credentials | +| OpenBao | Revoke or seal root token, verify non-root admin path, review policies | +| Kubernetes | Review service accounts, tokens, namespaces, and privileged bindings | +| SSH/access | Review keys, remove unknown keys, rotate setup access where needed | +| SOPS/age | Review recipients and emergency bundle handling | +| State Hub | Record non-secret decisions, progress, and remaining gates | +| Backups | Take snapshot and run restore drill before live secrets | +| Audit | Confirm durable audit routing or documented interim custody | +| Scans | Run host/workload checks available for the current environment | + +## Reopen Gates + +The platform may be marked reopened only when: + +- king credential kit is complete; +- OpenBao is initialized and unsealed or approved for the next seal posture; +- root token is revoked or offline-sealed; +- non-root platform admin path exists; +- bootstrap databases and admin credentials are reset or rotated; +- no unknown platform admins remain; +- backup snapshot exists; +- restore drill has passed; +- audit handling is known; +- user lifecycle paths are documented; and +- remaining risk exceptions are listed with owners and dates. + +## UX Shape + +The handover screen should be a checklist with evidence rows: + +```text +HANDOVER + +Stage +S4 - Cleanup and hardening + +Blocked +- Reopen platform: restore drill missing +- Live secrets: root-token disposition deferred + +Evidence +- King credential kit: complete +- OpenBao preflight: passed +- Non-root admin path: pending +``` + +The UI should avoid a celebratory "complete" state. It should say "reopened +under custody" and list any remaining exceptions. + +## Related Workplan Review + +When `NET-WP-0016` closes, review related security and bootstrap workplans for +stale assumptions: + +- `NET-WP-0015` for king credential and custody status; +- `NK-WP-0001` for older Vault and admin bootstrap language; +- `NK-WP-0004` for credential-management foundation alignment; +- `NK-WP-0005` for agent-driven bootstrap boundaries; +- `NK-WP-0006` for platform-root architecture language; +- `NK-WP-0007` for OpenBao and STS responsibility split; +- `NK-WP-0011` for future expanded-mode identity; +- `RAIL-PL-WP-0002` for OpenBao live ceremony gates; and +- any SSO/MFA bootstrap scripts that still assume MVP credentials are final. + +Each review should result in one of: + +- keep as-is; +- update stale language; +- add follow-up task; +- mark superseded; or +- archive/retire if the workplan is now represented by the guided bootstrap + experience. diff --git a/docs/security-bootstrap-king-credential-kit.md b/docs/security-bootstrap-king-credential-kit.md new file mode 100644 index 0000000..c632c7d --- /dev/null +++ b/docs/security-bootstrap-king-credential-kit.md @@ -0,0 +1,168 @@ +# King Credential Kit + +Status: draft UX contract +Date: 2026-05-24 + +## Purpose + +This document defines the non-secret output for the king credential kit. It is +the product contract for `NET-WP-0016-T03`. + +The king credential is a dedicated platform-root credential. It is separate +from day-to-day Gitea, email, chat, and setup-operator accounts. + +## Kit Outputs + +The bootstrap console may generate or print these non-secret artifacts: + +| Artifact | Secret-free content | +| --- | --- | +| Credential checklist | Steps to create or import the credential | +| Custody packet template | Blank fields for offline writing, not filled by software | +| OTP setup checklist | Verifies factor was enrolled with its real verifier without recording seed | +| Recovery checklist | Verifies recovery material exists without recording values | +| Storage checklist | Confirms password safe/offline storage choice | +| Metadata record | Label, date, operator, custody posture, review date | +| Handover receipt | Non-secret statement that custody was accepted | + +## V1 Recommended Kit + +The first practical kit is intentionally simple. It is good enough for +pre-production bootstrap, but it does not pretend to be the final +multi-custodian posture. + +| Field | V1 value | +| --- | --- | +| Credential label | `platform-root` | +| Setup operator/contact | `tegwick` | +| Notification contact | `bernd.worsch@gmail.com` | +| Primary storage | local password safe | +| Offline recovery | printed or handwritten custody packet | +| First second factor | TOTP or WebAuthn/hardware token | +| Email role | notifications only, no secret transfer | +| Day-to-day use | forbidden | +| OpenBao init | still blocked until custody mode is approved | + +This kit defines the credential shape. It does not by itself approve the +custody mode or authorize live OpenBao initialization. + +## Required Metadata + +The UI may record: + +| Field | Example | +| --- | --- | +| Credential label | `platform-root` | +| Custody posture | `temporary-single-king` or `two-of-three-planned` | +| Notification contact | `bernd.worsch@gmail.com` | +| Setup operator | `tegwick` | +| Created date | `2026-05-24` | +| Review date | date for next custody review | +| Storage class | `password-safe`, `offline-paper`, `hardware-token`, or similar | +| MFA class | `totp`, `webauthn`, `hardware-token`, or similar | +| MFA enrolled confirmed | `true` only after the factor is enrolled with its verifier | +| MFA enrollment source | non-secret source label such as `identity-provider` or `hardware-registration` | +| Recovery confirmed | `true` only after offline recovery material exists | +| Custody packet prepared | `true` only after a blank packet is prepared offline | +| No secret capture confirmed | `true` only after the operator confirms no secret values were entered into software | + +It must not record: + +- passwords; +- OTP seeds; +- recovery codes; +- private keys; +- OpenBao unseal shares; +- OpenBao root tokens; +- screenshots of secret output; or +- reset links. + +## Guided Steps + +### 1. Name The Credential + +Suggested label: `platform-root`. + +The UI should explain that this is not a normal user and not a day-to-day admin +account. It is rare root custody. + +### 2. Choose Storage + +Allowed first-version choices: + +| Choice | Meaning | +| --- | --- | +| Password safe | Stored in a local password manager controlled by the operator | +| Offline packet | Written into an offline custody packet | +| Hardware-backed | Protected by a hardware token or equivalent | + +The UI should permit a combination. It should not ask for secret values. + +### 3. Add A Second Factor + +Allowed first-version choices: + +| Choice | Meaning | +| --- | --- | +| TOTP | App-based one-time password | +| WebAuthn | Hardware or platform authenticator | +| Deferred | Only allowed before live OpenBao custody | + +`Deferred` blocks live OpenBao init. + +For TOTP, the QR code or setup key must come from the authority that will +verify login, such as the selected identity provider. A local bootstrap console +must not generate an orphan OTP seed because it would not authenticate +anything. The console records only that enrollment completed and where, +without storing the seed, QR code, recovery codes, or screenshots. + +### 4. Prepare Recovery + +The operator confirms that recovery codes or equivalent recovery material exist +and are stored offline. The UI records only `confirmed` or `not confirmed`. + +### 5. Select Custody Mode + +First-version choices: + +| Mode | Meaning | +| --- | --- | +| `temporary-single-king` | One king custodian for pre-production only | +| `two-of-three-planned` | Independent custody is planned but not ready | +| `two-of-three-ready` | Independent custody holders are ready now | + +Only `temporary-single-king` and `two-of-three-ready` can unblock OpenBao init, +and both still require human ceremony. + +### 6. Print Custody Packet + +The custody packet is a blank template for offline use. It should include: + +- credential label; +- date; +- custody mode; +- storage location description; +- second-factor location description; +- recovery material location description; +- OpenBao share assignment rows; +- root-token disposition row; and +- signature/date line. + +The software must not fill secret fields. + +## Completion Criteria + +The king credential kit is complete when: + +- the credential label exists; +- storage choice is recorded; +- second factor is enrolled with its real verifier and confirmed; +- recovery material is confirmed; +- custody mode is selected; +- offline custody packet is printed or acknowledged; and +- no secret value has been captured. + +For `NET-WP-0015-T02`, the kit can be considered defined when all fields except +the final custody-mode approval have a concrete, non-secret value. `NET-WP-0015` +keeps custody mode approval as T03 because that decision gates live OpenBao +initialization. diff --git a/docs/security-bootstrap-openbao-ceremony-ux.md b/docs/security-bootstrap-openbao-ceremony-ux.md new file mode 100644 index 0000000..0f136ae --- /dev/null +++ b/docs/security-bootstrap-openbao-ceremony-ux.md @@ -0,0 +1,117 @@ +# OpenBao Ceremony UX + +Status: draft UX contract +Date: 2026-05-24 + +## Purpose + +This document translates the Railiance OpenBao ceremony into a guided UX. It is +the product contract for `NET-WP-0016-T05`. + +The UI must make unsafe live initialization difficult to do accidentally. The +first prototype should not execute `bao operator init`; it should show +readiness, blocked gates, and the manual ceremony steps. + +## Ceremony States + +| State | Meaning | +| --- | --- | +| `not-ready` | Required gates are missing | +| `preflight-ready` | Safe checks pass, but human ceremony still required | +| `human-ceremony` | Operator is running the live init/unseal outside automation | +| `post-unseal-config` | Audit, mounts, auth, and policies are being configured | +| `root-disposition` | Root token is revoked or sealed offline | +| `restore-required` | Snapshot/restore drill still blocks live secrets | +| `ready-for-handover` | OpenBao is ready for cleanup and custody handover | + +## Readiness Gates + +Live initialization is blocked unless: + +- king credential kit is complete; +- custody mode is selected; +- offline custody packet is prepared; +- OpenBao pod and PVC preflight passes; +- OpenBao reports `Initialized: false` and `Sealed: true`; +- operator has acknowledged no secret output enters unsafe channels; +- maintenance window is deliberate; and +- human ceremony mode is selected. + +## Preflight Panel + +The OpenBao panel should show: + +| Check | Source | +| --- | --- | +| Pod status | `make openbao-status` | +| PVC status | `make openbao-status` | +| Initialized/sealed state | `bao status` through Railiance target | +| Helper script readiness | `make openbao-verify` | +| Custody gates | NetKingdom bootstrap metadata | +| Restore gate | State Hub task/progress evidence | + +The UI should show command output as plain text and mark any unknown result as +unknown, not success. + +## Human Ceremony Guide + +When ready, the UI prints a guide: + +1. Confirm no screen recording or shared terminal logging is active. +2. Confirm the offline custody packet is physically ready. +3. Run the Railiance command manually in the approved shell. +4. Route each unseal share directly to its approved custody location. +5. Unseal by prompt, not by command-line argument. +6. Use the root token only for first configuration. +7. Run initial configuration helper. +8. Create non-root admin path. +9. Revoke or offline-seal the root token. +10. Record only non-secret evidence. + +The UI must not ask the operator to paste unseal shares or root tokens. + +## Post-Unseal Configuration + +The UI may guide these safe commands: + +```bash +make openbao-configure-initial +make openbao-verify-post-unseal +``` + +Token entry must remain prompt-based or local-file based as implemented by +Railiance helpers. The UI should not store or echo the token. + +## Evidence Record + +The UI may record: + +- ceremony date; +- custody mode; +- OpenBao initialized status; +- audit enabled status; +- initial mounts/policies loaded; +- root-token disposition category: `revoked`, `offline-sealed`, or `deferred`; +- snapshot taken status; +- restore drill status; and +- operator/contact. + +It must not record secret values. + +## Failure Handling + +If init output is accidentally exposed: + +1. Stop the ceremony. +2. Mark the ceremony as compromised. +3. Do not migrate live secrets. +4. Rotate/rekey according to OpenBao procedure before proceeding. +5. Record non-secret incident evidence. + +If post-unseal configuration fails: + +1. Keep root token handling in human custody. +2. Do not create live secrets. +3. Fix the configuration issue. +4. Rerun the helper or manual command. +5. Record the failed step without secret output. diff --git a/docs/security-bootstrap-operator-journey.md b/docs/security-bootstrap-operator-journey.md new file mode 100644 index 0000000..e56757d --- /dev/null +++ b/docs/security-bootstrap-operator-journey.md @@ -0,0 +1,192 @@ +# Security Bootstrap Operator Journey + +Status: draft UX contract +Date: 2026-05-24 + +## Purpose + +This document defines the first operator journey for the guided security +bootstrap experience. It is the product contract for `NET-WP-0016-T02`. + +The first interface may be a CLI or local web console, but the interaction +model should already be UI-shaped: clear state, blocked gates, next safe +action, and quiet instructions that do not assume the operator is an OpenBao or +IAM expert. + +## Design System + +The UI should use `whynot-design` where a visual surface is built. + +Required visual posture: + +- mostly black and white, using `--hi` only as a highlighter or signal marker; +- 1px hairlines, document-like panels, generous whitespace; +- sentence case for labels, headings, and buttons; +- uppercase mono only for short stage/status eyebrows; +- no gradients, decorative imagery, card shadows, scale transforms, or hype + copy; +- square or lightly rounded panels; pills only for labels; and +- Lucide-style icons only when they speed recognition. + +The console should feel like a field notebook and control surface, not a SaaS +dashboard. The security work is serious, but the interface should stay calm. + +## Shell + +The first screen always answers four questions: + +| Area | Content | +| --- | --- | +| Trust stage | Current stage, e.g. `S1 - Low-trust assembly` | +| Next safe action | One primary action that can be done now | +| Blocked gates | Actions that are blocked and why | +| Evidence | Non-secret records already present | + +Suggested first screen: + +```text +SECURITY BOOTSTRAP + +Stage +S1 - Low-trust assembly + +Next safe action +Define king credential kit + +Blocked gates +- OpenBao init: king credential is not prepared +- Live secrets: restore drill is not complete +- Reopen platform: bootstrap credentials have not been rotated + +Available actions +1. Review setup operator +2. Define king credential kit +3. Run OpenBao preflight +4. Print custody packet template +5. Show handover checklist +``` + +Only one action should be visually primary. Everything else is secondary, +status-only, or explicitly blocked. + +## Navigation + +Top-level areas: + +| Area | Purpose | +| --- | --- | +| Overview | Trust stage, next action, blocked gates | +| King credential | Dedicated root credential kit and custody posture | +| Users | Onboard, lock, offboard, review credentials | +| OpenBao | Preflight, ceremony readiness, post-unseal checks | +| Fabrics | New fabric setup and admin handover | +| Handover | Reset, rotate, scan, restore, reopen | +| Audit | Non-secret evidence and progress records | + +The first prototype has CLI sections plus a localhost custody-approval UI. A +larger web UI should use a left rail with short labels and a sticky top +hairline header. + +## Action Model + +Each action has a typed shape: + +| Field | Meaning | +| --- | --- | +| Name | Short sentence-case label | +| Stage | Earliest trust stage where action is valid | +| Preconditions | Required non-secret facts | +| Warnings | Plain-language risk note | +| Inputs | Non-secret inputs only | +| Secret handling | Where the secret is created, displayed, or explicitly not handled | +| Result | Non-secret evidence recorded | +| Undo/recovery | What to do if the action was wrong or incomplete | + +Actions that would handle root material must default to blocked until the UI can +prove the prerequisites. The first prototype should not run live `bao operator +init`; it should only report readiness and print the manual ceremony guide. + +## Gate Model + +Gate status values: + +| Value | Meaning | +| --- | --- | +| `ready` | Safe to perform now | +| `blocked` | Required facts are missing | +| `human` | Requires deliberate human ceremony or approval | +| `done` | Completed and recorded | +| `deferred` | Intentionally postponed | + +Avoid red/green severity colors. Use labels, ordering, and text. If color is +used, stay in the whynot grey ramp; reserve yellow for a selected or +human-required marker. + +## Required Gates + +OpenBao live initialization is blocked until: + +- king credential kit is defined; +- custody mode is selected; +- offline recovery storage is prepared; +- OpenBao preflight passes; +- setup operator understands no secret output may enter Git, State Hub, chat, + tickets, email, screenshots, or shell history; and +- the run is human-attended. + +Platform reopen is blocked until: + +- OpenBao root token is revoked or sealed offline; +- non-root admin path exists; +- bootstrap-era credentials are reset or rotated; +- host/workload checks are complete or explicitly deferred; +- backup and restore drill are complete; and +- audit sinks are known. + +## Data Sources + +The first console can read: + +| Source | Use | +| --- | --- | +| State Hub REST | workstream/task status, decisions, progress events | +| `net-kingdom` docs | custody model and use-case definitions | +| `railiance-platform` make targets | OpenBao preflight and post-unseal checks | +| local operator metadata | non-secret answers such as custody mode and kit label | + +Local metadata should be non-secret and should live outside Git by default. If +a repo file is needed, it must be a template or example only. + +## Content Rules + +The UI should use short declarative text: + +- "King credential is not prepared." +- "OpenBao init is blocked." +- "Run preflight from `railiance-platform`." +- "Do not paste secret output anywhere." + +Avoid moralizing or dramatic language. Good security here should feel like a +clear checklist with good defaults. + +## First Prototype Boundary + +The first prototype should: + +- show trust stage and gates; +- print the king credential kit checklist; +- run safe OpenBao preflight commands or explain why they cannot run; +- generate a non-secret custody packet template; +- list user lifecycle actions as guided flows; +- list handover cleanup gates; and +- refuse live OpenBao init. + +The first prototype should not: + +- store passwords, root tokens, unseal shares, OTP seeds, recovery codes, or + private keys; +- call `bao operator init`; +- send email; +- alter IAM users; +- rotate live databases automatically; or +- hide uncertainty behind a green check. diff --git a/docs/security-bootstrap-related-workplan-review.md b/docs/security-bootstrap-related-workplan-review.md new file mode 100644 index 0000000..c58f8bb --- /dev/null +++ b/docs/security-bootstrap-related-workplan-review.md @@ -0,0 +1,56 @@ +# Security Bootstrap Related Workplan Review + +Status: closeout review for `NET-WP-0016` +Date: 2026-05-24 + +## Purpose + +This review closes `NET-WP-0016-T08`. It classifies related NetKingdom and +Railiance workplans after the guided security bootstrap experience became the +canonical operator-facing path. + +## Review Results + +| Workplan | Result | Action | +| --- | --- | --- | +| `NK-WP-0001` SSO/MFA Platform | Retired historical reference | Leave archived. Its HashiCorp Vault and single-credential language is historical only. Active paths are `NK-WP-0003`, `NK-WP-0011`, `RAIL-PL-WP-0002`, `NET-WP-0015`, and `NET-WP-0016`. | +| `NK-WP-0004` Credential Management Foundation | Keep as low-level bootstrap foundation | Added closeout note. SOPS/age and generated bundles remain useful substrate tooling, but the operator-facing path is now the guided bootstrap experience. | +| `NK-WP-0005` Agent-Driven Credential Bootstrap | Keep as automation substrate, supersede as product UX | Added closeout note. Agent automation remains useful, but "zero human ops" must not apply to king custody or live OpenBao init. | +| `NK-WP-0006` Recursive Platform Identity And Security Architecture | Keep | Already aligned with platform-root, OpenBao, and tenant boundary model. No retirement. | +| `NK-WP-0007` Object Storage STS Credential Vending | Keep | Already prevents OpenBao root/admin authority from becoming storage policy. No retirement. | +| `NK-WP-0011` Enterprise Federation And SAML | Keep proposed | Expanded-mode Keycloak should consume OpenBao and king-custody gates; no bootstrap ownership moves here. | +| `NET-WP-0015` King Credential And OpenBao Identity Bootstrap | Keep active | Continues the concrete king credential, custody mode, OpenBao ceremony, and reopen work. | +| `RAIL-PL-WP-0002` OpenBao Platform Secrets Service | Keep active | Updated stale `human:tegwick` root-custodian wording to the setup-operator plus king-credential model. | +| `RAILIANCE-WP-0003` Apps PostgreSQL Shared Cluster | Keep active | Bootstrap DB role remains acceptable as platform substrate, but handover cleanup must rotate or review bootstrap-era credentials before live use. | + +## Retired Assumptions + +- A day-to-day Gitea/email identity is not platform root of trust. +- "Zero human ops" does not apply to king credential custody. +- HashiCorp Vault is not the target runtime secret authority. +- KeePassXC is optional personal/offline storage, not the canonical platform + authority. +- Temporary bootstrap credentials are not production credentials. + +## Current Canonical Path + +1. Low-trust setup operator assembles infrastructure. +2. Guided bootstrap console shows stage, gates, next safe action, and local + custody-mode approval. +3. King credential kit is created or imported. +4. OpenBao ceremony is run as a human-attended event. +5. Root token is revoked or sealed offline. +6. Bootstrap-era credentials and access paths are reset or rotated. +7. Restore, audit, and scan/check gates pass. +8. Platform reopens under king credential oversight. +9. Multi-custodian control is added later without redesign. + +## Follow-Ups + +- `NET-WP-0015` remains the active place for king credential creation and live + OpenBao ceremony gates. +- `NET-WP-0016` remains closed; `NET-WP-0015` now carries the live approval and + OpenBao ceremony gates. +- The first local web UI exists as the custody approval surface. Later product + work should extend it into the full user, fabric, audit, and handover console + only after the first ceremony has been exercised. diff --git a/docs/security-bootstrap-use-cases.md b/docs/security-bootstrap-use-cases.md new file mode 100644 index 0000000..0302080 --- /dev/null +++ b/docs/security-bootstrap-use-cases.md @@ -0,0 +1,206 @@ +# Security Bootstrap Use Cases + +Status: draft product/security model +Date: 2026-05-24 + +## Purpose + +This document defines the operator-facing use cases for bootstrapping +NetKingdom security infrastructure and Railiance OpenBao custody. The goal is a +guided experience that makes the secure path the easy path. + +Early infrastructure may be assembled by an anonymous or low-assurance setup +operator. That operator can deploy, observe, and prepare the system, but must +not become the permanent root of trust by accident. The dedicated king +credential accepts custody later, then the platform is reset, rotated, checked, +and reopened under explicit oversight. + +## Design Principles + +- No secret value is stored in Git, State Hub, chat, tickets, screenshots, or + email. +- Email may notify; it must not carry root secrets or recovery material. +- Day-to-day accounts can operate setup tooling but should not hold master + custody. +- Every dangerous step has plain language, a checklist, a confirmation, and a + rollback or recovery explanation. +- The UI should show trust stage, current gates, and next safe action. +- The system should be useful before all future security components exist. +- Two-of-three custody should be an upgrade path, not a redesign. + +## Trust Stages + +| Stage | Name | UX posture | +| --- | --- | --- | +| S0 | MVP/prototype | Warn that state is not clean enough for live secrets. | +| S1 | Low-trust assembly | Allow setup by a low-assurance operator; block live custody actions. | +| S2 | King credential preparation | Guide creation/import, second factor, and offline recovery. | +| S3 | OpenBao bootstrap | Guide init/unseal/configure without exposing secret output. | +| S4 | Cleanup and hardening | Reset/rotate bootstrap-era state and run checks. | +| S5 | Reopen under custody | Enable delegated admin/user operations under king oversight. | +| S6 | Multi-custodian upgrade | Add independent custody and rekey/recovery drills. | + +## Use Cases + +### A. Set Up King Credential + +Goal: create or import the dedicated platform-root credential independently of +day-to-day accounts. + +UX requirements: + +- explain why the king credential is separate from Gitea/email; +- guide password-safe or offline storage choice; +- guide OTP or hardware-backed second factor setup through the verifying + authority, without generating or storing seed material in the bootstrap UI; +- verify recovery codes exist without collecting them; +- record only non-secret metadata: credential label, creation date, custody + posture, and notification contact; and +- require a deliberate confirmation before OpenBao bootstrap can proceed. + +### B. Onboard New User + +Goal: add a user with scoped day-to-day access. + +UX requirements: + +- identify whether the user is a setup operator, platform admin, tenant admin, + reviewer, or workload/service principal; +- map requested access to IAM groups and OpenBao/flex-auth scopes; +- require MFA where role sensitivity demands it; +- avoid granting platform-root authority through ordinary onboarding; and +- produce an audit record and review date. + +### C. Temporarily Lock User + +Goal: suspend access without deleting identity history. + +UX requirements: + +- disable login/session/token issuance; +- revoke active sessions and temporary tokens where supported; +- preserve audit identity and ownership records; +- make unlock path explicit; and +- show dependent service accounts or keys that may be affected. + +### D. Permanently Lock And Offboard User + +Goal: remove a user from operational access while preserving audit evidence. + +UX requirements: + +- revoke sessions, tokens, app passwords, SSH keys, and OpenBao tokens; +- transfer owned resources or service principals; +- remove groups/roles and tenant memberships; +- keep immutable audit records; +- schedule credential rotation for any shared material the user may have seen; + and +- require a second confirmation for platform/admin users. + +### E. Review And Change User Credentials + +Goal: inspect credential posture and rotate or reset safely. + +UX requirements: + +- show MFA state, recovery-code freshness, active tokens, SSH keys, and role + memberships; +- separate "rotate credential" from "change authorization"; +- offer a safe emergency reset flow; +- record non-secret evidence of completion; and +- never reveal credential values. + +### F. Set Up New Fabric With Its Own Admin + +Goal: create a new fabric or deployment with delegated admin control. + +UX requirements: + +- create fabric identity and admin boundary; +- bind admin claims to that fabric without granting platform-root; +- create OpenBao path prefixes and policies; +- create audit routing and backup expectations; +- document handover to the fabric admin; and +- make transfer/revocation path clear. + +### G. Bootstrap OpenBao + +Goal: initialize OpenBao after the king credential is prepared. + +UX requirements: + +- verify OpenBao pod, PVCs, initialization state, and backup posture; +- show the exact trust stage and why live init is or is not allowed; +- guide key-share/threshold choice; +- avoid printing or storing secret output where possible; +- prompt for manual custody actions out-of-band; +- configure audit, initial mounts, and non-root admin path; and +- require root-token revoke or offline escrow disposition. + +### H. Transfer Custody To King Credential + +Goal: move from low-trust setup to explicit platform-root control. + +UX requirements: + +- verify king credential login and second factor; +- verify root-token disposition and OpenBao admin path; +- rotate bootstrap credentials and admin passwords; +- reset databases or service credentials created during MVP/prototype mode; +- run host/workload checks and vulnerability scans where available; +- complete backup/restore drill; and +- only then mark the platform reopened under custody. + +### I. Add Multi-Custodian Control + +Goal: upgrade from single king custody to two-of-three or equivalent. + +UX requirements: + +- guide custodian invitation without sending secrets by email; +- explain share/threshold consequences in plain language; +- perform OpenBao rekey or recovery-key update when appropriate; +- record escrow holders without recording secret shares; +- run a recovery drill; and +- keep single-custodian fallback only as an explicit exception. + +## First Interface Shape + +The first version can be a local operator console rather than a full web app. +It should be command-driven but humane: + +```text +Security Bootstrap Console + +Trust stage: S1 - Low-trust assembly +Next safe action: Create or import king credential + +Blocked: +- OpenBao init: king credential not prepared +- Live secrets: OpenBao not initialized and restore drill not complete + +Available: +- Review setup operator +- Generate king credential checklist +- Run OpenBao preflight +- Print offline custody packet template +``` + +As the flow matures, it can become a local web UI that ties together +NetKingdom identity, OpenBao status, user lifecycle, custody posture, audit +events, and fabric onboarding. + +The visual language should follow `whynot-design`: quiet document surfaces, +sentence-case labels, mostly black and white, one yellow highlighter accent, +1px hairlines, no gradients, no card shadows, and no marketing copy. Security +state should be legible through labels and structure, not decorative color. + +## Non-Goals For The First Version + +- no secret manager UI that displays secret values; +- no automated email delivery of credentials; +- no unattended OpenBao initialization; +- no permanent trust in MVP bootstrap accounts; +- no tenant-admin path to platform-root authority; and +- no dependency on full Keycloak/federation before the bootstrap console is + useful. diff --git a/docs/security-bootstrap-user-lifecycle.md b/docs/security-bootstrap-user-lifecycle.md new file mode 100644 index 0000000..49f308e --- /dev/null +++ b/docs/security-bootstrap-user-lifecycle.md @@ -0,0 +1,146 @@ +# Security Bootstrap User Lifecycle + +Status: draft UX contract +Date: 2026-05-24 + +## Purpose + +This document defines the first guided user lifecycle flows for the security +bootstrap experience. It is the product contract for `NET-WP-0016-T04`. + +The goal is to make common access operations clear without granting platform +root by accident. + +## Actor Classes + +| Class | Meaning | Root risk | +| --- | --- | --- | +| Setup operator | Can assemble or observe early infrastructure | Must not imply root custody | +| Platform admin | Day-to-day delegated platform administration | Scoped and revocable | +| Tenant admin | Admin for one tenant or fabric | No platform root | +| Reviewer | Read-only inspection and audit role | No secret reads by default | +| Workload principal | Service account or automation identity | Least privilege | +| King credential | Rare platform-root custody | Break-glass only | + +The UI must always distinguish actor class before granting access. + +## Onboard User + +Inputs: + +- display name; +- contact address; +- actor class; +- tenant or fabric scope; +- requested groups/roles; +- MFA requirement; +- review date. + +Flow: + +1. Select actor class. +2. Select scope. +3. Show effective privileges before creation. +4. Highlight any platform-admin or root-adjacent role. +5. Require MFA for privileged roles. +6. Create or prepare identity in the selected IAM provider. +7. Record non-secret audit event. + +Blocked conditions: + +- actor class is missing; +- scope is missing for tenant/fabric roles; +- privileged role without MFA; +- ordinary onboarding tries to grant king custody. + +## Temporarily Lock User + +Purpose: suspend access without deleting identity history. + +Flow: + +1. Select user. +2. Show active groups, roles, sessions, keys, tokens, and owned resources where + available. +3. Disable login or token issuance. +4. Revoke active sessions and short-lived tokens where supported. +5. Preserve audit subject and ownership records. +6. Record unlock instructions and review date. + +The UI should label this as reversible. + +## Permanently Lock And Offboard User + +Purpose: remove operational access while preserving audit evidence. + +Flow: + +1. Select user. +2. Require reason and effective date. +3. Transfer owned resources or service principals. +4. Revoke sessions, tokens, app passwords, SSH keys, and OpenBao tokens. +5. Remove groups, roles, and tenant memberships. +6. Schedule rotation for shared material the user may have seen. +7. Record non-secret offboarding evidence. + +Platform-admin offboarding requires a second confirmation. King credential +offboarding is not a normal lifecycle action; it is a custody replacement +ceremony. + +## Review And Change Credentials + +Purpose: inspect posture and rotate safely. + +The review screen should show: + +- MFA state; +- recovery confirmation age; +- SSH keys; +- active tokens; +- group and role memberships; +- last review date; +- owned service principals; and +- rotation recommendations. + +Actions: + +| Action | Meaning | +| --- | --- | +| Rotate credential | Replace a secret or key | +| Reset credential | Emergency replacement | +| Change authorization | Add/remove roles or groups | +| Schedule review | Set next review date | + +The UI must keep rotation separate from authorization changes. + +## New Fabric With Its Own Admin + +Purpose: create a fabric with delegated administration but no platform-root +authority. + +Flow: + +1. Name the fabric. +2. Assign fabric admin. +3. Create IAM scope and group mapping. +4. Create OpenBao path prefix and policy request. +5. Define audit and backup expectations. +6. Produce a handover checklist. +7. Record non-secret progress event. + +Blocked conditions: + +- fabric admin missing; +- platform-root role requested; +- no OpenBao path prefix; +- no review date. + +## UX Rules + +- Show effective access before saving. +- Use plain labels: "locked", "offboarded", "needs review". +- Do not use red/yellow/green as the only indicator. +- Do not display secret values. +- Do not send secrets by email. +- Keep every high-risk action reversible where possible, or explain why it is + not reversible. diff --git a/examples/security-bootstrap/king-credential-metadata.example.json b/examples/security-bootstrap/king-credential-metadata.example.json new file mode 100644 index 0000000..559862d --- /dev/null +++ b/examples/security-bootstrap/king-credential-metadata.example.json @@ -0,0 +1,30 @@ +{ + "credential_label": "platform-root", + "setup_operator": "tegwick", + "notification_contact": "bernd.worsch@gmail.com", + "storage_classes": [ + "password-safe", + "offline-packet" + ], + "mfa_class": "totp", + "mfa_enrolled_confirmed": false, + "mfa_enrollment_source": "deferred", + "mfa_enrollment_reference": "", + "recovery_confirmed": false, + "custody_packet_prepared": false, + "no_secret_capture_confirmed": false, + "king_credential_ready": false, + "custody_mode": "", + "custody_mode_approved": false, + "custody_approved_at": "", + "custody_approved_by": "", + "approval_scope": "", + "openbao_preflight_passed": false, + "openbao_initialized": false, + "root_token_disposition": "", + "restore_drill_passed": false, + "cleanup_complete": false, + "platform_reopened": false, + "review_date": "", + "notes": "Non-secret metadata only. Do not store passwords, OTP seeds, recovery codes, private keys, OpenBao root tokens, or unseal shares here." +} diff --git a/tools/security-bootstrap-console/README.md b/tools/security-bootstrap-console/README.md new file mode 100644 index 0000000..67caec7 --- /dev/null +++ b/tools/security-bootstrap-console/README.md @@ -0,0 +1,97 @@ +# Security Bootstrap Console + +Local console and localhost web UI for the NetKingdom guided security bootstrap +experience. + +The console prints trust stage, gates, checklists, non-secret templates, and can +write an explicit custody-mode approval record. It does not collect secret +values and refuses live OpenBao initialization. + +Run: + +```bash +python3 tools/security-bootstrap-console/security_bootstrap_console.py status +``` + +Print the king credential kit checklist: + +```bash +python3 tools/security-bootstrap-console/security_bootstrap_console.py king-kit +``` + +Validate non-secret kit metadata: + +```bash +python3 tools/security-bootstrap-console/security_bootstrap_console.py \ + --metadata /tmp/security-bootstrap.json \ + validate-king-kit +``` + +Approve custody mode from the CLI: + +```bash +python3 tools/security-bootstrap-console/security_bootstrap_console.py \ + --metadata /tmp/security-bootstrap.json \ + approve-custody-mode \ + --mode temporary-single-king \ + --mfa-enrolled-confirmed \ + --mfa-enrollment-source identity-provider \ + --recovery-confirmed \ + --custody-packet-prepared \ + --no-secret-capture-confirmed +``` + +The command asks for the phrase `approve custody mode` unless `--yes` is passed. +`two-of-three-planned` can be recorded in metadata but cannot approve live +OpenBao init. + +For TOTP, use the QR code or setup key from the identity provider or other +authority that will verify the login. This tool records only the non-secret +enrollment confirmation and source. + +Serve the local approval UI: + +```bash +python3 tools/security-bootstrap-console/security_bootstrap_console.py \ + --metadata /tmp/security-bootstrap.json \ + web-ui +``` + +Open `http://127.0.0.1:8765`. + +Print a blank offline custody packet template: + +```bash +python3 tools/security-bootstrap-console/security_bootstrap_console.py custody-packet +``` + +Show safe OpenBao preflight commands: + +```bash +python3 tools/security-bootstrap-console/security_bootstrap_console.py openbao-preflight \ + --railiance-path ../railiance-platform +``` + +Run safe OpenBao preflight targets: + +```bash +python3 tools/security-bootstrap-console/security_bootstrap_console.py openbao-preflight \ + --railiance-path ../railiance-platform \ + --run +``` + +This still does not run `bao operator init`. + +Optional non-secret metadata can be supplied: + +```bash +python3 tools/security-bootstrap-console/security_bootstrap_console.py metadata-template \ + > /tmp/security-bootstrap.json + +python3 tools/security-bootstrap-console/security_bootstrap_console.py \ + --metadata /tmp/security-bootstrap.json \ + status +``` + +Do not put passwords, OTP seeds, OpenBao root tokens, unseal shares, recovery +codes, private keys, or screenshots of secret output into the metadata file. diff --git a/tools/security-bootstrap-console/security_bootstrap_console.py b/tools/security-bootstrap-console/security_bootstrap_console.py new file mode 100755 index 0000000..60736bf --- /dev/null +++ b/tools/security-bootstrap-console/security_bootstrap_console.py @@ -0,0 +1,1259 @@ +#!/usr/bin/env python3 +"""Non-secret security bootstrap console. + +This tool is intentionally conservative. It prints status, gates, checklists, +and templates. It does not collect or store secret values, and it refuses to run +live OpenBao initialization. +""" + +from __future__ import annotations + +import argparse +import html +import json +import subprocess +import sys +from dataclasses import dataclass +from datetime import datetime, timezone +from http import HTTPStatus +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from typing import Any + + +DEFAULT_STAGE = "S1 - Low-trust assembly" +DEFAULT_METADATA_PATH = Path("/tmp/net-kingdom-security-bootstrap.json") +APPROVAL_PHRASE = "approve custody mode" +VALID_STORAGE_CLASSES = {"password-safe", "offline-packet", "hardware-token"} +VALID_MFA_CLASSES = {"totp", "webauthn", "hardware-token"} +VALID_MFA_ENROLLMENT_SOURCES = { + "identity-provider", + "external-verifier", + "hardware-registration", + "deferred", +} +VALID_CUSTODY_MODES = {"temporary-single-king", "two-of-three-planned", "two-of-three-ready"} +CUSTODY_APPROVAL_MODES = {"temporary-single-king", "two-of-three-ready"} + + +@dataclass(frozen=True) +class Gate: + name: str + status: str + reason: str + + +def load_metadata(path: Path | None) -> dict[str, Any]: + if path is None or not path.exists(): + return {} + try: + data = json.loads(path.read_text()) + except json.JSONDecodeError as exc: + raise SystemExit(f"metadata is not valid JSON: {path}: {exc}") from exc + if not isinstance(data, dict): + raise SystemExit(f"metadata root must be an object: {path}") + return data + + +def write_metadata(path: Path, data: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + tmp_path = path.with_name(f".{path.name}.tmp") + tmp_path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n") + tmp_path.replace(path) + + +def utc_now() -> str: + return datetime.now(timezone.utc).replace(microsecond=0).isoformat() + + +def normalize_storage_classes(value: Any) -> list[str]: + if isinstance(value, str): + raw_values = [item.strip() for item in value.split(",")] + elif isinstance(value, list): + raw_values = [str(item).strip() for item in value] + else: + raw_values = [] + values = [item for item in raw_values if item] + return sorted(set(values)) + + +def yes(data: dict[str, Any], key: str) -> bool: + return data.get(key) is True + + +def second_factor_ready(data: dict[str, Any]) -> bool: + return ( + data.get("mfa_class") in VALID_MFA_CLASSES + and yes(data, "mfa_enrolled_confirmed") + and data.get("mfa_enrollment_source") in VALID_MFA_ENROLLMENT_SOURCES - {"deferred"} + ) + + +def second_factor_reason(data: dict[str, Any]) -> str: + if data.get("mfa_class") not in VALID_MFA_CLASSES: + return "Select TOTP, WebAuthn, or hardware-token." + if data.get("mfa_enrollment_source") == "deferred": + return "Deferred factor enrollment blocks live OpenBao custody." + if not yes(data, "mfa_enrolled_confirmed"): + return "Confirm the factor was enrolled with the authority that will verify it; record no seed." + if data.get("mfa_enrollment_source") not in VALID_MFA_ENROLLMENT_SOURCES: + return "Record the non-secret enrollment source." + return "Second factor enrollment is confirmed without recording seed material." + + +def kit_validation(data: dict[str, Any]) -> list[Gate]: + storage_classes = data.get("storage_classes", []) + if not isinstance(storage_classes, list): + storage_classes = [] + storage_values = {str(item) for item in storage_classes} + custody_mode = data.get("custody_mode", "") + return [ + Gate( + "Credential label", + "done" if data.get("credential_label") else "blocked", + "Use a dedicated label such as platform-root.", + ), + Gate( + "Setup operator/contact", + "done" if data.get("setup_operator") and data.get("notification_contact") else "blocked", + "Record non-secret setup operator and notification contact.", + ), + Gate( + "Storage class", + "done" if storage_values & VALID_STORAGE_CLASSES else "blocked", + "Select password-safe, offline-packet, hardware-token, or a combination.", + ), + Gate( + "Second factor", + "done" if second_factor_ready(data) else "blocked", + second_factor_reason(data), + ), + Gate( + "Recovery material", + "done" if yes(data, "recovery_confirmed") else "blocked", + "Confirm recovery material exists without recording values.", + ), + Gate( + "Custody packet", + "done" if yes(data, "custody_packet_prepared") else "blocked", + "Prepare the offline custody packet.", + ), + Gate( + "No secret capture", + "done" if yes(data, "no_secret_capture_confirmed") else "blocked", + "Confirm no secret values were stored in metadata, Git, State Hub, chat, tickets, or email.", + ), + Gate( + "Custody mode", + "done" if custody_mode in VALID_CUSTODY_MODES else "blocked", + "Approve temporary-single-king, two-of-three-planned, or two-of-three-ready.", + ), + ] + + +def king_kit_ready(data: dict[str, Any]) -> bool: + gates = kit_validation(data) + required = [gate for gate in gates if gate.name != "Custody mode"] + return all(gate.status == "done" for gate in required) + + +def custody_mode_approved(data: dict[str, Any]) -> bool: + return data.get("custody_mode") in CUSTODY_APPROVAL_MODES and yes(data, "custody_mode_approved") + + +def custody_mode_reason(data: dict[str, Any]) -> str: + mode = data.get("custody_mode") + if mode in CUSTODY_APPROVAL_MODES and yes(data, "custody_mode_approved"): + return "Approved for the next gate under the selected custody mode." + if mode == "two-of-three-planned": + return "Two-of-three is recorded as the target, but live init stays blocked until it is ready." + if mode in CUSTODY_APPROVAL_MODES and not yes(data, "custody_mode_approved"): + return "Mode is selected but not yet explicitly approved." + return "Choose temporary-single-king or two-of-three-ready for live OpenBao custody." + + +def derive_stage(data: dict[str, Any]) -> str: + if yes(data, "platform_reopened"): + return "S5 - Reopen under custody" + if yes(data, "cleanup_complete"): + return "S4 - Cleanup and hardening" + if yes(data, "openbao_initialized"): + return "S3 - OpenBao bootstrap" + if yes(data, "king_credential_ready") or king_kit_ready(data): + return "S2 - King credential preparation" + return DEFAULT_STAGE + + +def build_gates(data: dict[str, Any]) -> list[Gate]: + return [ + Gate( + "King credential kit", + "done" if yes(data, "king_credential_ready") or king_kit_ready(data) else "blocked", + "Dedicated king credential, second factor, and recovery storage.", + ), + Gate( + "Custody mode", + "done" if custody_mode_approved(data) else "blocked", + custody_mode_reason(data), + ), + Gate( + "OpenBao preflight", + "done" if yes(data, "openbao_preflight_passed") else "blocked", + "Run safe Railiance OpenBao status and verification checks.", + ), + Gate( + "OpenBao init ceremony", + "human" if not yes(data, "openbao_initialized") else "done", + "Human-attended ceremony only. This console will not run init.", + ), + Gate( + "Root-token disposition", + "done" if data.get("root_token_disposition") in {"revoked", "offline-sealed"} else "blocked", + "Root token is revoked or sealed offline without recording value.", + ), + Gate( + "Restore drill", + "done" if yes(data, "restore_drill_passed") else "blocked", + "Snapshot and isolated restore proof before live secrets.", + ), + Gate( + "Cleanup and rotation", + "done" if yes(data, "cleanup_complete") else "blocked", + "Bootstrap-era credentials, databases, and access paths reviewed.", + ), + ] + + +def next_action(gates: list[Gate]) -> str: + for gate in gates: + if gate.status == "blocked": + if gate.name == "King credential kit": + return "Define king credential kit" + if gate.name == "Custody mode": + return "Choose custody mode" + if gate.name == "OpenBao preflight": + return "Run OpenBao preflight" + if gate.name == "Root-token disposition": + return "Record root-token disposition" + if gate.name == "Restore drill": + return "Run restore drill" + if gate.name == "Cleanup and rotation": + return "Complete handover cleanup" + return "Review related workplans" + + +def print_status(data: dict[str, Any]) -> None: + gates = build_gates(data) + print("SECURITY BOOTSTRAP") + print("") + print("Stage") + print(derive_stage(data)) + print("") + print("Next safe action") + print(next_action(gates)) + print("") + print("Gates") + for gate in gates: + print(f"- {gate.status}: {gate.name} - {gate.reason}") + print("") + print("Available actions") + print("1. king-kit") + print("2. custody-packet") + print("3. openbao-preflight") + print("4. handover-checklist") + print("5. metadata-template") + print("6. approve-custody-mode") + print("7. web-ui") + print("") + print("Refusal boundary") + print("This console will not run bao operator init or collect secret values.") + + +def print_king_kit() -> None: + print("KING CREDENTIAL KIT") + print("") + rows = [ + "Name the credential, for example platform-root.", + "Choose storage: password safe, offline packet, hardware-backed, or a combination.", + "Add a second factor: TOTP, WebAuthn, or hardware token.", + "Prepare recovery material without recording values in software.", + "Select custody mode: temporary-single-king, two-of-three-planned, or two-of-three-ready.", + "Print or prepare the offline custody packet.", + "Record only non-secret metadata.", + ] + for index, row in enumerate(rows, start=1): + print(f"{index}. {row}") + + +def print_validate_king_kit(data: dict[str, Any]) -> int: + print("KING CREDENTIAL KIT VALIDATION") + print("") + if not data: + print("No metadata loaded. Use --metadata with a non-secret JSON file.") + print("Run metadata-template for the expected shape.") + return 2 + gates = kit_validation(data) + for gate in gates: + print(f"- {gate.status}: {gate.name} - {gate.reason}") + print("") + if king_kit_ready(data) and custody_mode_approved(data): + print("Kit definition and custody-mode approval are complete.") + print("Live OpenBao init remains a separate human-attended ceremony.") + return 0 + if king_kit_ready(data): + print("Kit definition is complete except custody-mode approval.") + print("Live OpenBao init is still blocked until T03 approves custody mode.") + return 0 + print("Kit definition is incomplete.") + return 1 + + +def merged_approval_metadata( + existing: dict[str, Any], + payload: dict[str, Any], +) -> dict[str, Any]: + data = metadata_template() + data.update(existing) + text_fields = ( + "credential_label", + "setup_operator", + "notification_contact", + "mfa_class", + "mfa_enrollment_source", + "mfa_enrollment_reference", + "custody_mode", + "notes", + ) + for field in text_fields: + if field in payload and payload[field] is not None: + data[field] = str(payload[field]).strip() + if "storage_classes" in payload: + data["storage_classes"] = normalize_storage_classes(payload["storage_classes"]) + for field in ( + "recovery_confirmed", + "custody_packet_prepared", + "no_secret_capture_confirmed", + "mfa_enrolled_confirmed", + ): + if field in payload: + data[field] = payload[field] is True + return data + + +def validate_custody_approval( + data: dict[str, Any], + approval_phrase: str, +) -> list[str]: + errors: list[str] = [] + mode = data.get("custody_mode") + if approval_phrase.strip().lower() != APPROVAL_PHRASE: + errors.append(f'Type "{APPROVAL_PHRASE}" to approve custody mode.') + if mode not in VALID_CUSTODY_MODES: + errors.append("Select a custody mode.") + elif mode not in CUSTODY_APPROVAL_MODES: + errors.append( + "two-of-three-planned is a target state, not live-init approval. " + "Use temporary-single-king now or two-of-three-ready when shares exist." + ) + for gate in kit_validation(data): + if gate.name == "Custody mode": + continue + if gate.status != "done": + errors.append(f"{gate.name}: {gate.reason}") + return errors + + +def approve_custody_metadata( + existing: dict[str, Any], + payload: dict[str, Any], + approval_phrase: str, + approver: str, +) -> tuple[dict[str, Any], list[str]]: + data = merged_approval_metadata(existing, payload) + errors = validate_custody_approval(data, approval_phrase) + if errors: + return data, errors + data["king_credential_ready"] = True + data["custody_mode_approved"] = True + data["custody_approved_at"] = utc_now() + data["custody_approved_by"] = approver or data.get("setup_operator", "") + data["approval_scope"] = "Non-secret local custody-mode approval only. Does not run OpenBao init." + return data, [] + + +def print_approve_custody_mode(args: argparse.Namespace, data: dict[str, Any]) -> int: + if args.metadata is None: + print("ERROR: approve-custody-mode requires --metadata /path/to/non-secret.json", file=sys.stderr) + return 2 + approval_phrase = args.approval_phrase or "" + if args.yes: + approval_phrase = APPROVAL_PHRASE + elif not approval_phrase: + print("This writes non-secret custody approval metadata only.") + print("It will not run OpenBao init and will not store secret values.") + try: + approval_phrase = input(f'Type "{APPROVAL_PHRASE}" to continue: ') + except EOFError: + approval_phrase = "" + payload: dict[str, Any] = { + "custody_mode": args.mode, + } + for key in ( + "credential_label", + "setup_operator", + "notification_contact", + "mfa_class", + "mfa_enrollment_source", + "mfa_enrollment_reference", + "notes", + ): + value = getattr(args, key) + if value is not None: + payload[key] = value + if args.storage_class: + payload["storage_classes"] = args.storage_class + for field in ( + "recovery_confirmed", + "custody_packet_prepared", + "no_secret_capture_confirmed", + "mfa_enrolled_confirmed", + ): + if getattr(args, field): + payload[field] = True + approved, errors = approve_custody_metadata( + data, + payload, + approval_phrase, + args.approved_by or "", + ) + if errors: + print("CUSTODY MODE NOT APPROVED") + print("") + for error in errors: + print(f"- {error}") + return 1 + write_metadata(args.metadata, approved) + print("CUSTODY MODE APPROVED") + print("") + print(f"Metadata: {args.metadata}") + print(f"Mode: {approved['custody_mode']}") + print(f"Approved by: {approved.get('custody_approved_by', '')}") + print(f"Approved at: {approved.get('custody_approved_at', '')}") + print("") + print("OpenBao init remains a separate human-attended ceremony.") + return 0 + + +def print_custody_packet() -> None: + print("CUSTODY PACKET TEMPLATE") + print("") + print("Credential label:") + print("Date:") + print("Setup operator/contact:") + print("Custody mode:") + print("Notification contact:") + print("") + print("Storage location description:") + print("Second-factor location description:") + print("Recovery material location description:") + print("") + print("OpenBao share assignment rows:") + print("- Share A:") + print("- Share B:") + print("- Share C:") + print("") + print("Root-token disposition:") + print("Signature/date:") + print("") + print("Do not write this packet into Git, State Hub, chat, tickets, or email.") + + +def print_handover_checklist() -> None: + print("HANDOVER CHECKLIST") + print("") + rows = [ + "King credential kit complete.", + "OpenBao initialized and unsealed under approved custody mode.", + "Root token revoked or sealed offline.", + "Non-root platform admin path verified.", + "Bootstrap-era database credentials rotated.", + "Temporary admin accounts reviewed and removed or scoped.", + "Kubernetes service accounts and privileged bindings reviewed.", + "SOPS/age recipients and emergency bundle reviewed.", + "Backup snapshot exists.", + "Restore drill passed.", + "Audit handling known.", + "Remaining risk exceptions recorded with owner and date.", + ] + for row in rows: + print(f"- {row}") + + +def metadata_template() -> dict[str, Any]: + return { + "credential_label": "platform-root", + "setup_operator": "tegwick", + "notification_contact": "bernd.worsch@gmail.com", + "storage_classes": ["password-safe", "offline-packet"], + "mfa_class": "totp", + "mfa_enrolled_confirmed": False, + "mfa_enrollment_source": "deferred", + "mfa_enrollment_reference": "", + "recovery_confirmed": False, + "custody_packet_prepared": False, + "no_secret_capture_confirmed": False, + "king_credential_ready": False, + "custody_mode": "", + "custody_mode_approved": False, + "custody_approved_at": "", + "custody_approved_by": "", + "approval_scope": "", + "openbao_preflight_passed": False, + "openbao_initialized": False, + "root_token_disposition": "", + "restore_drill_passed": False, + "cleanup_complete": False, + "platform_reopened": False, + "review_date": "", + "notes": "Non-secret metadata only.", + } + + +def print_openbao_preflight(args: argparse.Namespace) -> int: + print("OPENBAO PREFLIGHT") + print("") + print("Safe commands:") + print(f"make -C {args.railiance_path} openbao-status") + print(f"make -C {args.railiance_path} openbao-verify") + print("") + if not args.run: + print("Dry run only. Pass --run to execute safe preflight commands.") + return 0 + + railiance_path = Path(args.railiance_path).expanduser().resolve() + if not railiance_path.is_dir(): + print(f"ERROR: Railiance path not found: {railiance_path}", file=sys.stderr) + return 2 + for target in ("openbao-status", "openbao-verify"): + result = subprocess.run( + ["make", "-C", str(railiance_path), target], + check=False, + ) + if result.returncode != 0: + return result.returncode + return 0 + + +def gate_payload(gate: Gate) -> dict[str, str]: + return { + "name": gate.name, + "status": gate.status, + "reason": gate.reason, + } + + +def status_payload(data: dict[str, Any], metadata_path: Path) -> dict[str, Any]: + gates = build_gates(data) + return { + "metadata_path": str(metadata_path), + "stage": derive_stage(data), + "next_action": next_action(gates), + "gates": [gate_payload(gate) for gate in gates], + "kit_gates": [gate_payload(gate) for gate in kit_validation(data)], + "metadata": data, + "approval_phrase": APPROVAL_PHRASE, + "custody_approval_modes": sorted(CUSTODY_APPROVAL_MODES), + } + + +def ui_html() -> str: + return """ + + + + + NetKingdom Security Bootstrap + + + +
+
NetKingdom control surface
+

Security bootstrap custody approval

+
+
+
+
+ Stage + Loading +
+
+ Next safe action + Loading +
+
+ Metadata + Loading +
+
+ +
+
+
+

King credential

+

Local non-secret metadata only. OpenBao initialization stays manual.

+
+ + + + +
+
+ +
+

Second factor enrollment

+

The QR code or setup key belongs to the authority that verifies login. This UI records confirmation only.

+
+ + +
+
+ +
+
+ +
+

Storage and recovery

+
+ + + + + + +
+
+ +
+

Custody mode

+
+ + + +
+
+ Approval phrase + +
+
+ + +
+
Waiting for local approval.
+
+
+ + +
+
+ + + +""" + + +def make_ui_handler(metadata_path: Path) -> type[BaseHTTPRequestHandler]: + class SecurityBootstrapUIHandler(BaseHTTPRequestHandler): + server_version = "SecurityBootstrapUI/1.0" + + def log_message(self, format: str, *args: Any) -> None: + print(f"{self.address_string()} - {format % args}") + + def send_json(self, status: HTTPStatus, payload: dict[str, Any]) -> None: + body = json.dumps(payload, indent=2).encode("utf-8") + self.send_response(status.value) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + + def do_GET(self) -> None: + if self.path == "/" or self.path == "/index.html": + body = ui_html().encode("utf-8") + self.send_response(HTTPStatus.OK.value) + self.send_header("Content-Type", "text/html; charset=utf-8") + self.send_header("Cache-Control", "no-store") + self.send_header("Content-Length", str(len(body))) + self.end_headers() + self.wfile.write(body) + return + if self.path == "/api/status": + data = load_metadata(metadata_path) + if not data: + data = metadata_template() + self.send_json(HTTPStatus.OK, status_payload(data, metadata_path)) + return + self.send_error(HTTPStatus.NOT_FOUND.value) + + def do_POST(self) -> None: + if self.path != "/api/approve-custody": + self.send_error(HTTPStatus.NOT_FOUND.value) + return + try: + length = int(self.headers.get("Content-Length", "0")) + except ValueError: + self.send_json(HTTPStatus.BAD_REQUEST, {"errors": ["Invalid content length."]}) + return + if length > 65536: + self.send_json(HTTPStatus.REQUEST_ENTITY_TOO_LARGE, {"errors": ["Request too large."]}) + return + try: + payload = json.loads(self.rfile.read(length) or b"{}") + except json.JSONDecodeError as exc: + self.send_json(HTTPStatus.BAD_REQUEST, {"errors": [f"Invalid JSON: {exc}"]}) + return + if not isinstance(payload, dict): + self.send_json(HTTPStatus.BAD_REQUEST, {"errors": ["JSON body must be an object."]}) + return + existing = load_metadata(metadata_path) + approval_phrase = str(payload.get("approval_phrase", "")) + approved_by = str(payload.get("approved_by", "")) + approved, errors = approve_custody_metadata(existing, payload, approval_phrase, approved_by) + if errors: + response = status_payload(approved, metadata_path) + response["errors"] = errors + self.send_json(HTTPStatus.BAD_REQUEST, response) + return + write_metadata(metadata_path, approved) + response = status_payload(approved, metadata_path) + response["message"] = "Custody mode approved." + self.send_json(HTTPStatus.OK, response) + + return SecurityBootstrapUIHandler + + +def serve_web_ui(args: argparse.Namespace) -> int: + metadata_path = args.metadata or DEFAULT_METADATA_PATH + handler = make_ui_handler(metadata_path) + httpd = ThreadingHTTPServer((args.host, args.port), handler) + host = html.escape(args.host) + print("SECURITY BOOTSTRAP UI") + print("") + print(f"URL: http://{host}:{args.port}") + print(f"Metadata: {metadata_path}") + print("") + print("Local non-secret custody approval only. Press Ctrl+C to stop.") + try: + httpd.serve_forever() + except KeyboardInterrupt: + print("") + print("Stopped.") + finally: + httpd.server_close() + return 0 + + +def refuse_live_init() -> int: + print("REFUSED") + print("") + print("This console does not run bao operator init.") + print("Use the human-attended Railiance OpenBao ceremony after all gates pass.") + return 2 + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Non-secret NetKingdom security bootstrap console.", + ) + parser.add_argument( + "--metadata", + type=Path, + help="Optional non-secret metadata JSON file.", + ) + sub = parser.add_subparsers(dest="command", required=True) + sub.add_parser("status", help="Show trust stage, gates, and next safe action.") + sub.add_parser("king-kit", help="Print king credential kit checklist.") + sub.add_parser("validate-king-kit", help="Validate non-secret king credential metadata.") + approve = sub.add_parser("approve-custody-mode", help="Approve a live-init-ready custody mode.") + approve.add_argument( + "--mode", + choices=sorted(VALID_CUSTODY_MODES), + default="temporary-single-king", + help="Custody mode to record. two-of-three-planned cannot approve live init.", + ) + approve.add_argument("--credential-label", dest="credential_label") + approve.add_argument("--setup-operator", dest="setup_operator") + approve.add_argument("--notification-contact", dest="notification_contact") + approve.add_argument("--storage-class", dest="storage_class", action="append") + approve.add_argument("--mfa-class", dest="mfa_class", choices=sorted(VALID_MFA_CLASSES)) + approve.add_argument("--mfa-enrolled-confirmed", action="store_true") + approve.add_argument( + "--mfa-enrollment-source", + choices=sorted(VALID_MFA_ENROLLMENT_SOURCES), + default=None, + ) + approve.add_argument("--mfa-enrollment-reference") + approve.add_argument("--recovery-confirmed", action="store_true") + approve.add_argument("--custody-packet-prepared", action="store_true") + approve.add_argument("--no-secret-capture-confirmed", action="store_true") + approve.add_argument("--approval-phrase", default="") + approve.add_argument("--approved-by", default="") + approve.add_argument("--notes") + approve.add_argument( + "--yes", + action="store_true", + help=f'Use the required approval phrase "{APPROVAL_PHRASE}" non-interactively.', + ) + sub.add_parser("custody-packet", help="Print blank offline custody packet template.") + sub.add_parser("handover-checklist", help="Print handover and cleanup checklist.") + sub.add_parser("metadata-template", help="Print non-secret metadata JSON template.") + sub.add_parser("refuse-live-init", help="Explain why live OpenBao init is refused.") + web = sub.add_parser("web-ui", help="Serve a local custody approval UI.") + web.add_argument("--host", default="127.0.0.1", help="Bind host. Defaults to localhost.") + web.add_argument("--port", type=int, default=8765, help="Bind port. Defaults to 8765.") + preflight = sub.add_parser("openbao-preflight", help="Show or run safe OpenBao preflight.") + preflight.add_argument( + "--railiance-path", + default="../railiance-platform", + help="Path to railiance-platform repo.", + ) + preflight.add_argument( + "--run", + action="store_true", + help="Run safe preflight targets. Does not run OpenBao init.", + ) + return parser + + +def main(argv: list[str] | None = None) -> int: + parser = build_parser() + args = parser.parse_args(argv) + data = load_metadata(args.metadata) + + if args.command == "status": + print_status(data) + return 0 + if args.command == "king-kit": + print_king_kit() + return 0 + if args.command == "validate-king-kit": + return print_validate_king_kit(data) + if args.command == "approve-custody-mode": + return print_approve_custody_mode(args, data) + if args.command == "custody-packet": + print_custody_packet() + return 0 + if args.command == "handover-checklist": + print_handover_checklist() + return 0 + if args.command == "metadata-template": + print(json.dumps(metadata_template(), indent=2)) + return 0 + if args.command == "openbao-preflight": + return print_openbao_preflight(args) + if args.command == "web-ui": + return serve_web_ui(args) + if args.command == "refuse-live-init": + return refuse_live_init() + parser.error(f"unknown command: {args.command}") + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md b/workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md new file mode 100644 index 0000000..9acc558 --- /dev/null +++ b/workplans/NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap.md @@ -0,0 +1,213 @@ +--- +id: NET-WP-0015 +type: workplan +title: "King Credential And OpenBao Identity Bootstrap" +domain: netkingdom +repo: net-kingdom +status: active +owner: codex +topic_slug: netkingdom +created: "2026-05-24" +updated: "2026-05-24" +depends_on: + - NK-WP-0006 + - NK-WP-0012 +state_hub_workstream_id: "6b9c25e4-1008-429a-8de6-54361872c0dd" +--- + +# NET-WP-0015 - King Credential And OpenBao Identity Bootstrap + +## Goal + +Define and execute the first safe bridge between low-trust setup operations, a +dedicated king credential, NetKingdom identity, and Railiance OpenBao +bootstrap. + +The revised decision is that `tegwick` / `bernd.worsch@gmail.com` is the +initial accountable setup operator and notification contact, not the long-term +platform root of trust. The actual platform-root authority should move to a +separate king credential before OpenBao becomes live secret custody. + +## Context + +Railiance owns OpenBao deployment and operations. NetKingdom owns the identity, +custody, and security semantics that say who can administer the platform and +how that authority transitions from bootstrap material into normal IAM claims. + +The platform is still in MVP/prototype bootstrap. That means early databases, +admin accounts, tokens, and access paths must be treated as potentially +contaminated by convenience. The platform should be assembled in low-trust +mode, then handed over to the king credential, reset/rotated, checked, and +reopened under explicit custody. + +## Scope + +In scope: + +- record the setup operator/contact identity; +- define the separate king credential target; +- define the temporary single-operator king custody exception; +- specify target NetKingdom IAM claims for the first admin identity; +- coordinate the OpenBao initialization prerequisites with Railiance; +- define the transition from OpenBao root token to scoped admin access; and +- add follow-up gates for independent escrow, OIDC/JWT admin auth, + reset/rotation, scan checks, and restore verification. + +Out of scope: + +- storing any secret material in this repo; +- running `bao operator init` from an unattended agent session; +- deploying key-cape, Keycloak, privacyIDEA, or OpenBao itself; and +- granting tenant administrators platform-root authority. + +## Tasks + +### T01 - Record Setup Operator And King Credential Model + +```task +id: NET-WP-0015-T01 +status: done +priority: high +state_hub_task_id: "60659e25-fed1-478e-b8a3-4bc7b2f3846b" +``` + +Record `tegwick` / `bernd.worsch@gmail.com` / Gitea `tegwick` as the initial +setup operator and contact. Define the separate king credential as the actual +platform-root target. + +**2026-05-24:** Added `docs/platform-root-custody.md` and updated +`docs/platform-identity-security-architecture.md` plus `SCOPE.md`. + +**2026-05-24:** Revised the custody model: `tegwick` is no longer modeled as +the platform root of trust. The day-to-day account can assemble and observe the +platform, while a dedicated king credential receives final custody after the +guided bootstrap path is ready. + +### T02 - Define King Credential Kit + +```task +id: NET-WP-0015-T02 +status: done +priority: high +state_hub_task_id: "1a1c45a2-be66-4667-89f8-581f4fe9970b" +``` + +Define the first king credential kit: dedicated identity name, local/offline +password-safe storage, second factor, recovery-code handling, no email secret +transfer, no day-to-day browsing/Git use, and operator instructions clear +enough for a non-expert. + +**2026-05-24:** Defined the v1 kit in +`docs/security-bootstrap-king-credential-kit.md`: label `platform-root`, setup +operator/contact `tegwick`, notification-only email +`bernd.worsch@gmail.com`, local password safe plus offline custody packet, +TOTP/WebAuthn/hardware-token second factor, no day-to-day use, and no email or +Git secret transfer. Added +`examples/security-bootstrap/king-credential-metadata.example.json` plus +console validation for non-secret kit metadata. Custody-mode approval remains +blocked under T03. + +### T03 - Approve King Custody Mode + +```task +id: NET-WP-0015-T03 +status: blocked +priority: high +state_hub_task_id: "56a6266a-4acd-41e6-a395-85e90a5c35c6" +``` + +Choose either the preferred independent two-of-three king custody model or an +explicit temporary single-operator king credential exception for pre-production +bootstrap. Do not run OpenBao initialization until this choice is recorded. + +**2026-05-24:** Added local approval surfaces for this human gate: +`approve-custody-mode` for the CLI and `web-ui` for the localhost console. +Both write non-secret metadata only and keep live OpenBao initialization as a +separate attended ceremony. Current recommended approval mode is +`temporary-single-king`; `two-of-three-planned` records the target state but +does not unblock live init. + +**2026-05-24:** Tightened MFA handling after review: a TOTP QR code or setup +key must come from the authority that will verify login, not from the local +metadata console. Custody approval now requires explicit non-secret +confirmation that the factor was enrolled with its real verifier. + +### T04 - Complete Railiance OpenBao Bootstrap Ceremony + +```task +id: NET-WP-0015-T04 +status: blocked +priority: high +state_hub_task_id: "2102366e-064b-4071-8b6a-574d9d37d109" +``` + +Coordinate with `RAIL-PL-WP-0002-T03` to initialize and unseal OpenBao under +the king credential model, enable audit and the first mounts/policies, create a +non-root `platform-admin` access path, and revoke or offline-escrow the initial +root token. + +### T05 - Provision First NetKingdom Admin Identity + +```task +id: NET-WP-0015-T05 +status: todo +priority: high +state_hub_task_id: "d2a81d7b-9964-4bd5-9b8c-ef1324e02cd4" +``` + +Provision the first king/admin identity in the selected NetKingdom IAM +implementation. The target claims are `tenant=platform`, +`principal_type=human` or `break_glass`, MFA-backed assurance, and groups/roles +for `platform-root`, `platform-admin`, `netkingdom-admin`, and +`railiance-platform-admin`. `tegwick` may receive delegated day-to-day admin +roles later, but must be revocable without losing root custody. + +### T06 - Bind OpenBao Admin Auth To NetKingdom IAM + +```task +id: NET-WP-0015-T06 +status: todo +priority: medium +state_hub_task_id: "ef97f3cb-9792-4b9d-bd2b-8871d368a50f" +``` + +Replace temporary operator tokens with NetKingdom IAM-backed OpenBao admin +auth when the issuer and claim mapping are ready. The OpenBao root token must +not be the normal admin path. + +### T07 - Verify Recovery, Audit, And Rotation + +```task +id: NET-WP-0015-T07 +status: todo +priority: medium +state_hub_task_id: "aa40cbb4-36d3-405d-b59d-0c21ae8c9539" +``` + +Confirm snapshot/restore drill, durable audit-log handling, root-token +disposition, unseal/recovery rotation expectations, and the follow-up owner +for adding at least one additional human escrow holder. + +### T08 - Reset, Rotate, And Reopen Under King Oversight + +```task +id: NET-WP-0015-T08 +status: todo +priority: high +state_hub_task_id: "e6a60dca-547b-4493-a36c-f6b668d1bf52" +``` + +After the king credential accepts custody, reset or rotate bootstrap-era +database credentials, admin passwords, service tokens, OpenBao tokens, and +temporary access paths. Run host/workload checks and reopen the platform only +after the new custody state is verified. + +## Acceptance Criteria + +- The setup operator and king credential model are recorded without secret + values. +- The custody mode is explicit before OpenBao initialization. +- OpenBao root-token use is limited to bootstrap or break-glass handling. +- Routine admin access has a non-root path and a target NetKingdom IAM path. +- Production readiness has a clear gate for independent escrow, audit, restore, + reset/rotation, and reopening under king oversight. diff --git a/workplans/NET-WP-0016-guided-security-bootstrap-experience.md b/workplans/NET-WP-0016-guided-security-bootstrap-experience.md new file mode 100644 index 0000000..c92116b --- /dev/null +++ b/workplans/NET-WP-0016-guided-security-bootstrap-experience.md @@ -0,0 +1,202 @@ +--- +id: NET-WP-0016 +type: workplan +title: "Guided Security Bootstrap Experience" +domain: netkingdom +repo: net-kingdom +status: finished +owner: codex +topic_slug: netkingdom +created: "2026-05-24" +updated: "2026-05-24" +depends_on: + - NET-WP-0015 + - NK-WP-0012 +state_hub_workstream_id: "16069174-6698-4855-ad9e-5092c8571f38" +--- + +# NET-WP-0016 - Guided Security Bootstrap Experience + +## Goal + +Create the operator-facing bootstrap experience that makes NetKingdom and +OpenBao security setup understandable, repeatable, and safe for non-experts. + +The platform should be possible to assemble with a low-trust setup operator, +then hand over to a dedicated king credential, reset and harden the bootstrap +state, and reopen under explicit custody. + +## Context + +Railiance and NetKingdom have reached a point where raw runbooks are not enough. +The infrastructure is still early and evolving, and the human operator does not +need to be an OpenBao/Keycloak/flex-auth expert to take the next safe step. + +Good security here should feel like guided operations: visible trust stage, +clear blocked actions, plain-language explanations, and no accidental secret +exposure. + +## Scope + +In scope: + +- define bootstrap use cases for king credential setup, user lifecycle, + OpenBao bootstrap, fabric setup, break-glass, and multi-custodian upgrade; +- design the first local operator console/checklist flow; +- define safety gates for live OpenBao initialization; +- define non-secret status records and audit/progress events; +- define where the UI reads status from NetKingdom, Railiance, and State Hub; + and +- implement a first minimal CLI or local UI if the design stabilizes. + +Out of scope: + +- storing or displaying secret values; +- implementing the full web UI before the workflow is validated; +- replacing OpenBao, key-cape, Keycloak, or flex-auth administrative UIs; +- unattended OpenBao initialization; and +- sending root material or recovery secrets by email. + +## Tasks + +### T01 - Define Bootstrap Use Cases + +```task +id: NET-WP-0016-T01 +status: done +priority: high +state_hub_task_id: "67af8a29-7ca1-4a9d-be3e-bdc48dd2d1fd" +``` + +Document the canonical bootstrap use cases and trust stages. + +**2026-05-24:** Added `docs/security-bootstrap-use-cases.md` covering king +credential setup, onboarding, temporary lockout, permanent lockout/offboarding, +credential review/rotation, new fabric admin setup, OpenBao bootstrap, custody +handover, and later multi-custodian upgrade. + +### T02 - Design The First Operator Journey + +```task +id: NET-WP-0016-T02 +status: done +priority: high +state_hub_task_id: "662e439b-5fba-4e17-bc62-0ace97ba8788" +``` + +Design the first command-driven or local-web operator journey: trust stage, +next safe action, blocked gates, preflight checks, custody packet template, and +clear plain-language instructions. + +**2026-05-24:** Added `docs/security-bootstrap-operator-journey.md`. The first +journey uses a quiet `whynot-design` control surface: trust stage, one next +safe action, blocked gates, evidence rows, and a refusal boundary around live +OpenBao initialization. + +### T03 - Define King Credential Kit Output + +```task +id: NET-WP-0016-T03 +status: done +priority: high +state_hub_task_id: "98aba75f-a7c1-4486-be7f-e8d1148d5303" +``` + +Define the non-secret artifacts the bootstrap experience can generate for the +king credential: checklist, custody packet template, OTP setup instructions, +password-safe guidance, and verification prompts. + +**2026-05-24:** Added `docs/security-bootstrap-king-credential-kit.md`. + +### T04 - Define User Lifecycle Flows + +```task +id: NET-WP-0016-T04 +status: done +priority: high +state_hub_task_id: "44766b45-21b8-45cd-8c0a-0ca8281ae8e9" +``` + +Define guided flows for onboarding, temporary lockout, permanent lockout, +offboarding, credential review, credential rotation, and delegated fabric admin +setup. + +**2026-05-24:** Added `docs/security-bootstrap-user-lifecycle.md`. + +### T05 - Define OpenBao Ceremony UX + +```task +id: NET-WP-0016-T05 +status: done +priority: high +state_hub_task_id: "53f55c99-8403-4b58-9ed4-b03e68c1ef3c" +``` + +Translate the Railiance OpenBao ceremony into a guided sequence that can show +status, block unsafe live init, guide offline custody, and record non-secret +completion evidence. + +**2026-05-24:** Added `docs/security-bootstrap-openbao-ceremony-ux.md`. + +### T06 - Prototype Local Bootstrap Console + +```task +id: NET-WP-0016-T06 +status: done +priority: medium +state_hub_task_id: "ef1c8ee4-250c-479a-b0fb-0b5cf4249bd9" +``` + +Implement the first minimal local operator console or CLI once the journey is +clear. It should read status, print checklists, run safe preflight commands, +and refuse live bootstrap when gates are missing. + +**2026-05-24:** Added +`tools/security-bootstrap-console/security_bootstrap_console.py`, a read-only +local console with status, king-kit, custody-packet, handover-checklist, +metadata-template, and OpenBao preflight commands. Added Make targets for the +safe entry points. The console refuses live OpenBao init. + +### T07 - Define Handover And Cleanup Gates + +```task +id: NET-WP-0016-T07 +status: done +priority: medium +state_hub_task_id: "46c7e3dc-e824-46ef-833d-9a83189735e0" +``` + +Define the post-king handover cleanup flow: reset databases, rotate tokens, +review admin accounts, run scan/check steps, verify backups, and mark the +platform reopened under king oversight. + +**2026-05-24:** Added `docs/security-bootstrap-handover-cleanup.md`. + +### T08 - Review Related Workplans On Closeout + +```task +id: NET-WP-0016-T08 +status: done +priority: medium +state_hub_task_id: "7665f6ac-6b0e-4a09-8a9b-9d2150310114" +``` + +When this workplan closes, review related NetKingdom and Railiance security +workplans to update stale bootstrap assumptions, retire superseded tasks, and +add follow-ups where the guided bootstrap experience becomes the canonical +operator path. + +**2026-05-24:** Added +`docs/security-bootstrap-related-workplan-review.md`, kept `NK-WP-0004` and +`NK-WP-0005` as substrate workplans with closeout notes, left historical +`NK-WP-0001` archived, and updated stale Railiance OpenBao custody wording. + +## Acceptance Criteria + +- The setup operator can see the current trust stage and next safe action. +- Live OpenBao init remains blocked until king credential and custody gates are + satisfied. +- User lifecycle operations are described in plain, auditable flows. +- New fabrics can receive delegated admins without granting platform root. +- Secret values are never stored or displayed by the bootstrap experience. +- The path to two-of-three custody is explicit and low-friction. diff --git a/workplans/NK-WP-0004-credential-management-foundation.md b/workplans/NK-WP-0004-credential-management-foundation.md index 45497ef..87188d9 100644 --- a/workplans/NK-WP-0004-credential-management-foundation.md +++ b/workplans/NK-WP-0004-credential-management-foundation.md @@ -8,7 +8,7 @@ status: done owner: custodian topic_slug: netkingdom created: "2026-03-20" -updated: "2026-05-18" +updated: "2026-05-24" state_hub_workstream_id: "d9cf7c4b-886b-4cd1-ad7b-99c4e1929c9e" --- @@ -92,6 +92,18 @@ be treated as bootstrap artifacts, delivery caches, or compatibility mechanisms. Long-lived workload secret authority belongs in OpenBao, governed by NetKingdom policy and Railiance platform operations. +## NET-WP-0016 Closeout Review + +`NET-WP-0016` keeps this workplan as the low-level bootstrap credential +foundation. SOPS/age, encrypted bundles, generated secrets, and Kubernetes +Secret injection remain useful substrate tooling. + +The operator-facing path is no longer the old `/creds-bootstrap` experience by +itself. The canonical guided path is the security bootstrap console and related +docs from `NET-WP-0016`, with the dedicated king credential model from +`NET-WP-0015`. KeePassXC remains optional personal/offline storage; it is not +the platform root of trust. + ## Dependency on canon standard All design decisions in this workplan follow diff --git a/workplans/NK-WP-0005-agent-driven-credential-bootstrap.md b/workplans/NK-WP-0005-agent-driven-credential-bootstrap.md index e72a86b..28bf13c 100644 --- a/workplans/NK-WP-0005-agent-driven-credential-bootstrap.md +++ b/workplans/NK-WP-0005-agent-driven-credential-bootstrap.md @@ -8,7 +8,7 @@ status: done owner: custodian topic_slug: netkingdom created: "2026-03-21" -updated: "2026-05-18" +updated: "2026-05-24" depends_on: NK-WP-0004 state_hub_workstream_id: "75bc472b-cc0a-48f2-afb6-62b896f7cc19" --- @@ -86,6 +86,19 @@ to tenant administrators. If they are included in an emergency bundle, that bundle is platform-control-plane break-glass material and requires the strongest storage and review procedure available for the deployment. +## NET-WP-0016 Closeout Review + +This workplan remains useful as automation substrate, but its "zero human ops" +framing is superseded at the product and custody layer by `NET-WP-0015` and +`NET-WP-0016`. + +Agents may still generate, encrypt, inject, verify, and rotate bootstrap +material. They must not silently assume king credential custody, run live +OpenBao initialization unattended, or treat emergency bundles as ordinary +operator conveniences. The guided bootstrap experience is the canonical +operator path for king credential setup, OpenBao ceremony readiness, handover +cleanup, and reopening under custody. + ## Design ### What changes from NK-WP-0004