NK-WP-0056-T18 (part): executor's own approval client and request phase

KeyCapeClientCredentials: client_secret_basic exchange, one scope per token,
public Host for the in-cluster URL, no redirects, bounded response checks.
ApprovalEngine takes a per-scope token provider (create/read/consume) and
can create. cli.py --request files the approval for one admission as
service:nk-bind-executor (principal = first approver's subject, target
admission id+digest, validity = window, human_control, required_count =
approvers) and verifies the created object. The client secret comes through
its own OpenBao lane (oidc-service-client case), read once, login revoked at
once, kept in memory only. Refusals before any call: other executor,
unmapped approver, closed window, shared role. 56 executor tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
This commit is contained in:
tegwick 2026-10-31 23:02:19 +01:00
parent 5d5f15df55
commit b71f509ce0
3 changed files with 437 additions and 13 deletions

View file

@ -19,6 +19,19 @@ Exit codes: 0 completed, 3 reverted, 4 refused before any effect, 5 stopped / un
python3 bounded-executor/cli.py --journey-only --config FILE [--result FILE]
python3 bounded-executor/cli.py --request --admission FILE --config FILE [--result FILE]
Request (NK-WP-0056-T18): file the approval for one admission as the executor itself (approval-engine requires
binding.actor == the token's sub). Binding: action bounded-execution, actor = executor_identity, principal = the
first listed approver's subject, target {admission_id, admission_digest}, validity = the admission window,
human_control on, required_count = number of listed approvers. Prints the approval id; consumes nothing.
Exit codes: 0 requested, 4 refused.
approval_engine.client{token_url, client_id, k8s_role, kv, host?} selects the executor's own KeyCape client
(SecurityCaseType oidc-service-client, case nk-bind-executor-approval): the client secret is read once through its
own OpenBao lane (k8s_role, field CLIENT_SECRET), that login is revoked at once, and every approval call exchanges a
fresh token for exactly its scope. approval_engine.token_file remains for fixtures.
Journey-only (NK-WP-0056-T10): read-only, no admission. Logs in with the journey role only (journey.k8s_role, the
openbao-audience token), runs the synthetic journey once and prints its value-free receipt. No write role, no
Kubernetes or directory client, no approval or audit call. Needs only openbao{addr, jwt_file, ca_file?} and
@ -50,12 +63,39 @@ C = _load("clients")
EXIT = {"completed": 0, "reverted": 3, "refused": 4}
def approval_carrier(cfg: dict, logins: list):
"""The approval-engine client: the executor's own KeyCape client when configured, else a token file."""
ae, o = cfg["approval_engine"], cfg["openbao"]
if "client" not in ae:
return C.ApprovalEngine(ae["url"], ae["token_file"], ae.get("ca_file"))
c = ae["client"]
lane = OPS.OpenBao(o["addr"], k8s_role=c["k8s_role"], jwt_file=o["jwt_file"], ca_file=o.get("ca_file"))
logins.append(lane)
data, _ = lane.read(c["kv"])
lane.revoke_self() # one read; the role's token is not kept for the run
if set(data) != {"CLIENT_SECRET"} or not isinstance(data["CLIENT_SECRET"], str) or not data["CLIENT_SECRET"]:
raise OPS.OperationError("approval client kv shape")
secret = data["CLIENT_SECRET"]
tokens = C.KeyCapeClientCredentials(c["token_url"], c["client_id"], lambda: secret, host=c.get("host"),
ca_file=c.get("ca_file"))
return C.ApprovalEngine(ae["url"], token_provider=tokens.token, ca_file=ae.get("ca_file"))
def distinct_roles(cfg: dict) -> None:
roles = [cfg["openbao"].get("k8s_role"), cfg.get("journey", {}).get("k8s_role"),
cfg.get("approval_engine", {}).get("client", {}).get("k8s_role")]
roles = [r for r in roles if r]
if len(roles) != len(set(roles)):
raise ValueError("every lane must use its own OpenBao role")
def build(cfg: dict, logins: list):
"""`logins` collects every OpenBao client as soon as it exists, so the caller revokes them all, even when a
later step of setup fails."""
o = cfg["openbao"]
if cfg["journey"]["k8s_role"] == o["k8s_role"]:
raise ValueError("the journey read and the bind write must use separate roles")
distinct_roles(cfg)
OPS.wait_reachable(o["addr"], ca_file=o.get("ca_file")) # new pod: NetworkPolicy may not be applied yet
@ -79,8 +119,7 @@ def build(cfg: dict, logins: list):
def alert(kind: str, info: dict) -> None:
print(json.dumps({"alert": kind, **info}), file=sys.stderr, flush=True)
deps = X.Deps(carrier=C.ApprovalEngine(cfg["approval_engine"]["url"], cfg["approval_engine"]["token_file"],
cfg["approval_engine"].get("ca_file")),
deps = X.Deps(carrier=approval_carrier(cfg, logins),
operations=operations, checks=checks, trust=lambda state: state in trusted,
audit=C.AuditCore(cfg["audit_core"]["url"], cfg["audit_core"]["token_file"],
cfg["audit_core"].get("ca_file")),
@ -91,6 +130,53 @@ def build(cfg: dict, logins: list):
return config, deps
def request_binding(doc: dict, cfg: dict) -> tuple[dict, dict, int]:
"""The approval binding for one admission. Refuses an invalid admission, another executor, an unmapped
approver or a closed window, before anything is sent."""
report = X.bea.evaluate(doc, json.loads(X.bea.SCHEMA.read_text()))
if not report["valid"]:
raise ValueError("admission invalid")
spec = doc["spec"]
if spec["executor"]["identity"] != cfg["executor_identity"]:
raise ValueError("admission names another executor")
approvers = spec["approval"]["approvers"]
principal = cfg["approver_subjects"][approvers[0]]
closes = X.dt.datetime.fromisoformat(spec["window"]["not_after"].replace("Z", "+00:00"))
if X.dt.datetime.now(X.dt.UTC) >= closes:
raise ValueError("admission window closed")
binding = {"action": X.ACTION, "actor": cfg["executor_identity"], "principal": principal,
"purpose": f"{spec['id']}: {spec['title']}"[:200],
"target": {"admission_id": spec["id"], "admission_digest": X.bea.admission_digest(doc)}}
validity = {"not_before": spec["window"]["not_before"], "expires_at": spec["window"]["not_after"]}
return binding, validity, len(approvers)
def run_request(admission_path: str, cfg: dict) -> tuple[dict, int]:
logins: list = []
try:
doc = X.bea.load(pathlib.Path(admission_path))
binding, validity, required = request_binding(doc, cfg)
distinct_roles(cfg)
OPS.wait_reachable(cfg["openbao"]["addr"], ca_file=cfg["openbao"].get("ca_file"))
created = approval_carrier(cfg, logins).create(binding, validity, required)
got = created.get("binding") or {}
if (got.get("actor") != binding["actor"] or got.get("target") != binding["target"]
or created.get("status") != "requested" or not created.get("id")):
raise ValueError("created approval does not match the request")
result = {"mode": "request", "outcome": "requested", "approval_id": created["id"],
"binding_digest": got.get("digest"), "admission_id": binding["target"]["admission_id"],
"admission_digest": binding["target"]["admission_digest"], "required_count": required,
"effects": "approval requested"}
code = 0
except Exception as error: # nothing was consumed; at most an unapproved request exists
result = {"mode": "request", "outcome": "refused", "reason": "request:" + type(error).__name__}
code = 4
finally:
for client in logins:
client.revoke_self()
return result, code
def journey_only(cfg: dict, logins: list) -> dict:
o, j = cfg["openbao"], cfg["journey"]
OPS.wait_reachable(o["addr"], ca_file=o.get("ca_file")) # new pod: NetworkPolicy may not be applied yet
@ -125,11 +211,18 @@ def exit_code(result: dict) -> int:
def main(argv=None) -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
parser.add_argument("--journey-only", action="store_true", help="read-only journey run, no admission (T10)")
parser.add_argument("--request", action="store_true", help="file the approval for --admission (T18)")
parser.add_argument("--admission")
parser.add_argument("--approval-id")
parser.add_argument("--config", required=True)
parser.add_argument("--result", help="also write the result here")
args = parser.parse_args(argv)
if args.request:
if args.journey_only or args.approval_id or not args.admission:
parser.error("--request takes --admission and --config only")
result, code = run_request(args.admission, json.loads(pathlib.Path(args.config).read_text()))
_emit(result, args.result)
return code
if args.journey_only == bool(args.admission or args.approval_id):
parser.error("give either --journey-only, or --admission with --approval-id")
if not args.journey_only and not (args.admission and args.approval_id):

View file

@ -6,6 +6,7 @@ Errors carry status codes only, never response bodies.
from __future__ import annotations
import base64
import json
import pathlib
import ssl
@ -18,20 +19,83 @@ class ClientError(Exception):
pass
class _NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args, **kwargs): # credentials never follow a redirect
return None
def _opener(ca_file: str | None):
context = ssl.create_default_context(cafile=ca_file) if ca_file else ssl.create_default_context()
return urllib.request.build_opener(urllib.request.ProxyHandler({}), _NoRedirect(),
urllib.request.HTTPSHandler(context=context)).open
class KeyCapeClientCredentials:
"""client_credentials exchange at KeyCape (src/internal/server/oidc/token.go: client_secret_basic, every
requested scope must be allowed for the client). A fresh token per call and scope; nothing is cached or logged,
as secrets-engine's approval consumer does. `secret` is a callable so the value stays in its owner's frame.
`host` sends the public Host (and X-Forwarded-*) when the token URL is the in-cluster service, the way the
synthetic journey reaches KeyCape (sso-mfa/k8s/synthetic-journey/journey.py host_map)."""
def __init__(self, token_url: str, client_id: str, secret, *, host: str | None = None,
ca_file: str | None = None, timeout: float = 15):
parsed = urllib.parse.urlsplit(token_url)
if parsed.scheme not in ("https", "http") or not parsed.netloc or parsed.query or parsed.fragment:
raise ClientError("token url")
if not callable(secret):
raise ClientError("secret provider")
self.token_url, self.client_id, self.secret, self.host = token_url, client_id, secret, host
self.open = _opener(ca_file)
self.timeout = timeout
def token(self, scope: str) -> str:
if not scope or " " in scope:
raise ClientError("one scope per token")
basic = base64.b64encode((urllib.parse.quote(self.client_id, safe="") + ":"
+ urllib.parse.quote(self.secret(), safe="")).encode()).decode()
headers = {"Authorization": "Basic " + basic, "Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json"}
if self.host:
headers.update({"Host": self.host, "X-Forwarded-Host": self.host, "X-Forwarded-Proto": "https"})
body = urllib.parse.urlencode({"grant_type": "client_credentials", "scope": scope}).encode()
request = urllib.request.Request(self.token_url, method="POST", headers=headers, data=body)
try:
with self.open(request, timeout=self.timeout) as response:
data = json.loads(response.read(1 << 16))
except urllib.error.HTTPError as error:
error.close()
raise ClientError(f"token {error.code}") from None
token = data.get("access_token")
if (not isinstance(token, str) or not token or len(token) > 16384 or any(c.isspace() for c in token)
or str(data.get("token_type", "")).lower() != "bearer"
or not isinstance(data.get("expires_in"), int) or not 0 < data["expires_in"] <= 900):
raise ClientError("token response")
return token
class _Http:
def __init__(self, base: str, token_file: str, ca_file: str | None = None, timeout: float = 15):
def __init__(self, base: str, token_file: str | None = None, ca_file: str | None = None, timeout: float = 15,
token_provider=None):
parsed = urllib.parse.urlsplit(base)
if parsed.scheme not in ("https", "http") or not parsed.netloc or parsed.query or parsed.fragment:
raise ClientError("base url")
if (token_file is None) == (token_provider is None):
raise ClientError("exactly one of token_file and token_provider")
self.base = base.rstrip("/")
self.token_file = token_file
context = ssl.create_default_context(cafile=ca_file) if ca_file else ssl.create_default_context()
self.open = urllib.request.build_opener(urllib.request.ProxyHandler({}),
urllib.request.HTTPSHandler(context=context)).open
self.token_provider = token_provider
self.open = _opener(ca_file)
self.timeout = timeout
def call(self, method: str, path: str, body: dict | None = None, headers: dict | None = None) -> tuple[int, dict]:
head = {"Authorization": "Bearer " + pathlib.Path(self.token_file).read_text().strip(),
def bearer(self, scope: str | None) -> str:
if self.token_provider is not None:
return self.token_provider(scope)
return pathlib.Path(self.token_file).read_text().strip()
def call(self, method: str, path: str, body: dict | None = None, headers: dict | None = None,
scope: str | None = None) -> tuple[int, dict]:
head = {"Authorization": "Bearer " + self.bearer(scope),
"Accept": "application/json", **(headers or {})}
data = None
if body is not None:
@ -48,10 +112,20 @@ class _Http:
class ApprovalEngine:
"""GET /v1/approvals/{id} and POST /v1/approvals/{id}/consume (approval-engine api.py)."""
"""POST /v1/approvals, GET /v1/approvals/{id} and POST /v1/approvals/{id}/consume (approval-engine api.py).
Each call asks for exactly the scope it needs (approval:create, approval:read, approval:consume)."""
def __init__(self, base: str, token_file: str, ca_file: str | None = None):
self.http = _Http(base, token_file, ca_file)
def __init__(self, base: str, token_file: str | None = None, ca_file: str | None = None, token_provider=None):
self.http = _Http(base, token_file, ca_file, token_provider=token_provider)
def create(self, binding: dict, validity: dict, required_count: int) -> dict:
"""File the approval request as the actor itself. human_control is always on and pdp_path off (pilot)."""
status, data = self.http.call("POST", "/v1/approvals", {
"binding": binding, "validity": validity, "required_count": required_count,
"human_control": True, "pdp_path": False}, scope="approval:create")
if status != 201:
raise ClientError(f"create {status}")
return data
@staticmethod
def _path(approval_id: str) -> str:
@ -60,11 +134,12 @@ class ApprovalEngine:
return "/v1/approvals/" + urllib.parse.quote(approval_id, safe="")
def get(self, approval_id: str) -> dict:
return self.http.call("GET", self._path(approval_id))[1]
return self.http.call("GET", self._path(approval_id), scope="approval:read")[1]
def consume(self, approval_id: str, request_digest: str, decision_id: str) -> dict:
return self.http.call("POST", self._path(approval_id) + "/consume",
{"request_digest": request_digest, "decision_id": decision_id})[1]
{"request_digest": request_digest, "decision_id": decision_id},
scope="approval:consume")[1]
class AuditCore:

View file

@ -0,0 +1,256 @@
"""The executor's own approval client and request phase (NK-WP-0056-T18). One fake server plays OpenBao, KeyCape and
approval-engine; no live service."""
from __future__ import annotations
import base64
import contextlib
import copy
import importlib.util
import io
import json
import pathlib
import sys
import tempfile
import threading
import unittest
import urllib.parse
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import yaml
HERE = pathlib.Path(__file__).resolve().parent
def load(name):
spec = importlib.util.spec_from_file_location(name, HERE.parent / f"{name}.py")
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
CLI = load("cli")
C, OPS, X = CLI.C, CLI.OPS, CLI.X
ADMISSION = X.REPO / "examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml"
SECRET = "synthetic-client-secret/with:specials"
CLIENT_KV = "platform/workloads/keycape/nk-bind-executor-approval"
APPROVAL_ROLE = "workload-kv-read-keycape-nk-bind-executor-approval-bounded-executor"
class World:
def __init__(self):
self.requests = [] # (method, path, headers, body)
self.revoked = []
self.token_status = 200
self.token_body = None
self.create_status = 201
self.create_override = {}
self.kv = {"CLIENT_SECRET": SECRET}
def serve(w: World):
class H(BaseHTTPRequestHandler):
def _send(self, code, body, headers=None):
raw = json.dumps(body).encode()
self.send_response(code)
for k, v in (headers or {}).items():
self.send_header(k, v)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def _body(self):
return self.rfile.read(int(self.headers.get("Content-Length") or 0))
def do_GET(self):
w.requests.append(("GET", self.path, dict(self.headers), None))
if self.path == "/v1/sys/health":
return self._send(200, {"initialized": True})
if self.path == "/v1/platform/data/workloads/keycape/nk-bind-executor-approval":
return self._send(200, {"data": {"data": w.kv, "metadata": {"version": 1}}})
self._send(404, {})
def do_POST(self):
raw = self._body()
w.requests.append(("POST", self.path, dict(self.headers), raw))
if self.path == "/v1/auth/kubernetes/login":
role = json.loads(raw)["role"]
return self._send(200, {"auth": {"client_token": f"tok-{role}", "lease_duration": 900}})
if self.path == "/v1/auth/token/revoke-self":
w.revoked.append(self.headers.get("X-Vault-Token"))
return self._send(204, {})
if self.path == "/token":
if w.token_status == 302:
return self._send(302, {}, {"Location": "http://elsewhere.invalid/token"})
if w.token_status != 200:
return self._send(w.token_status, {"error": "invalid_scope"})
scope = urllib.parse.parse_qs(raw.decode())["scope"][0]
return self._send(200, w.token_body or {"access_token": "at-" + scope.replace(":", "-"),
"token_type": "Bearer", "expires_in": 900})
if self.path == "/v1/approvals":
body = json.loads(raw)
created = {"id": "apr-1", "status": "requested",
"binding": {**body["binding"], "digest": "sha256:" + "ab" * 32, "human_control": True},
"validity": body["validity"], **w.create_override}
return self._send(w.create_status, created)
self._send(404, {})
def log_message(self, *a):
return
server = ThreadingHTTPServer(("127.0.0.1", 0), H)
threading.Thread(target=server.serve_forever, kwargs={"poll_interval": 0.02}, daemon=True).start()
return server
class Base(unittest.TestCase):
def setUp(self):
self.w = World()
self.server = serve(self.w)
self.addCleanup(self.server.shutdown)
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
self.root = pathlib.Path(tmp.name)
(self.root / "jwt").write_text("synthetic-openbao-audience-jwt\n")
self.addr = f"http://127.0.0.1:{self.server.server_port}"
def cfg(self, **approval_client):
return {
"executor_identity": "service:nk-bind-executor",
"approver_subjects": {"admin": "uid=admin,ou=people,dc=netkingdom,dc=local"},
"openbao": {"addr": self.addr, "k8s_role": "net-kingdom-bind-executor-svc-authelia-write",
"jwt_file": str(self.root / "jwt")},
"journey": {"kv": "x", "k8s_role": "net-kingdom-bind-executor-syn-journey-read", "config": {}},
"approval_engine": {"url": self.addr, "client": {
"token_url": self.addr + "/token", "client_id": "nk-bind-executor-approval",
"k8s_role": APPROVAL_ROLE, "kv": CLIENT_KV, "host": "kc.coulomb.social", **approval_client}},
}
def calls(self, path):
return [r for r in self.w.requests if r[1] == path]
class KeyCapeExchange(Base):
def client(self):
return C.KeyCapeClientCredentials(self.addr + "/token", "nk-bind-executor-approval", lambda: SECRET,
host="kc.coulomb.social")
def test_client_secret_basic_one_scope_public_host(self):
self.assertEqual(self.client().token("approval:create"), "at-approval-create")
_, _, headers, raw = self.calls("/token")[0]
user, _, password = base64.b64decode(headers["Authorization"].split()[1]).decode().partition(":")
self.assertEqual((urllib.parse.unquote(user), urllib.parse.unquote(password)),
("nk-bind-executor-approval", SECRET))
self.assertEqual(urllib.parse.parse_qs(raw.decode()),
{"grant_type": ["client_credentials"], "scope": ["approval:create"]})
self.assertEqual((headers["Host"], headers["X-Forwarded-Proto"]), ("kc.coulomb.social", "https"))
def test_refusals(self):
with self.assertRaises(C.ClientError):
self.client().token("approval:read approval:create") # one scope per token
self.w.token_status = 400
with self.assertRaisesRegex(C.ClientError, "token 400"):
self.client().token("approval:read")
self.w.token_status = 302 # credentials never follow a redirect
with self.assertRaises(C.ClientError):
self.client().token("approval:read")
self.w.token_status = 200
for body in ({"access_token": "a", "token_type": "Bearer", "expires_in": 3600},
{"access_token": "a", "token_type": "mac", "expires_in": 900},
{"access_token": "a b", "token_type": "Bearer", "expires_in": 900}):
self.w.token_body = body
with self.subTest(body=body), self.assertRaisesRegex(C.ClientError, "token response"):
self.client().token("approval:read")
class ScopesPerCall(Base):
def test_each_approval_call_asks_for_exactly_its_scope(self):
seen = []
ae = C.ApprovalEngine(self.addr, token_provider=lambda scope: seen.append(scope) or "t")
ae.create({"action": "a"}, {}, 1)
with contextlib.suppress(C.ClientError):
ae.get("apr-1")
with contextlib.suppress(C.ClientError):
ae.consume("apr-1", "sha256:" + "0" * 64, "run-1")
self.assertEqual(seen, ["approval:create", "approval:read", "approval:consume"])
body = json.loads(self.calls("/v1/approvals")[0][3])
self.assertEqual((body["human_control"], body["pdp_path"], body["required_count"]), (True, False, 1))
def test_exactly_one_token_source(self):
with self.assertRaises(C.ClientError):
C.ApprovalEngine(self.addr)
with self.assertRaises(C.ClientError):
C.ApprovalEngine(self.addr, token_file="/x", token_provider=lambda s: "t")
class RequestPhase(Base):
def run_cli(self, cfg, admission=ADMISSION):
(self.root / "cfg.json").write_text(json.dumps(cfg))
with contextlib.redirect_stdout(io.StringIO()) as out:
code = CLI.main(["--request", "--admission", str(admission), "--config", str(self.root / "cfg.json")])
return code, json.loads(out.getvalue()), out.getvalue()
def admission_with(self, mutate):
doc = yaml.safe_load(ADMISSION.read_text())
mutate(doc["spec"])
path = self.root / "admission.yaml"
path.write_text(yaml.safe_dump(doc, sort_keys=False))
return path
def test_files_the_approval_as_the_executor(self):
code, result, text = self.run_cli(self.cfg())
self.assertEqual((code, result["outcome"], result["approval_id"]), (0, "requested", "apr-1"))
doc = X.bea.load(ADMISSION)
body = json.loads(self.calls("/v1/approvals")[0][3])
self.assertEqual(body["binding"], {
"action": "bounded-execution", "actor": "service:nk-bind-executor",
"principal": "uid=admin,ou=people,dc=netkingdom,dc=local",
"purpose": f"{doc['spec']['id']}: {doc['spec']['title']}"[:200],
"target": {"admission_id": doc["spec"]["id"], "admission_digest": X.bea.admission_digest(doc)}})
self.assertEqual(body["validity"], {"not_before": doc["spec"]["window"]["not_before"],
"expires_at": doc["spec"]["window"]["not_after"]})
self.assertEqual((body["human_control"], body["pdp_path"], body["required_count"]), (True, False, 1))
# the approval token was exchanged for exactly approval:create, with the bearer from KeyCape
self.assertEqual(self.calls("/v1/approvals")[0][2]["Authorization"], "Bearer at-approval-create")
# the approval lane logged in once, read the secret and was revoked; no other lane logged in
logins = [json.loads(r[3])["role"] for r in self.calls("/v1/auth/kubernetes/login")]
self.assertEqual(logins, [APPROVAL_ROLE])
self.assertIn("tok-" + APPROVAL_ROLE, self.w.revoked)
self.assertNotIn(SECRET, text)
def test_refused_before_anything_is_sent(self):
cases = {
"another executor": (self.cfg(), self.admission_with(
lambda s: s["executor"].update(identity="service:other"))),
"unmapped approver": ({**self.cfg(), "approver_subjects": {}}, ADMISSION),
"closed window": (self.cfg(), self.admission_with(lambda s: s["window"].update(
not_before="2026-01-01T09:00:00Z", not_after="2026-01-01T11:00:00Z"))),
"shared role": (self.cfg(k8s_role="net-kingdom-bind-executor-syn-journey-read"), ADMISSION),
}
for label, (cfg, admission) in cases.items():
self.w.requests.clear()
with self.subTest(label):
code, result, _ = self.run_cli(cfg, admission)
self.assertEqual((code, result["outcome"]), (4, "refused"))
self.assertEqual([r for r in self.w.requests if r[1] != "/v1/sys/health"], [])
def test_refused_when_the_created_approval_differs(self):
for override in ({"status": "approved"}, {"binding": {"actor": "service:other"}}):
self.w.create_override = override
with self.subTest(override=override):
code, result, _ = self.run_cli(self.cfg())
self.assertEqual((code, result["outcome"]), (4, "refused"))
def test_bad_client_kv_shape_is_refused_and_revoked(self):
self.w.kv = {"CLIENT_SECRET": SECRET, "EXTRA": "x"}
code, result, text = self.run_cli(self.cfg())
self.assertEqual((code, result["reason"]), (4, "request:OperationError"))
self.assertIn("tok-" + APPROVAL_ROLE, self.w.revoked)
self.assertEqual(self.calls("/v1/approvals"), [])
self.assertNotIn(SECRET, text)
if __name__ == "__main__":
unittest.main()