tools/service-binds/executor-lanes.py (bind-lane.py shape): under both sha256-bound, approved, binding-confirmed CCRs (0058 write, 0059 journey) and an attended platform-admin, apply both policies, set cas_required on the svc-authelia key, create the standing journey-read role, refuse if the window-scoped write role exists, prove the agent fence; verify proves the journey token reads only its path (<=15m) and denies wrong SA, wrong audience and write-role login. Value-free receipts. 12 tests on a fake OpenBao (real helpers), in make bounded-executor-test. Founder runbook: sso-mfa/k8s/bounded-executor/T10-LANES-RUNBOOK.md. 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
163 lines
8.1 KiB
Python
163 lines
8.1 KiB
Python
"""executor-lanes.py against an in-memory OpenBao and Kubernetes (NK-WP-0056-T10); no values leave the fakes."""
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
PLATFORM = ROOT.parent / 'railiance-platform'
|
|
CCRS = {'write': 'CCR-2026-0058-net-kingdom-bind-executor-svc-authelia-write.yaml',
|
|
'journey': 'CCR-2026-0059-net-kingdom-bind-executor-syn-journey-read.yaml'}
|
|
JOURNEY = 'platform/data/workloads/net-kingdom/synthetic-journey/syn-journey'
|
|
BIND = 'platform/data/workloads/net-kingdom/directory-binds/svc-authelia'
|
|
FENCE = ''.join(f'path "{p}" {{ capabilities = ["deny"] }}\n' for p in (
|
|
JOURNEY, JOURNEY.replace('/data/', '/metadata/'), BIND, BIND.replace('/data/', '/metadata/')))
|
|
|
|
pytestmark = pytest.mark.skipif(not all((PLATFORM / 'credential-change-requests' / c).exists() for c in CCRS.values()),
|
|
reason='railiance-platform checkout with CCR-2026-0058/0059 required')
|
|
|
|
|
|
def platform_copy(tmp_path, confirm=True):
|
|
root = tmp_path / 'railiance-platform'
|
|
shutil.copytree(PLATFORM / 'scripts', root / 'scripts', ignore=shutil.ignore_patterns('__pycache__'))
|
|
(root / 'openbao').mkdir(); shutil.copytree(PLATFORM / 'openbao' / 'policies', root / 'openbao' / 'policies')
|
|
(root / 'credential-change-requests').mkdir()
|
|
for name in CCRS.values():
|
|
shutil.copy(PLATFORM / 'credential-change-requests' / name, root / 'credential-change-requests' / name)
|
|
if confirm:
|
|
subprocess.run([sys.executable, str(root / 'scripts/credential-change.py'), 'confirm-binding',
|
|
'--reviewer', 'platform-owner', '--comment', 'synthetic test',
|
|
str(root / 'credential-change-requests' / name)], check=True, capture_output=True)
|
|
return root
|
|
|
|
|
|
def digests(root):
|
|
return {k: hashlib.sha256((root / 'credential-change-requests' / v).read_bytes()).hexdigest() for k, v in CCRS.items()}
|
|
|
|
|
|
class FakeBao:
|
|
def __init__(self, m):
|
|
self.m = m
|
|
self.store = {m.BOUNDARY: {'policy': 'path "x" { capabilities = ["deny"] }\n' + FENCE}}
|
|
self.cas = False
|
|
self.identity = {'policies': ['platform-admin'], 'path': 'auth/netkingdom/oidc/callback'}
|
|
self.login_ok = {('nk-bind-executor', 'openbao', m.LANES['journey']['role'])}
|
|
self.wide = False
|
|
self.revoked = []
|
|
|
|
def __call__(self, args, *, payload=None, token=None, allow_failure=False):
|
|
out = lambda d, code=0, err=b'': SimpleNamespace(returncode=code, stdout=json.dumps(d).encode(), stderr=err)
|
|
missing = out({}, 2, b'No value found')
|
|
if args[:2] == ['token', 'lookup']: return out({'data': self.identity})
|
|
if args[:2] == ['token', 'create']: return out({'auth': {'client_token': 'child'}})
|
|
if args[:3] == ['kv', 'metadata', 'get']: return out({'data': {'cas_required': self.cas}})
|
|
if args[:3] == ['kv', 'metadata', 'patch']:
|
|
assert '-cas-required=true' in args; self.cas = True; return out({})
|
|
if args[0] == 'read':
|
|
path = args[-1]
|
|
if path == JOURNEY and token == 'reader':
|
|
return out({'data': {'data': {'PASSWORD': 'SYNTHETIC-PW-VALUE', 'TOTP_SEED': 'SYNTHETIC-SEED-VALUE'}, 'metadata': {'version': 1}}})
|
|
return out({'data': self.store[path]}) if path in self.store else missing
|
|
if args[0] == 'write':
|
|
path = args[-2] if args[-1] == '-' else args[-1]
|
|
if path == 'auth/token/revoke': self.revoked.append(payload['token']); return out({})
|
|
if path == 'sys/capabilities':
|
|
def cap(p):
|
|
if payload['token'] == 'reader' and (p == JOURNEY or self.wide): return ['read']
|
|
return ['deny']
|
|
return out({'data': {p: cap(p) for p in payload['paths']}})
|
|
if path == 'auth/kubernetes/login':
|
|
sa, aud = payload['jwt'].split('|')
|
|
if (sa, aud, payload['role']) in self.login_ok:
|
|
return out({'auth': {'client_token': 'reader', 'lease_duration': 900,
|
|
'token_policies': [self.m.LANES['journey']['policy']]}})
|
|
return out({}, 2, b'Code: 403')
|
|
if path.startswith('auth/kubernetes/role/'):
|
|
self.store[path] = {k: (900 if v == '15m' else v) for k, v in payload.items()}; return out({})
|
|
self.store[path] = payload; return out({})
|
|
raise AssertionError(args)
|
|
|
|
|
|
def build(tmp_path, monkeypatch, confirm=True):
|
|
root = platform_copy(tmp_path, confirm)
|
|
monkeypatch.setenv('NK_PLATFORM_ROOT', str(root))
|
|
spec = importlib.util.spec_from_file_location('executor_lanes', ROOT / 'tools/service-binds/executor-lanes.py')
|
|
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
|
|
fake = FakeBao(m)
|
|
for module in (m, sys.modules['state_hub_preflight_lane'], sys.modules['telemetry_smtp_delivery']):
|
|
if hasattr(module, 'bao'):
|
|
monkeypatch.setattr(module, 'bao', fake) # the real helpers (ensure, capabilities, revoke) run on the fake
|
|
monkeypatch.setattr(m, 'kube', lambda kc, args: f'{args[4]}|{args[5].split("=", 1)[1]}'.encode())
|
|
return m, fake, digests(root)
|
|
|
|
|
|
def run(m, action, d):
|
|
r = {}
|
|
try:
|
|
(m.setup(d, r) if action == 'setup' else m.verify(d, 'kc', r))
|
|
except m.Refused as error:
|
|
r.update(status='failed', reason=str(error))
|
|
return r
|
|
|
|
|
|
def test_setup_applies_policies_cas_and_journey_role_but_never_the_write_role(tmp_path, monkeypatch):
|
|
m, fake, d = build(tmp_path, monkeypatch)
|
|
r = run(m, 'setup', d)
|
|
assert r['status'] == 'lanes_prepared', r
|
|
for lane in m.LANES.values():
|
|
assert fake.store['sys/policies/acl/' + lane['policy']]['policy'] == lane['policy_text']
|
|
assert fake.cas is True
|
|
assert 'auth/kubernetes/role/' + m.LANES['journey']['role'] in fake.store
|
|
assert 'auth/kubernetes/role/' + m.LANES['write']['role'] not in fake.store
|
|
assert run(m, 'setup', d)['status'] == 'lanes_prepared' # idempotent
|
|
|
|
|
|
def test_verify_after_setup(tmp_path, monkeypatch):
|
|
m, fake, d = build(tmp_path, monkeypatch)
|
|
run(m, 'setup', d)
|
|
r = run(m, 'verify', d)
|
|
assert r['status'] == 'lanes_verified', r
|
|
assert r['write_login_denied'] and r['wrong_audience_denied'] and 'reader' in fake.revoked
|
|
assert 'SYNTHETIC-' not in json.dumps(r) # no values in the receipt
|
|
|
|
|
|
@pytest.mark.parametrize('mutate, reason', [
|
|
(lambda m, f, d: d.update(write='0' * 64), 'write:ccr_digest'),
|
|
(lambda m, f, d: f.store.update({m.BOUNDARY: {'policy': 'path "x" { capabilities = ["deny"] }\n'}}),
|
|
'agent_fence_missing'),
|
|
(lambda m, f, d: f.identity.update(policies=['root']), 'attended_platform_admin_required'),
|
|
(lambda m, f, d: f.store.update({'auth/kubernetes/role/' + m.LANES['write']['role']: {}}),
|
|
'write_role_exists_outside_a_window'),
|
|
(lambda m, f, d: f.store.update({'sys/policies/acl/' + m.LANES['journey']['policy']: {'policy': 'path "*" {}'}}),
|
|
'net-kingdom-bind-executor-syn-journey-read:policy_drift'),
|
|
])
|
|
def test_setup_refusals(tmp_path, monkeypatch, mutate, reason):
|
|
m, fake, d = build(tmp_path, monkeypatch)
|
|
mutate(m, fake, d)
|
|
r = run(m, 'setup', d)
|
|
assert (r['status'], r['reason']) == ('failed', reason)
|
|
|
|
|
|
def test_unconfirmed_binding_is_refused(tmp_path, monkeypatch):
|
|
m, fake, d = build(tmp_path, monkeypatch, confirm=False)
|
|
assert run(m, 'setup', d)['reason'] == 'write:binding_unconfirmed'
|
|
|
|
|
|
@pytest.mark.parametrize('mutate, reason', [
|
|
(lambda m, f: f.login_ok.add(('nk-bind-executor', 'openbao', m.LANES['write']['role'])), 'negative_login_accepted'),
|
|
(lambda m, f: f.login_ok.add(('default', 'openbao', m.LANES['journey']['role'])), 'negative_login_accepted'),
|
|
(lambda m, f: setattr(f, 'wide', True), 'journey_capabilities'),
|
|
(lambda m, f: setattr(f, 'cas', False), 'cas_required_not_set'),
|
|
])
|
|
def test_verify_refusals(tmp_path, monkeypatch, mutate, reason):
|
|
m, fake, d = build(tmp_path, monkeypatch)
|
|
run(m, 'setup', d)
|
|
mutate(m, fake)
|
|
r = run(m, 'verify', d)
|
|
assert (r['status'], r['reason']) == ('failed', reason)
|