Loading
+ 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 """ + +
+ + +Loading
+