RPF-WP-0054: record CCR-2026-0040 apply; fix State Hub evidence crash for prefix lanes
The founder apply wrote and read back both policies and the token role, then record_evidence_state_hub raised KeyError on openbao.auth (custody-generate-grant has kv_prefix, no auth), printing a traceback that made warden revoke the session. The summary now falls back across kv_path, kv_prefix, campaign_path and auth.mount. The missed hub event was posted afterwards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 469992@bnt-lap001 Assistant-Session: e4b8e420-df0a-441d-83ae-7f19130594d9
This commit is contained in:
parent
ff9a236f62
commit
117e837329
4 changed files with 66 additions and 17 deletions
|
|
@ -3,27 +3,33 @@ kind: credential-change-request
|
|||
schema_version: 1
|
||||
request_type: custody-generate-grant
|
||||
title: Broker grant keycape/client-secret-generate (create-only, generate in custody)
|
||||
status: proposed
|
||||
status: applied
|
||||
created: '2026-10-09'
|
||||
updated: '2026-10-09'
|
||||
requester:
|
||||
agent: railiance-platform
|
||||
reason: RPF-WP-0054-T04. net-kingdom SecurityCaseType oidc-browser-relying-party step
|
||||
mint-secret (lane keycape-client-secret-generate); ops-warden d9f889b1, ops-mason
|
||||
789feb39. Founder chose the templated create-only grant 2026-10-09 (T02 option A).
|
||||
reason: RPF-WP-0054-T04. net-kingdom SecurityCaseType oidc-browser-relying-party
|
||||
step mint-secret (lane keycape-client-secret-generate); ops-warden d9f889b1, ops-mason
|
||||
789feb39. Founder chose the templated create-only grant 2026-10-09 (T02 option
|
||||
A).
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-owner
|
||||
- key-cape-owner
|
||||
comments: []
|
||||
comments:
|
||||
- at: '2026-10-09T17:18:53+00:00'
|
||||
reviewer: platform-owner, key-cape-owner (founder Bernd Worsch)
|
||||
decision: approved
|
||||
comment: Approve create-only broker grant keycape/client-secret-generate (RPF-WP-0054-T02
|
||||
option A).
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: platform
|
||||
workload: keycape
|
||||
environment: production
|
||||
purpose: Let the credential broker create a new KeyCape confidential-client secret at
|
||||
platform/workloads/keycape/<client_id> once, without any value leaving custody.
|
||||
purpose: Let the credential broker create a new KeyCape confidential-client secret
|
||||
at platform/workloads/keycape/<client_id> once, without any value leaving custody.
|
||||
openbao:
|
||||
grant_id: keycape/client-secret-generate
|
||||
kv_prefix: platform/workloads/keycape
|
||||
|
|
@ -36,8 +42,8 @@ openbao:
|
|||
token_role: keycape-client-secret-generate
|
||||
token_ttl: 5m
|
||||
founder_confirmation_items:
|
||||
- This apply writes two policies and one token role. It binds the issuer policy to no
|
||||
identity, so nothing can mint the token until a separate review binds it to the
|
||||
- This apply writes two policies and one token role. It binds the issuer policy to
|
||||
no identity, so nothing can mint the token until a separate review binds it to the
|
||||
caller named in RPF-WP-0054-T01.
|
||||
- Create-only on keycape/+ means a token can create a new client entry once and can
|
||||
never change an existing one. Its widest misuse is an unused entry for a client
|
||||
|
|
@ -53,8 +59,8 @@ risk:
|
|||
- The token lives at most 5m and only inside the broker process.
|
||||
verification:
|
||||
positive:
|
||||
- Readback after apply matches source for both policies and the token role (done by
|
||||
applier-apply; prints nothing).
|
||||
- Readback after apply matches source for both policies and the token role (done
|
||||
by applier-apply; prints nothing).
|
||||
- Disposable probe (T05) creates probe-secret-generate-<date> at version 1.
|
||||
negative:
|
||||
- A second create on the probe path, a data read, update, delete and any path outside
|
||||
|
|
@ -63,7 +69,19 @@ verification:
|
|||
activation_conditions:
|
||||
- Founder approval (two reviews).
|
||||
- Issuer binding reviewed after RPF-WP-0054-T01; probe passed (T05).
|
||||
evidence: []
|
||||
evidence:
|
||||
- at: '2026-10-09T17:19:06+00:00'
|
||||
actor: founder
|
||||
kind: delegated_metadata_apply
|
||||
result: passed
|
||||
details:
|
||||
- Delegated metadata applier ran as founder using local bao CLI ambient authority.
|
||||
- 'Policy metadata write: sys/policies/acl/keycape-client-secret-generate'
|
||||
- 'Issuer policy metadata write: sys/policies/acl/credential-broker-keycape-client-secret-generate-issuer'
|
||||
- 'Token role metadata write: auth/token/roles/keycape-client-secret-generate'
|
||||
- Readback matched source for both policies and the token role.
|
||||
- Issuer policy is not bound to any identity by this apply.
|
||||
- No secret values were read, written, printed, or accepted in argv.
|
||||
lifecycle:
|
||||
deactivate: Delete auth/token/roles/keycape-client-secret-generate, then both policies;
|
||||
generated entries stay in custody with their cases.
|
||||
|
|
|
|||
|
|
@ -1331,10 +1331,19 @@ def record_evidence_state_hub(
|
|||
ccr: dict[str, Any], base_url: str, actor: str, kind: str, result: str, details: list[str]
|
||||
) -> dict[str, Any]:
|
||||
openbao = ccr["openbao"]
|
||||
# Lane request types differ: workload-kv-read has kv_path, auth lanes have
|
||||
# auth.mount, custody-generate-grant has kv_prefix. Never fail after an apply.
|
||||
auth_mount = (openbao.get("auth") or {}).get("mount")
|
||||
target = (
|
||||
openbao.get("kv_path")
|
||||
or openbao.get("kv_prefix")
|
||||
or openbao.get("campaign_path")
|
||||
or (f"auth/{auth_mount}" if auth_mount else "unknown")
|
||||
)
|
||||
summary = (
|
||||
f"CCR {ccr['id']} evidence {kind}/{result} by {actor}: "
|
||||
f"status={ccr['status']} path={openbao.get('kv_path') or 'auth/' + openbao['auth']['mount']} "
|
||||
f"policy={openbao['policy_name']}; "
|
||||
f"status={ccr['status']} path={target} "
|
||||
f"policy={openbao.get('policy_name', 'unknown')}; "
|
||||
+ "; ".join(details)
|
||||
)
|
||||
return state_hub_post_json(
|
||||
|
|
|
|||
|
|
@ -65,11 +65,17 @@ def stanzas(text: str) -> dict[str, str]:
|
|||
|
||||
|
||||
class BrokerGrantLaneTests(unittest.TestCase):
|
||||
def test_ccr_validates_and_waits_for_approval(self):
|
||||
def test_ccr_validates_and_records_the_delegated_apply(self):
|
||||
_ccr, errors, _w = cc.validate_ccr(CCR_PATH)
|
||||
self.assertEqual(errors, [])
|
||||
blockers = cc.applier_readiness_blockers(_ccr)
|
||||
self.assertTrue(any("got proposed" in b for b in blockers), blockers)
|
||||
self.assertEqual(_ccr["status"], "applied")
|
||||
self.assertTrue(any(e["kind"] == "delegated_metadata_apply" and e["result"] == "passed"
|
||||
for e in _ccr["verification"]["evidence"]))
|
||||
|
||||
def test_unapproved_ccr_blocks_the_applier(self):
|
||||
c = ccr()
|
||||
c["status"] = "proposed"
|
||||
self.assertTrue(any("got proposed" in b for b in cc.applier_readiness_blockers(c)))
|
||||
|
||||
def test_grant_policy_is_create_only(self):
|
||||
policy = stanzas((ROOT / ccr()["openbao"]["policy_file"]).read_text())
|
||||
|
|
@ -102,6 +108,16 @@ class BrokerGrantLaneTests(unittest.TestCase):
|
|||
mutate(c)
|
||||
self.assertNotEqual(lane.validate(c, ROOT), [], label)
|
||||
|
||||
def test_state_hub_evidence_summary_handles_prefix_lanes(self):
|
||||
posted = {}
|
||||
original = cc.state_hub_post_json
|
||||
cc.state_hub_post_json = lambda base, route, body: posted.update(body) or {"id": "x"}
|
||||
try:
|
||||
cc.record_evidence_state_hub(ccr(), "http://hub", "founder", "delegated_metadata_apply", "passed", ["ok"])
|
||||
finally:
|
||||
cc.state_hub_post_json = original
|
||||
self.assertIn("path=platform/workloads/keycape", posted["summary"])
|
||||
|
||||
def _apply(self, tamper=False):
|
||||
tmp = Path(tempfile.mkdtemp())
|
||||
(tmp / "state.json").write_text(json.dumps({"policies": {}, "roles": {}, "tamper": tamper}))
|
||||
|
|
|
|||
|
|
@ -183,6 +183,12 @@ python3 scripts/credential-change.py approve CCR-2026-0040 --reviewer "platform-
|
|||
&& warden access openbao-platform-admin-login --exec -- python3 scripts/credential-change.py applier-apply CCR-2026-0040 --confirm "DELEGATED APPLY CCR-2026-0040" --actor founder --record-state-hub --quiet
|
||||
```
|
||||
|
||||
2026-10-09: CCR-2026-0040 applied by the founder at 17:19Z; readback matched for
|
||||
both policies and the token role. The run then printed a traceback from the
|
||||
State Hub evidence step (KeyError on `openbao.auth` for a prefix lane), so warden
|
||||
revoked the session. That was fixed and the hub event was posted afterwards. The
|
||||
OpenBao objects were complete before the crash.
|
||||
|
||||
After T01 and T03 (separate session): review and apply the issuer binding for
|
||||
the named caller, then run `generate` once for a disposable client_id
|
||||
`probe-secret-generate-<date>`. Verify by metadata only that it is version 1.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue