From 271aa9464254c52bdb7f272f5df740092282d8f1 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 28 Jun 2026 12:41:39 +0200 Subject: [PATCH] Record whynot OpenBao lane apply evidence --- ...R-2026-0001-whynot-design-npm-publish.yaml | 13 +++++++ docs/whynot-design-npm-publish-handoff.md | 21 +++++++--- docs/workload-kv-access-lanes.md | 27 ++++++++++++- schemas/credential-change-request.schema.yaml | 5 +++ scripts/credential-change.py | 15 ++++++++ tests/test_credential_change.py | 17 +++++++++ ...LIANCE-WP-0006-workload-kv-access-lanes.md | 38 ++++++++++++++++--- ...007-credential-change-approval-workflow.md | 10 +++++ 8 files changed, 134 insertions(+), 12 deletions(-) diff --git a/credential-change-requests/CCR-2026-0001-whynot-design-npm-publish.yaml b/credential-change-requests/CCR-2026-0001-whynot-design-npm-publish.yaml index f4be06b..8a1832e 100644 --- a/credential-change-requests/CCR-2026-0001-whynot-design-npm-publish.yaml +++ b/credential-change-requests/CCR-2026-0001-whynot-design-npm-publish.yaml @@ -55,6 +55,9 @@ openbao: method: oidc mount: netkingdom role: whynot-design-workload-kv-read + allowed_redirect_uris: + - https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback + - http://localhost:8250/oidc/callback user_claim: sub groups_claim: groups bound_claims: @@ -91,6 +94,16 @@ verification: - OIDC role bound to confirmed whynot-design claim or approved service account. - Secret value provisioned directly in OpenBao through approved operator custody. - Positive and negative verification recorded with non-secret audit ids or timestamps. + evidence: + - at: '2026-06-28T10:37:42+00:00' + actor: codex + kind: non_secret_openbao_apply_check + result: passed + details: + - Policy read succeeded for workload-kv-read-whynot-design-npm-publish. + - OIDC role read showed the whynot-design bound claim, read policy, and callback URIs. + - Metadata read showed catalog-id whynot-design-npm-publish. + - Secret field presence check found NPM_AUTH_TOKEN without printing or recording the value. lifecycle: deactivate: Disable ops-warden catalog entry and remove or detach auth role policy. rotate: Replace NPM_AUTH_TOKEN value directly in OpenBao and record non-secret rotation diff --git a/docs/whynot-design-npm-publish-handoff.md b/docs/whynot-design-npm-publish-handoff.md index a27a753..a5bdcfb 100644 --- a/docs/whynot-design-npm-publish-handoff.md +++ b/docs/whynot-design-npm-publish-handoff.md @@ -7,7 +7,7 @@ This is the next-session handoff for `CCR-2026-0001` and the - CCR: `CCR-2026-0001` - Decision: `e6381a56-6b04-4fd5-b2de-f3ef59cde888` -- Status: approved +- Status: approved; non-secret OpenBao apply checks passed 2026-06-28 - Front door: `template`, `resolvable=false` - Catalog id: `whynot-design-npm-publish` - Tenant/org: `coulomb` @@ -18,9 +18,11 @@ This is the next-session handoff for `CCR-2026-0001` and the `https://gitea.coulomb.social/api/packages/coulomb/npm/` The operator reported that the Gitea token was generated and stored in OpenBao. -Codex could not verify the metadata from the current token-helper identity: -metadata lookup, policy read, and auth-role read all returned `403 permission -denied`. No secret value was read or printed. +Using the temporary operator token only for non-secret infrastructure work, Codex +confirmed that the policy exists, the OIDC role exists with the whynot-design +binding and redirect URIs, the secret metadata has the expected catalog id, and +the `NPM_AUTH_TOKEN` field is present. No secret value was printed, recorded, +or copied into Git, State Hub, chat, or workplans. ## Safety Rules @@ -94,6 +96,10 @@ Role payload: ```json { "role_type": "oidc", + "allowed_redirect_uris": [ + "https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback", + "http://localhost:8250/oidc/callback" + ], "user_claim": "sub", "groups_claim": "groups", "bound_claims": { @@ -111,6 +117,10 @@ role_payload_file="$(mktemp)" trap 'rm -f "$role_payload_file"' EXIT cat >"$role_payload_file" <<'JSON' { + "allowed_redirect_uris": [ + "https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback", + "http://localhost:8250/oidc/callback" + ], "bound_claims": { "groups": [ "whynot-design" @@ -181,7 +191,8 @@ Only after these are true: - secret metadata confirmed; - policy exists and is scoped to the corrected `coulomb/whynot-design` path; -- OIDC role exists and binds only `groups=["whynot-design"]`; +- OIDC role exists and binds only `groups=["whynot-design"]` with approved + browser and local CLI callback URIs; - positive verification passed; - negative verification passed; diff --git a/docs/workload-kv-access-lanes.md b/docs/workload-kv-access-lanes.md index a60f13e..0930830 100644 --- a/docs/workload-kv-access-lanes.md +++ b/docs/workload-kv-access-lanes.md @@ -35,6 +35,7 @@ Ops-warden batch follow-up: | Policy file | `openbao/policies/workload-kv-read-whynot-design-npm-publish.hcl` | | OIDC auth mount | `netkingdom` | | OIDC role | `whynot-design-workload-kv-read` | +| OIDC callback URIs | `https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback`, `http://localhost:8250/oidc/callback` | | Kubernetes auth role | `whynot-design-workload-kv-read` if an in-cluster service account consumes this lane | | flex-auth ref | `secret.read:whynot-design` if tenant policy requires pre-approval | @@ -109,6 +110,14 @@ The role must attach only: workload-kv-read-whynot-design-npm-publish ``` +The OIDC role must include the browser and local CLI callback URIs accepted by +OpenBao: + +```text +https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback +http://localhost:8250/oidc/callback +``` + The whynot-design pilot claim is confirmed as `groups=whynot-design`. Before applying any changed role, re-confirm the KeyCape/NetKingdom claim that identifies the whynot-design caller. The role must bind to that claim; do not @@ -154,9 +163,25 @@ Positive verification: 1. Authenticate as the whynot-design caller using the approved OIDC or Kubernetes auth role. -2. Fetch the field in an attended session or through `warden access --fetch`. +2. Fetch the field in an attended session or through `warden access --exec`. 3. Record only that the fetch succeeded; do not record the value. +Safe attended command shape before the dedicated ops-warden catalog id is +activated: + +```bash +set +x +bao login -method=oidc -path=netkingdom role=whynot-design-workload-kv-read +warden access "npm token" \ + --path platform/workloads/coulomb/whynot-design/npm-publish \ + --field NPM_AUTH_TOKEN \ + --no-policy \ + --exec -- sh -lc 'test -n "$NPM_AUTH_TOKEN"' +``` + +Use `--no-policy` only while the local ops-warden config reports +`policy.enabled=false`; remove it once the flex-auth gate is enforced. + Negative verification: 1. Authenticate as a non-whynot identity. diff --git a/schemas/credential-change-request.schema.yaml b/schemas/credential-change-request.schema.yaml index e3bbf94..4c13f8f 100644 --- a/schemas/credential-change-request.schema.yaml +++ b/schemas/credential-change-request.schema.yaml @@ -81,6 +81,11 @@ workload_kv_read: - deactivate - rotate - compromised + conditional: + openbao.auth.method=oidc: + required: + - allowed_redirect_uris + allowed_redirect_uris: non-empty list of OpenBao callback URIs accepted by the role access_frontdoor_readiness: allowed: diff --git a/scripts/credential-change.py b/scripts/credential-change.py index 6142a95..95435d7 100755 --- a/scripts/credential-change.py +++ b/scripts/credential-change.py @@ -169,6 +169,19 @@ def validate_workload_kv_read(ccr: dict[str, Any], errors: list[str], warnings: errors.append("openbao.auth.method must be oidc or kubernetes") require_string(auth.get("mount"), "openbao.auth.mount", errors) require_string(auth.get("role"), "openbao.auth.role", errors) + if method == "oidc": + redirect_uris = require_list( + auth.get("allowed_redirect_uris"), + "openbao.auth.allowed_redirect_uris", + errors, + ) + if not redirect_uris: + errors.append("openbao.auth.allowed_redirect_uris must not be empty for oidc") + for index, uri in enumerate(redirect_uris): + if not isinstance(uri, str) or not uri.strip(): + errors.append( + f"openbao.auth.allowed_redirect_uris[{index}] must be a non-empty string" + ) policies = [str(policy) for policy in require_list(auth.get("policies"), "openbao.auth.policies", errors)] if policies != [policy_name]: errors.append("openbao.auth.policies must contain exactly openbao.policy_name") @@ -346,6 +359,8 @@ def auth_payload(ccr: dict[str, Any]) -> dict[str, Any]: } if auth.get("groups_claim"): payload["groups_claim"] = auth["groups_claim"] + if auth.get("allowed_redirect_uris"): + payload["allowed_redirect_uris"] = auth["allowed_redirect_uris"] return payload diff --git a/tests/test_credential_change.py b/tests/test_credential_change.py index c7e099c..890bc77 100644 --- a/tests/test_credential_change.py +++ b/tests/test_credential_change.py @@ -127,6 +127,18 @@ class CredentialChangeTests(unittest.TestCase): self.assertEqual(payload["bound_service_account_namespaces"], ["issue-core"]) self.assertNotIn("bound_claims", payload) + def test_oidc_auth_payload_includes_redirect_uris(self) -> None: + ccr, errors, _warnings = credential_change.validate_ccr(self.sample) + self.assertEqual(errors, []) + payload = credential_change.auth_payload(ccr) + self.assertEqual( + payload["allowed_redirect_uris"], + [ + "https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback", + "http://localhost:8250/oidc/callback", + ], + ) + def test_apply_plan_refuses_unapproved_ccr(self) -> None: with self.assertRaises(SystemExit): credential_change.command_apply_plan(type("Args", (), {"ref": str(self.issue_core)})()) @@ -151,6 +163,11 @@ class CredentialChangeTests(unittest.TestCase): ) self.assertIn('role_payload_file="$(mktemp)"', rendered) self.assertIn('"bound_claims": {', rendered) + self.assertIn('"allowed_redirect_uris": [', rendered) + self.assertIn( + '"https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback"', + rendered, + ) self.assertIn( 'bao write auth/netkingdom/role/whynot-design-workload-kv-read @"$role_payload_file"', rendered, diff --git a/workplans/RAILIANCE-WP-0006-workload-kv-access-lanes.md b/workplans/RAILIANCE-WP-0006-workload-kv-access-lanes.md index 7c5e1ad..4650853 100644 --- a/workplans/RAILIANCE-WP-0006-workload-kv-access-lanes.md +++ b/workplans/RAILIANCE-WP-0006-workload-kv-access-lanes.md @@ -4,13 +4,13 @@ type: workplan title: "Workload KV Access Lanes for ops-warden Fetch" domain: financials repo: railiance-platform -status: blocked +status: active owner: codex topic_slug: railiance planning_priority: high planning_order: 6 created: "2026-06-27" -updated: "2026-06-27" +updated: "2026-06-28" depends_on_workplans: - RAIL-PL-WP-0002 - RAILIANCE-WP-0004 @@ -152,11 +152,15 @@ denied with `403 permission denied` while writing the policy, so live policy application waits on an approved platform-admin/operator token or a narrow token-helper capability. +**2026-06-28:** Using the temporary operator token provided outside the repo, +Codex applied/confirmed the live policy in OpenBao. The verification read of the +policy succeeded and no secret values were printed or recorded. + ## T03 - Define and apply auth bindings ```task id: RAILIANCE-WP-0006-T03 -status: wait +status: done priority: high state_hub_task_id: "a217371a-0f85-40c6-b691-ac67834c86b5" ``` @@ -181,11 +185,17 @@ Acceptance: of the KeyCape/NetKingdom whynot-design bound claim or approved service-account subject; do not create an unbounded OIDC role. +**2026-06-28:** Created/confirmed +`auth/netkingdom/role/whynot-design-workload-kv-read` with +`groups=["whynot-design"]`, only the +`workload-kv-read-whynot-design-npm-publish` policy, `ttl=15m`, and the approved +browser/local CLI callback URIs. + ## T04 - Provision the KV path without exposing the token ```task id: RAILIANCE-WP-0006-T04 -status: wait +status: done priority: high state_hub_task_id: "c43724a3-c83e-4ab6-b7d1-e427fd93a9a9" ``` @@ -208,11 +218,17 @@ Acceptance: provisioning is waiting on an approved operator/OpenBao custody path for the actual `NPM_AUTH_TOKEN` value. +**2026-06-28:** Confirmed the OpenBao metadata at +`platform/workloads/coulomb/whynot-design/npm-publish` includes +`catalog-id=whynot-design-npm-publish` and that the `NPM_AUTH_TOKEN` field is +present. The value was not printed, recorded, or copied into Git, State Hub, +chat, or workplans. + ## T05 - Verify caller-scoped fetch behavior ```task id: RAILIANCE-WP-0006-T05 -status: wait +status: progress priority: high state_hub_task_id: "dc1f470b-e78a-48a9-9957-965aed47861f" ``` @@ -233,11 +249,16 @@ Acceptance: secret provisioning. The runbook requires positive and negative fetch evidence without printing the token value. +**2026-06-28:** Non-secret operator checks now pass for policy, auth role, +metadata, and field presence. Remaining verification is the attended +whynot-design OIDC positive check and a non-whynot denial check, both without +printing the token. + ## T06 - Coordinate ops-warden catalog activation ```task id: RAILIANCE-WP-0006-T06 -status: wait +status: progress priority: high state_hub_task_id: "8e84ec19-01db-4baf-a532-de87e51d4994" ``` @@ -260,6 +281,11 @@ handoff payload for ops-warden and sent the pointers by State Hub message. The entry should remain draft/non-active until live OpenBao provisioning and verification complete. +**2026-06-28:** The generic `openbao-api-key` ops-warden access lane can proxy +the check with explicit `--path` and `--field`, but the dedicated +`whynot-design-npm-publish` route is not yet present in the ops-warden routing +catalog. Keep activation pending until caller verification and catalog update. + ## T07 - Decide whether to batch sibling workload-KV requests ```task diff --git a/workplans/RAILIANCE-WP-0007-credential-change-approval-workflow.md b/workplans/RAILIANCE-WP-0007-credential-change-approval-workflow.md index 89c8843..e98e5ad 100644 --- a/workplans/RAILIANCE-WP-0007-credential-change-approval-workflow.md +++ b/workplans/RAILIANCE-WP-0007-credential-change-approval-workflow.md @@ -180,6 +180,10 @@ not `approved` and also refuses unconfirmed bound claims. Remaining T04 work is to add a richer diff against existing source artifacts and eventually bridge from reviewed plan to the interactive live applier. +**2026-06-28:** Added OIDC `allowed_redirect_uris` to the CCR contract and +generated role payloads after live OpenBao rejected an OIDC role without +callbacks. Unit coverage now checks the generated whynot-design role payload. + ## T05 - Add chat/CLI approval commands ```task @@ -286,6 +290,12 @@ received `403 permission denied`. Prepared policy, auth-role, metadata verification, positive verification, negative verification, and activation without printing the token. +**2026-06-28:** With the temporary operator token, Codex applied/confirmed the +OpenBao read policy and OIDC role, confirmed metadata `catalog-id`, and confirmed +`NPM_AUTH_TOKEN` field presence without printing or recording the value. The CCR +now records non-secret evidence for that apply check. Positive whynot-design and +negative non-whynot caller verification still gate `active`/`ready`. + ## T08 - Add deactivation, rotation, and compromise flows ```task