railiance-platform/tests/test_issuer_binding_lane.py
codex 6f77484785
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
RPF-WP-0054-T05: fix issuer binding after the first probe
CCR-2026-0043 applied cleanly, but generate could not mint the grant token
(OpenBao refuses child tokens from a limited-use token; the role had
token_num_uses 8) and revoke-self was denied (missing from the issuer
policy). The role now uses 0, which the validator requires, and the issuer
policy grants revoke-self. Nothing was written by the probe.

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
2026-10-10 11:12:19 +02:00

133 lines
5.3 KiB
Python

"""RPF-WP-0054-T05: broker-issuer-binding lane (CCR-2026-0043)."""
from __future__ import annotations
import importlib.util
import json
import os
import stat
import sys
import tempfile
import unittest
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parents[1]
CCR_PATH = ROOT / "credential-change-requests/CCR-2026-0043-keycape-client-secret-generate-issuer-binding.yaml"
ISSUER = "credential-broker-keycape-client-secret-generate-issuer"
def load(name: str, rel: str):
spec = importlib.util.spec_from_file_location(name, ROOT / rel)
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
lane = load("issuer_binding_lane", "scripts/issuer_binding_lane.py")
cc = load("credential_change_cli_issuer", "scripts/credential-change.py")
def ccr() -> dict:
return yaml.safe_load(CCR_PATH.read_text())
FAKE_BAO = r'''#!/usr/bin/env python3
import json, os, sys
path = os.environ["FAKE_BAO_STATE"]
state = json.load(open(path))
args = [a for a in sys.argv[1:] if a != "-format=json"]
state.setdefault("calls", []).append(args)
if args[:2] == ["auth", "list"]:
print(json.dumps({k: {"type": v} for k, v in state["mounts"].items()}))
elif args[:2] == ["policy", "read"]:
if args[2] not in state["policies"]:
json.dump(state, open(path, "w")); sys.exit(2)
print(state["policies"][args[2]])
elif args[0] == "write":
state["roles"][args[1]] = json.load(open(args[2][1:]))
elif args[0] == "read":
print(json.dumps({"data": state["roles"][args[1]]}))
json.dump(state, open(path, "w"))
'''
class IssuerBindingLaneTests(unittest.TestCase):
def test_ccr_validates_and_is_ready_to_reapply(self):
_ccr, errors, _w = cc.validate_ccr(CCR_PATH)
self.assertEqual(errors, [])
self.assertEqual(cc.applier_readiness_blockers(_ccr), [])
def test_unconfirmed_binding_blocks_the_applier(self):
c = ccr()
c["openbao"]["auth"]["bound_claims_confirmed"] = False
self.assertIn("applier requires confirmed OpenBao auth binding", cc.applier_readiness_blockers(c))
def test_role_carries_only_the_issuer_policy(self):
role = json.loads((ROOT / ccr()["openbao"]["auth"]["role_file"]).read_text())
self.assertEqual(role["token_policies"], [ISSUER])
self.assertEqual(role["bound_claims"], {"groups": ["net-kingdom-admins"], "/assurance/level": "aal2"})
self.assertTrue(role["token_no_default_policy"])
def test_validator_rejects_widening(self):
good = json.loads((ROOT / ccr()["openbao"]["auth"]["role_file"]).read_text())
cases = [
("other policy", {"token_policies": [ISSUER, "platform-admin"]}),
("no aal2", {"bound_claims": {"groups": ["net-kingdom-admins"]}}),
("public callback", {"allowed_redirect_uris": ["https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback"]}),
("long ttl", {"token_ttl": "1h"}),
# Probe 2026-10-10: a limited-use token cannot create the grant token.
("limited uses", {"token_num_uses": 8}),
]
for label, change in cases:
with self.subTest(label):
with tempfile.TemporaryDirectory(dir=ROOT / "openbao/auth") as tmp:
role_file = Path(tmp) / "role.json"
role_file.write_text(json.dumps({**good, **change}))
c = ccr()
c["openbao"]["auth"]["role_file"] = str(role_file.relative_to(ROOT))
self.assertNotEqual(lane.validate(c, ROOT), [], label)
c = ccr()
c["openbao"]["issuer_policy"] = "platform-admin"
self.assertNotEqual(lane.validate(c, ROOT), [])
def _apply(self, policies, mounts=None):
tmp = Path(tempfile.mkdtemp())
state = {"mounts": mounts if mounts is not None else {"netkingdom/": "oidc"},
"policies": policies, "roles": {}}
(tmp / "state.json").write_text(json.dumps(state))
bao = tmp / "bao"
bao.write_text(FAKE_BAO)
bao.chmod(bao.stat().st_mode | stat.S_IEXEC)
os.environ["FAKE_BAO_STATE"] = str(tmp / "state.json")
error = None
try:
lane.run_apply(ccr(), ROOT, str(bao), quiet=True)
except lane.BaoError as exc:
error = exc
return error, json.loads((tmp / "state.json").read_text())
def live_issuer(self):
return {ISSUER: (ROOT / f"openbao/policies/{ISSUER}.hcl").read_text()}
def test_apply_writes_role_and_reads_back(self):
error, state = self._apply(self.live_issuer())
self.assertIsNone(error)
self.assertIn("auth/netkingdom/role/credential-broker-keycape-client-secret-generate", state["roles"])
def test_apply_refuses_missing_or_drifted_issuer_policy_before_writing(self):
for policies in ({}, {ISSUER: 'path "platform/*" { capabilities = ["read"] }'}):
with self.subTest(policies=list(policies)):
error, state = self._apply(policies)
self.assertIsNotNone(error)
self.assertEqual(state["roles"], {})
def test_apply_refuses_non_oidc_mount(self):
error, state = self._apply(self.live_issuer(), {"netkingdom/": "jwt"})
self.assertIsNotNone(error)
self.assertEqual(state["roles"], {})
if __name__ == "__main__":
unittest.main()