informed-decision/tests/review_components.py
tegwick 83849b75d4 Connect policy-gated browser review and audit runtime
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
2026-09-11 00:31:03 +02:00

79 lines
4.3 KiB
Python

"""Disposable actual Flex Auth HTTP process, with synthetic Kubernetes custody.
The evaluator and inbound caller authenticator are real. TokenReview and its
identities are fixtures; this does not admit any production caller or policy.
"""
from contextlib import contextmanager
from datetime import datetime, timedelta, timezone
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import json
import os
from pathlib import Path
import socket
import subprocess
import threading
import time
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.x509.oid import NameOID
from informed_decision.http_transport import JSONTransport, TransportError
from informed_decision.policy import PolicyClient
FIXTURE = Path(__file__).parent / 'fixtures/review-policy'
@contextmanager
def flex_component(binary, directory, signing_key):
tokens={'synthetic-workload-token':'system:serviceaccount:informed-decision:review',
'synthetic-wrong-caller':'system:serviceaccount:other:other'}
calls=[]
class Reviewer(BaseHTTPRequestHandler):
def log_message(self,*args):pass
def do_POST(self):
body=json.loads(self.rfile.read(int(self.headers['Content-Length'])))
token=body['spec']['token'];calls.append({'known':token in tokens,'audiences':body['spec']['audiences']})
valid=(self.path=='/apis/authentication.k8s.io/v1/tokenreviews'
and self.headers.get('Authorization')=='Bearer synthetic-reviewer'
and body['spec']['audiences']==['flex-auth'] and token in tokens)
raw=json.dumps({'status':{'authenticated':valid,'audiences':['flex-auth'] if valid else [],
'user':{'username':tokens.get(token,'')}}}).encode()
self.send_response(201);self.send_header('Content-Type','application/json');self.end_headers();self.wfile.write(raw)
server=ThreadingHTTPServer(('127.0.0.1',0),Reviewer)
thread=threading.Thread(target=server.serve_forever,daemon=True);thread.start()
now=datetime.now(timezone.utc)
name=x509.Name([x509.NameAttribute(NameOID.COMMON_NAME,'synthetic-tokenreview-fixture')])
cert=(x509.CertificateBuilder().subject_name(name).issuer_name(name).public_key(signing_key.public_key())
.serial_number(x509.random_serial_number()).not_valid_before(now-timedelta(minutes=1))
.not_valid_after(now+timedelta(hours=1)).add_extension(x509.BasicConstraints(ca=True,path_length=None),critical=True)
.sign(signing_key,hashes.SHA256()))
ca=directory/'fixture-ca.pem';ca.write_bytes(cert.public_bytes(serialization.Encoding.PEM))
reviewer=directory/'reviewer-token';reviewer.write_text('synthetic-reviewer');reviewer.chmod(0o600)
with socket.socket() as listener:
listener.bind(('127.0.0.1',0));port=listener.getsockname()[1]
origin=f'http://127.0.0.1:{port}'
process=subprocess.Popen([str(binary),'serve','--addr',f'127.0.0.1:{port}',
'--registry',str(FIXTURE/'registry.json'),'--policy',str(FIXTURE/'policy.md'),
'--caller-auth-mode','enforce','--caller-binding','informed-decision=system:serviceaccount:informed-decision:review',
'--caller-kubernetes-url',f'http://127.0.0.1:{server.server_port}',
'--caller-reviewer-token-file',str(reviewer),'--caller-ca-file',str(ca)],stdout=subprocess.DEVNULL,stderr=subprocess.PIPE)
try:
transport=JSONTransport(allow_internal_http=True)
for _ in range(100):
if process.poll() is not None:
raise RuntimeError('fixture evaluator exited: '+process.stderr.read().decode()[:1000])
try:
if transport.request('GET',origin+'/healthz')[0]==200:break
except TransportError:pass
time.sleep(.05)
else:raise RuntimeError('fixture evaluator did not start')
pin=json.loads((FIXTURE/'pin.json').read_text());pin.pop('fixture_only')
client=PolicyClient(origin,lambda:'synthetic-workload-token',allow_internal_http=True,**pin)
yield client,calls
finally:
process.terminate()
try:process.wait(timeout=5)
except subprocess.TimeoutExpired:process.kill();process.wait(timeout=5)
process.stderr.close();server.shutdown();server.server_close();thread.join(timeout=5)