Record whynot OpenBao lane apply evidence

This commit is contained in:
2026-06-28 12:41:39 +02:00
parent 3ef25cb787
commit 271aa94642
8 changed files with 134 additions and 12 deletions

View File

@@ -55,6 +55,9 @@ openbao:
method: oidc method: oidc
mount: netkingdom mount: netkingdom
role: whynot-design-workload-kv-read 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 user_claim: sub
groups_claim: groups groups_claim: groups
bound_claims: bound_claims:
@@ -91,6 +94,16 @@ verification:
- OIDC role bound to confirmed whynot-design claim or approved service account. - OIDC role bound to confirmed whynot-design claim or approved service account.
- Secret value provisioned directly in OpenBao through approved operator custody. - Secret value provisioned directly in OpenBao through approved operator custody.
- Positive and negative verification recorded with non-secret audit ids or timestamps. - 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: lifecycle:
deactivate: Disable ops-warden catalog entry and remove or detach auth role policy. 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 rotate: Replace NPM_AUTH_TOKEN value directly in OpenBao and record non-secret rotation

View File

@@ -7,7 +7,7 @@ This is the next-session handoff for `CCR-2026-0001` and the
- CCR: `CCR-2026-0001` - CCR: `CCR-2026-0001`
- Decision: `e6381a56-6b04-4fd5-b2de-f3ef59cde888` - Decision: `e6381a56-6b04-4fd5-b2de-f3ef59cde888`
- Status: approved - Status: approved; non-secret OpenBao apply checks passed 2026-06-28
- Front door: `template`, `resolvable=false` - Front door: `template`, `resolvable=false`
- Catalog id: `whynot-design-npm-publish` - Catalog id: `whynot-design-npm-publish`
- Tenant/org: `coulomb` - 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/` `https://gitea.coulomb.social/api/packages/coulomb/npm/`
The operator reported that the Gitea token was generated and stored in OpenBao. 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: Using the temporary operator token only for non-secret infrastructure work, Codex
metadata lookup, policy read, and auth-role read all returned `403 permission confirmed that the policy exists, the OIDC role exists with the whynot-design
denied`. No secret value was read or printed. 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 ## Safety Rules
@@ -94,6 +96,10 @@ Role payload:
```json ```json
{ {
"role_type": "oidc", "role_type": "oidc",
"allowed_redirect_uris": [
"https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback",
"http://localhost:8250/oidc/callback"
],
"user_claim": "sub", "user_claim": "sub",
"groups_claim": "groups", "groups_claim": "groups",
"bound_claims": { "bound_claims": {
@@ -111,6 +117,10 @@ role_payload_file="$(mktemp)"
trap 'rm -f "$role_payload_file"' EXIT trap 'rm -f "$role_payload_file"' EXIT
cat >"$role_payload_file" <<'JSON' 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": { "bound_claims": {
"groups": [ "groups": [
"whynot-design" "whynot-design"
@@ -181,7 +191,8 @@ Only after these are true:
- secret metadata confirmed; - secret metadata confirmed;
- policy exists and is scoped to the corrected `coulomb/whynot-design` path; - 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; - positive verification passed;
- negative verification passed; - negative verification passed;

View File

@@ -35,6 +35,7 @@ Ops-warden batch follow-up:
| Policy file | `openbao/policies/workload-kv-read-whynot-design-npm-publish.hcl` | | Policy file | `openbao/policies/workload-kv-read-whynot-design-npm-publish.hcl` |
| OIDC auth mount | `netkingdom` | | OIDC auth mount | `netkingdom` |
| OIDC role | `whynot-design-workload-kv-read` | | 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 | | 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 | | 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 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 The whynot-design pilot claim is confirmed as `groups=whynot-design`. Before
applying any changed role, re-confirm the KeyCape/NetKingdom claim that 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 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 1. Authenticate as the whynot-design caller using the approved OIDC or
Kubernetes auth role. 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. 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: Negative verification:
1. Authenticate as a non-whynot identity. 1. Authenticate as a non-whynot identity.

View File

@@ -81,6 +81,11 @@ workload_kv_read:
- deactivate - deactivate
- rotate - rotate
- compromised - 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: access_frontdoor_readiness:
allowed: allowed:

View File

@@ -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") errors.append("openbao.auth.method must be oidc or kubernetes")
require_string(auth.get("mount"), "openbao.auth.mount", errors) require_string(auth.get("mount"), "openbao.auth.mount", errors)
require_string(auth.get("role"), "openbao.auth.role", 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)] policies = [str(policy) for policy in require_list(auth.get("policies"), "openbao.auth.policies", errors)]
if policies != [policy_name]: if policies != [policy_name]:
errors.append("openbao.auth.policies must contain exactly openbao.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"): if auth.get("groups_claim"):
payload["groups_claim"] = auth["groups_claim"] payload["groups_claim"] = auth["groups_claim"]
if auth.get("allowed_redirect_uris"):
payload["allowed_redirect_uris"] = auth["allowed_redirect_uris"]
return payload return payload

View File

@@ -127,6 +127,18 @@ class CredentialChangeTests(unittest.TestCase):
self.assertEqual(payload["bound_service_account_namespaces"], ["issue-core"]) self.assertEqual(payload["bound_service_account_namespaces"], ["issue-core"])
self.assertNotIn("bound_claims", payload) 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: def test_apply_plan_refuses_unapproved_ccr(self) -> None:
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
credential_change.command_apply_plan(type("Args", (), {"ref": str(self.issue_core)})()) 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('role_payload_file="$(mktemp)"', rendered)
self.assertIn('"bound_claims": {', 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( self.assertIn(
'bao write auth/netkingdom/role/whynot-design-workload-kv-read @"$role_payload_file"', 'bao write auth/netkingdom/role/whynot-design-workload-kv-read @"$role_payload_file"',
rendered, rendered,

View File

@@ -4,13 +4,13 @@ type: workplan
title: "Workload KV Access Lanes for ops-warden Fetch" title: "Workload KV Access Lanes for ops-warden Fetch"
domain: financials domain: financials
repo: railiance-platform repo: railiance-platform
status: blocked status: active
owner: codex owner: codex
topic_slug: railiance topic_slug: railiance
planning_priority: high planning_priority: high
planning_order: 6 planning_order: 6
created: "2026-06-27" created: "2026-06-27"
updated: "2026-06-27" updated: "2026-06-28"
depends_on_workplans: depends_on_workplans:
- RAIL-PL-WP-0002 - RAIL-PL-WP-0002
- RAILIANCE-WP-0004 - 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 application waits on an approved platform-admin/operator token or a narrow
token-helper capability. 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 ## T03 - Define and apply auth bindings
```task ```task
id: RAILIANCE-WP-0006-T03 id: RAILIANCE-WP-0006-T03
status: wait status: done
priority: high priority: high
state_hub_task_id: "a217371a-0f85-40c6-b691-ac67834c86b5" 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 of the KeyCape/NetKingdom whynot-design bound claim or approved service-account
subject; do not create an unbounded OIDC role. 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 ## T04 - Provision the KV path without exposing the token
```task ```task
id: RAILIANCE-WP-0006-T04 id: RAILIANCE-WP-0006-T04
status: wait status: done
priority: high priority: high
state_hub_task_id: "c43724a3-c83e-4ab6-b7d1-e427fd93a9a9" 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 provisioning is waiting on an approved operator/OpenBao custody path for the
actual `NPM_AUTH_TOKEN` value. 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 ## T05 - Verify caller-scoped fetch behavior
```task ```task
id: RAILIANCE-WP-0006-T05 id: RAILIANCE-WP-0006-T05
status: wait status: progress
priority: high priority: high
state_hub_task_id: "dc1f470b-e78a-48a9-9957-965aed47861f" state_hub_task_id: "dc1f470b-e78a-48a9-9957-965aed47861f"
``` ```
@@ -233,11 +249,16 @@ Acceptance:
secret provisioning. The runbook requires positive and negative fetch evidence secret provisioning. The runbook requires positive and negative fetch evidence
without printing the token value. 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 ## T06 - Coordinate ops-warden catalog activation
```task ```task
id: RAILIANCE-WP-0006-T06 id: RAILIANCE-WP-0006-T06
status: wait status: progress
priority: high priority: high
state_hub_task_id: "8e84ec19-01db-4baf-a532-de87e51d4994" 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 entry should remain draft/non-active until live OpenBao provisioning and
verification complete. 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 ## T07 - Decide whether to batch sibling workload-KV requests
```task ```task

View File

@@ -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 to add a richer diff against existing source artifacts and eventually bridge
from reviewed plan to the interactive live applier. 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 ## T05 - Add chat/CLI approval commands
```task ```task
@@ -286,6 +290,12 @@ received `403 permission denied`. Prepared
policy, auth-role, metadata verification, positive verification, negative policy, auth-role, metadata verification, positive verification, negative
verification, and activation without printing the token. 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 ## T08 - Add deactivation, rotation, and compromise flows
```task ```task