diff --git a/docs/credential-governance-break-glass.md b/docs/credential-governance-break-glass.md index 240e34c..9262258 100644 --- a/docs/credential-governance-break-glass.md +++ b/docs/credential-governance-break-glass.md @@ -37,11 +37,6 @@ Hard lockdown is not an agent command. The irreversible hold point is the platform owner's attended seal action. All prerequisites below must be true before that hold point; repository access alone grants no authority to cross it. -Route planning must select `openbao-shamir-recovery-ceremony` and return one -`founder_required` approval act. A result that selects `openbao-api-key`, asks -for paste-once provisioning, or offers any raw-value transport is a routing -failure and must not be executed. - ## Pre-seal hold point - An approved, bounded window names the accountable platform driver and a diff --git a/registry/generated/high-risk-data-paths.yaml b/registry/generated/high-risk-data-paths.yaml index 9ce16d4..28602e2 100644 --- a/registry/generated/high-risk-data-paths.yaml +++ b/registry/generated/high-risk-data-paths.yaml @@ -10,12 +10,12 @@ # declares it, and is null where the field set has not been established -- # null means unknown, never 'one field'. -generated_at: "2026-08-22T18:54:51Z" +generated_at: "2026-08-22T18:21:24Z" source: ops-warden/registry/routing/catalog.yaml -catalog_revision: "e3b9b1620c67236d7ba2c53512e7113819ea20a9" -catalog_revision_date: "2026-08-22T20:54:45+02:00" +catalog_revision: "8280c0b7b7f1e134be29d64bf24483247932c661" +catalog_revision_date: "2026-08-22T19:08:21+02:00" catalog_dirty: false -high_risk_lane_count: 21 +high_risk_lane_count: 20 concrete_path_count: 14 # Graded high but not a single KV address -- a routing pattern, a broker @@ -26,7 +26,6 @@ no_concrete_path: - object-storage-sts - openbao-api-key - openbao-platform-admin-login - - openbao-shamir-recovery-ceremony - ops-warden-warden-sign-token paths: diff --git a/registry/routing/catalog.yaml b/registry/routing/catalog.yaml index f4c5382..07b2c42 100644 --- a/registry/routing/catalog.yaml +++ b/registry/routing/catalog.yaml @@ -171,29 +171,6 @@ entries: exec_capable: true lane: login - - id: openbao-shamir-recovery-ceremony - title: Attended OpenBao Shamir seal and unseal recovery ceremony - # A ceremony pointer, not a credential-value lane. Approval coordinates - # existing out-of-band custodians; Warden never requests or transports a share. - risk: high - workload_ref: - applicability: not-applicable - reason: "Attended platform trust-root ceremony; no workload credential is retrieved." - need_keywords: [openbao, shamir, seal, unseal, sealed, emergency, recovery, break-glass, quorum, share, shares, custodian, ceremony, raft, snapshot, provider-console, abort, attended] - owner_repo: railiance-platform - subsystem: OpenBao operator recovery - warden_executes: false - wiki_ref: wiki/playbooks/openbao-shamir-recovery-ceremony.md#worker-checklist - canon_ref: railiance-platform/docs/railiance01-coordinated-reboot.md - reviewed: "2026-08-22" - status: active - delegation: - mode: native - intended_owner: railiance-platform - reviewed: "2026-08-22" - verified: source-read - lane: ceremony - - id: whynot-design-npm-publish title: whynot-design npm publish token (@whynot/design → coulomb Gitea registry) # Publish rights to the package registry — a leaked token is a supply-chain write, not a read (WARDEN-WP-0032-T05). diff --git a/src/warden/plan.py b/src/warden/plan.py index dd0d7dd..639ccfc 100644 --- a/src/warden/plan.py +++ b/src/warden/plan.py @@ -185,16 +185,6 @@ def _founder_for_entry(entry: RouteEntry, need: str, domain: Optional[str]) -> F "desk_hint": f"warden desk --from-plan (act=oidc_login) or: {expanded.fetch_command}", }, ) - if entry.lane == "ceremony": - return FounderAct( - kind="approve", - summary=f"Attended owner ceremony approval required for {entry.id}", - details={ - "lane_id": entry.id, - "wiki_ref": entry.wiki_ref, - "desk_hint": "warden desk --act approve --lane " + entry.id, - }, - ) if _APPROVAL_SIGNS.search(need): return FounderAct( kind="approve", @@ -303,9 +293,8 @@ def build_plan( domain=domain, ) - # Login and ceremony lanes always need a human act. A ceremony is a pure - # owner pointer: it must never fall through to secret paste-once mechanics. - if entry.lane in ("login", "ceremony"): + # Login lanes always need a human identity act + if entry.lane == "login": act = _founder_for_entry(entry, need, domain) return AccessPlan( need=need, @@ -319,11 +308,7 @@ def build_plan( founder_act=act, catalog=freshness, candidates=candidates, - reasons=[ - "login lane requires interactive founder/operator identity act" - if entry.lane == "login" - else "ceremony lane requires attended owner approval" - ], + reasons=["login lane requires interactive founder/operator identity act"], actor=actor, domain=domain, ) diff --git a/src/warden/routing/catalog.py b/src/warden/routing/catalog.py index c04f7d7..aca8eaf 100644 --- a/src/warden/routing/catalog.py +++ b/src/warden/routing/catalog.py @@ -69,7 +69,7 @@ _REQUIRED_FIELDS = ( "workload_ref", ) _VALID_STATUS = ("active", "draft") -_VALID_LANES = ("secret", "login", "ceremony") +_VALID_LANES = ("secret", "login") _VALID_ROTATION_METHODS = ("rotate", "re-establish") # Default review cadence for a catalog pointer — "is this still the right owner diff --git a/src/warden/routing/models.py b/src/warden/routing/models.py index 2485c69..c9c348e 100644 --- a/src/warden/routing/models.py +++ b/src/warden/routing/models.py @@ -174,8 +174,6 @@ class RouteEntry: # "login" — interactive auth bootstrap (OIDC/MFA). No secret-read gate (you have # no identity yet), no caller-auth precheck (the point is to get one), # run interactively as the caller; warden never captures the token. - # "ceremony" — attended owner operation such as Shamir seal/unseal. It is a - # pointer plus approval boundary, never an executable access lane. lane: str = "secret" # Owner-native exec front door (WP-0019). When `exec_owner` is set, that subsystem # (e.g. secrets-engine) provides the PRIMARY way to run a secret-backed command; the diff --git a/tests/test_plan.py b/tests/test_plan.py index 7f6b7aa..447d487 100644 --- a/tests/test_plan.py +++ b/tests/test_plan.py @@ -80,23 +80,6 @@ def test_plan_first_time_openbao_database_admin_uses_platform_admin_login(): ) -def test_plan_openbao_shamir_recovery_uses_approval_ceremony_not_secret_provision(): - plan = build_plan( - "coordinate one attended production OpenBao emergency seal/unseal drill " - "with a fresh encrypted off-host Raft snapshot receipt, verified " - "provider-console access, two-of-three Shamir custodian quorum, named " - "driver and abort operator, without exposing credential values" - ) - assert plan.verdict == "founder_required" - assert plan.lane_id == "openbao-shamir-recovery-ceremony" - assert plan.founder_act is not None - assert plan.founder_act.kind == "approve" - assert "openbao-shamir-recovery-ceremony" in plan.founder_act.details["desk_hint"] - assert "paste_once" not in plan.founder_act.details["desk_hint"] - assert all("warden access" not in item for item in plan.commands) - assert any("openbao-shamir-recovery-ceremony" in item for item in plan.commands) - - def test_plan_unroutable(): # Zero keyword overlap with catalog (avoid tokens like secret/key/token) plan = build_plan("xyzzy-plugh-fnord-qqq-zzzz") diff --git a/tests/test_routing.py b/tests/test_routing.py index 209e54c..1ad3498 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -170,18 +170,6 @@ def test_platform_admin_login_lane_is_exact_and_non_value_bearing(): assert entry.workload_ref.resolution == "not-applicable" -def test_openbao_recovery_ceremony_is_non_value_bearing_owner_pointer(): - entry = load_catalog(_repo_catalog()).get("openbao-shamir-recovery-ceremony") - assert entry.lane == "ceremony" - assert entry.risk == "high" - assert entry.owner_repo == "railiance-platform" - assert entry.warden_executes is False - assert entry.exec_capable is False - assert entry.has_handoff is False - assert entry.vends_secret is False - assert entry.workload_ref.resolution == "not-applicable" - - def test_find_exact_id_wins_over_keyword_collision(): catalog = load_catalog(_repo_catalog()) # "npm" alone collides with openbao-api-key; the exact id must resolve uniquely. diff --git a/tests/test_workload_join.py b/tests/test_workload_join.py index 559a875..2f60fa5 100644 --- a/tests/test_workload_join.py +++ b/tests/test_workload_join.py @@ -14,7 +14,7 @@ def test_repo_catalog_uses_only_explicit_workload_references(): assert report["ok"] is True assert len(report["resolved"]) == 3 assert len(report["unknown"]) == 17 - assert len(report["not_applicable"]) == 9 + assert len(report["not_applicable"]) == 8 assert {row["lane"] for row in report["resolved"]} == { "ops-warden-warden-sign-token", "issue-core-ingestion-api-key", diff --git a/wiki/CredentialRouting.md b/wiki/CredentialRouting.md index a9b04bf..3b86c42 100644 --- a/wiki/CredentialRouting.md +++ b/wiki/CredentialRouting.md @@ -91,7 +91,6 @@ lists the interim set — WARDEN-WP-0030). | `ops-warden-warden-sign-token` | "railiance-platform broker owns the `warden-sign` lease — use `credential exec`" | `railiance-platform/scripts/credential.py exec --grant ops-warden/warden-sign` (see playbook) | | `openbao-api-key` | "OpenBao owns this — here is the path/command shape" | Call OpenBao directly, or use `warden access --fetch/--exec` as yourself when the lane is `exec_capable` | | `openbao-platform-admin-login` | "This is an attended OpenBao administration identity act, not workload secret retrieval" | Use KeyCape-backed OIDC/MFA at `auth/netkingdom`, role `platform-admin`; never substitute a workload role or root token | -| `openbao-shamir-recovery-ceremony` | "This is an attended trust-root ceremony, not secret provisioning" | Obtain one explicit approval and follow the railiance-platform recovery checklist with the existing out-of-band custodians; never send shares through Warden | | `flex-auth-policy-check` | "flex-auth decides — here is the policy doc" | Query flex-auth / embed the PEP | | `key-cape-oidc-login` | "key-cape / Keycloak owns identity" | Authenticate via IAM Profile, or use the `warden access` login lane as yourself | | `ops-bridge-tunnel` | "ops-bridge owns transport — supply a `cert_command`" | Open the tunnel with ops-bridge | diff --git a/wiki/playbooks/openbao-shamir-recovery-ceremony.md b/wiki/playbooks/openbao-shamir-recovery-ceremony.md deleted file mode 100644 index 8aac70b..0000000 --- a/wiki/playbooks/openbao-shamir-recovery-ceremony.md +++ /dev/null @@ -1,37 +0,0 @@ -# OpenBao Shamir recovery ceremony - -This lane routes an attended production seal/unseal or trust-root recovery need -to `railiance-platform`. It does not retrieve, provision, proxy, or transport an -unseal share, recovery value, OpenBao token, snapshot, or console credential. - -## Worker checklist - -1. Plan the need before drafting an operator step: - - ```bash - warden plan "attended OpenBao Shamir emergency seal/unseal recovery ceremony" --json - ``` - - The result must select `openbao-shamir-recovery-ceremony`, return - `founder_required`, and name one `approve` act. If it selects - `openbao-api-key`, proposes paste-once provisioning, or requests a raw share, - stop without executing the proposed act. - -2. Treat approval as coordination, not secret delivery. The platform owner uses - the existing out-of-band share custody and provider-console paths. Warden - Desk must never receive a share or console credential. - -3. Follow the authoritative owner checklist in - `railiance-platform/docs/railiance01-coordinated-reboot.md` and the OpenBao - recovery section of `railiance-platform/docs/openbao.md`. The local consumer - boundary and evidence requirements are in - `docs/credential-governance-break-glass.md`. - -4. Stop before the live hold point unless the owner preflight reports - `ready_for_live_execution: true` and the operator gives an explicit go/no-go - for that bounded window. - -Allowed records are non-secret approval ids, role attestations, timestamps, -seal-state booleans, hashes, and verification outcomes. Never place shares, -tokens, credentials, Secret data, or decrypted snapshots in Git, State Hub, -logs, shell history, or chat. diff --git a/workplans/WARDEN-WP-0027-credential-governance-lockdown.md b/workplans/WARDEN-WP-0027-credential-governance-lockdown.md index 3a15705..ffe2354 100644 --- a/workplans/WARDEN-WP-0027-credential-governance-lockdown.md +++ b/workplans/WARDEN-WP-0027-credential-governance-lockdown.md @@ -145,21 +145,6 @@ that boundary and still would not recover a sealed OpenBao. A separately approved ops-bridge unattended-signing design may re-evaluate the narrow AppRole under its own workplan; that is service access, not recovery authority. -**Read-only readiness 2026-08-22.** The owner `node-reboot` preflight passed -every automated check without observing a secret value: node/k3s, platform-pg -and continuous archiving, a 16.37-hour successful backup, initialized/unsealed -OpenBao with `shares=3` and `threshold=2`, ESO stores/projections, and the -reviewed audit-core digest were healthy. `ready_for_live_execution` correctly -remained false because no approved window, current encrypted off-host snapshot -receipt, provider-console attestation, quorum attestation, owner-ack flag, or -named abort operator was supplied. - -That preflight also exposed a Warden safety defect: the ceremony request matched -the generic `openbao-api-key` template and proposed paste-once provisioning. -`openbao-shamir-recovery-ceremony` now distinguishes the owner-operated approval -ceremony from secret retrieval. It is a non-value-bearing pointer and can never -offer `--fetch`, `--exec`, `--out`, `--wrap`, or paste-once share transport. - ## Task: Tamper-evident policy governance + reconcile ```task