"""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()