net-kingdom/tools/service-binds/executor-lanes.py
tegwick c43afd46a7 NK-WP-0056-T06 prep: admission helper, write-role window-open/close, service-client case; T15 evidence
- bounded-executor/prepare_admission.py: the live admission from the pilot example, pins read
  from the image's commit, window <= 2 h (audit-core's declared bound), validated.
- executor-lanes.py window-open/window-close: CCR-2026-0058's window-scoped write role, created
  for one admitted open window under attended platform-admin and deleted afterwards (idempotent).
- security-cases/nk-bind-executor-approval.yaml: the case of record, proposed (founder acceptance).
- docs/evidence/2026-11-04-nk56-t15-audit-sender: 8 checks against audit-core 876c3f6.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 523928@bnt-lap001
Assistant-Session: 6c737afb-9c14-4152-9f02-477214b9c350
2026-11-04 01:49:40 +01:00

321 lines
18 KiB
Python

#!/usr/bin/env python3
"""Attended OpenBao setup and proof for the bounded executor's two lanes (NK-WP-0056-T10, T06).
write CCR-2026-0058 policy only. Its Kubernetes role is window-scoped: created per admitted run by
window-open, deleted by window-close. setup sets cas_required on the svc-authelia key.
journey CCR-2026-0059 policy and the standing read role for sso/nk-bind-executor.
setup: under both approved, sha256-bound CCRs (binding confirmed) and an attended platform-admin session:
check that the coding-agent fence denies both paths, apply both policies, set cas_required, create the
journey role, prove that the write role does not exist, and prove that the fence beats both policies.
verify: read back policies, cas_required and the role; log in as sso/nk-bind-executor (audience openbao) and prove
that the token reads only the journey path, with exactly its policy and at most 15m. Prove that a wrong
ServiceAccount, a wrong audience and a login to the write role are all denied.
window-open --admission FILE: for one admitted run (T06): the admission is valid, names this executor and the write
lane, and its window is open now; the policy, cas_required and the fence hold; the write role is absent. Then
create the write role exactly as CCR-2026-0058 declares it.
window-close --admission FILE: after the run's receipt or at window end, whichever comes first: delete the write role
and prove it is gone. Always allowed, also late; needs no CCR digests.
Receipts hold closed booleans only. The journey values are read into memory to check their field names, and never
printed or written.
"""
import argparse
import hashlib
import importlib.util
import json
import os
import subprocess
import sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
REPO = HERE.parents[1]
PLATFORM = Path(os.environ.get('NK_PLATFORM_ROOT', REPO.parent / 'railiance-platform'))
sys.path.insert(0, str(PLATFORM / 'scripts'))
from state_hub_preflight_lane import bao, data, capabilities, revoke # noqa: E402
from repair_eso_kubernetes_auth import role_payload, check_role # noqa: E402
from telemetry_smtp_delivery import ensure # noqa: E402
SA, NS = 'nk-bind-executor', 'sso'
APPROVERS = {'platform-owner', 'net-kingdom-owner'}
BOUNDARY = 'sys/policies/acl/agent-high-risk-boundary'
BINDS = 'platform/data/workloads/net-kingdom/directory-binds/'
LANES = {
'write': {
'ccr': 'CCR-2026-0058-net-kingdom-bind-executor-svc-authelia-write.yaml',
'request_type': 'workload-kv-write-next-version',
'kv': 'platform/workloads/net-kingdom/directory-binds/svc-authelia', 'fields': ['LDAP_BIND_PASSWORD'],
'policy': 'net-kingdom-bind-executor-svc-authelia-write',
'policy_text': ('path "platform/data/workloads/net-kingdom/directory-binds/svc-authelia" {\n'
' capabilities = ["read", "update"]\n allowed_parameters = {\n "data" = []\n'
' "options" = []\n }\n}\n\npath "auth/token/lookup-self" {\n capabilities = ["read"]\n}\n\n'
'path "auth/token/revoke-self" {\n capabilities = ["update"]\n}\n'),
},
'journey': {
'ccr': 'CCR-2026-0059-net-kingdom-bind-executor-syn-journey-read.yaml',
'request_type': 'workload-kv-read',
'kv': 'platform/workloads/net-kingdom/synthetic-journey/syn-journey', 'fields': ['PASSWORD', 'TOTP_SEED'],
'policy': 'net-kingdom-bind-executor-syn-journey-read',
'policy_text': ('path "platform/data/workloads/net-kingdom/synthetic-journey/syn-journey" '
'{ capabilities = ["read"] }\n'
'path "auth/token/lookup-self" { capabilities = ["read"] }\n'
'path "auth/token/revoke-self" { capabilities = ["update"] }\n'),
},
}
for _lane in LANES.values():
_lane.update(role=_lane['policy'], service_account=SA, namespace=NS,
data_path=_lane['kv'].replace('platform/', 'platform/data/', 1),
metadata_path=_lane['kv'].replace('platform/', 'platform/metadata/', 1))
CAS_KEY = 'platform/metadata/workloads/net-kingdom/directory-binds/svc-authelia'
KEPT_METADATA = ('max_versions', 'delete_version_after', 'custom_metadata')
class Refused(Exception):
pass
def load(name, path):
spec = importlib.util.spec_from_file_location(name, path)
module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module)
return module
def contract(name, expected):
lane = LANES[name]
path = PLATFORM / 'credential-change-requests' / lane['ccr']
if hashlib.sha256(path.read_bytes()).hexdigest() != expected: raise Refused(name + ':ccr_digest')
ccr, errors, _ = load('credential_change', PLATFORM / 'scripts/credential-change.py').validate_ccr(path)
if errors or ccr['status'] not in {'approved', 'applied', 'verified', 'active'}: raise Refused(name + ':ccr_status')
approved = {r['reviewer'] for r in ccr['review'].get('comments', []) if r.get('decision') == 'approved'}
if not APPROVERS <= approved: raise Refused(name + ':ccr_approval')
a = ccr['openbao']; auth = a['auth']
if (ccr['request_type'] != lane['request_type'] or a['kv_path'] != lane['kv'] or a['fields'] != lane['fields']
or a['policy_name'] != lane['policy'] or auth['role'] != lane['role'] or auth['policies'] != [lane['policy']]
or auth.get('method') != 'kubernetes' or auth.get('audience') != 'openbao'
or auth['bound_claims'] != {'service_account_names': [SA], 'service_account_namespaces': [NS]}
or auth.get('token_no_default_policy') is not True
or any(auth.get(k) != '15m' for k in ('ttl', 'token_max_ttl', 'token_explicit_max_ttl'))):
raise Refused(name + ':ccr_shape')
if auth.get('bound_claims_confirmed') is not True: raise Refused(name + ':binding_unconfirmed')
if (PLATFORM / a['policy_file']).read_text() != lane['policy_text']: raise Refused(name + ':policy_file')
return ccr
def attended_platform_admin():
ident = data(bao(['token', 'lookup', '-format=json']))['data']
if ('platform-admin' not in ident.get('policies', []) or 'root' in ident.get('policies', [])
or not ident.get('path', '').startswith('auth/netkingdom/')):
raise Refused('attended_platform_admin_required')
def fence_present():
current = data(bao(['read', '-format=json', BOUNDARY]))['data']['policy']
return all('path "' + p + '" { capabilities = ["deny"] }' in current
for lane in LANES.values() for p in (lane['data_path'], lane['metadata_path']))
def policy_check(lane):
def check(actual):
if actual.get('policy') != lane['policy_text']: raise Refused(lane['policy'] + ':policy_drift')
return check
def key_metadata():
return data(bao(['read', '-format=json', CAS_KEY]))['data']
def cas_required():
return key_metadata().get('cas_required') is True
def set_cas_required():
"""A metadata write that names only cas_required. platform-admin holds update, not patch, on platform/*.
KV v2 changes only the fields a write names (checked on the pinned OpenBao, 2026-10-31); the other settings
are compared before and after anyway."""
before = {k: key_metadata().get(k) for k in KEPT_METADATA}
bao(['write', CAS_KEY, '-'], payload={'cas_required': True})
after = key_metadata()
if {k: after.get(k) for k in KEPT_METADATA} != before: raise Refused('metadata_changed_beyond_cas_required')
def write_role_absent():
result = bao(['read', '-format=json', 'auth/kubernetes/role/' + LANES['write']['role']], allow_failure=True)
return result.returncode != 0 and b'No value found' in result.stdout + result.stderr
def contracts(digests):
return {name: contract(name, digests[name]) for name in LANES}
def setup(digests, receipt):
receipt['phase'] = 'contract'; ccrs = contracts(digests)
receipt['phase'] = 'identity'; attended_platform_admin()
receipt['phase'] = 'coding_agent_fence'
if not fence_present(): raise Refused('agent_fence_missing') # established by CCR-2026-0053 and -0057
receipt['phase'] = 'policies'
for lane in LANES.values():
ensure('sys/policies/acl/' + lane['policy'], {'policy': lane['policy_text']}, policy_check(lane))
receipt['phase'] = 'cas_required'
if not cas_required():
set_cas_required()
if not cas_required(): raise Refused('cas_required_not_set')
receipt['phase'] = 'journey_role'
journey = LANES['journey']
ensure('auth/kubernetes/role/' + journey['role'], role_payload(journey), lambda actual: check_role(actual, journey))
receipt['phase'] = 'write_role_absent'
if not write_role_absent(): raise Refused('write_role_exists_outside_a_window')
receipt['phase'] = 'fence_effective'
for lane in LANES.values():
child = data(bao(['token', 'create', '-format=json', '-policy=' + lane['policy'],
'-policy=agent-high-risk-boundary', '-no-default-policy', '-ttl=60s']))['auth']['client_token']
try:
caps = capabilities(child, [lane['data_path'], lane['metadata_path']])
if any(v != ['deny'] for v in caps.values()): raise Refused(lane['policy'] + ':fence_ineffective')
finally:
revoke(child)
receipt.update(phase='done', status='lanes_prepared', policies_applied=True, cas_required=True,
journey_role_created=True, write_role_absent=True, coding_agents_denied=True,
source_ccrs={n: c['id'] for n, c in ccrs.items()}, ccr_sha256=digests)
def kube(kubeconfig, args):
return subprocess.run(['kubectl', '--kubeconfig', kubeconfig, *args], check=True, capture_output=True).stdout
def verify(digests, kubeconfig, receipt):
receipt['phase'] = 'contract'; ccrs = contracts(digests)
receipt['phase'] = 'identity'; attended_platform_admin()
receipt['phase'] = 'read_back'
for lane in LANES.values():
policy_check(lane)(data(bao(['read', '-format=json', 'sys/policies/acl/' + lane['policy']]))['data'])
journey, write = LANES['journey'], LANES['write']
check_role(data(bao(['read', '-format=json', 'auth/kubernetes/role/' + journey['role']]))['data'], journey)
if not cas_required(): raise Refused('cas_required_not_set')
if not write_role_absent(): raise Refused('write_role_exists_outside_a_window')
if not fence_present(): raise Refused('agent_fence_missing')
def login(sa, audience, role, negative=False):
jwt = kube(kubeconfig, ['-n', NS, 'create', 'token', sa, '--audience=' + audience, '--duration=10m']).decode().strip()
result = bao(['write', '-format=json', 'auth/kubernetes/login', '-'],
payload={'role': role, 'jwt': jwt}, allow_failure=negative)
if negative:
if result.returncode == 0:
revoke(data(result)['auth']['client_token']); raise Refused('negative_login_accepted')
if b'403' not in result.stderr and b'400' not in result.stderr: raise Refused('negative_login_unclear')
return None
return data(result)['auth']
receipt['phase'] = 'journey_login'
auth = login(SA, 'openbao', journey['role']); token = auth['client_token']
try:
if (auth['token_policies'] != [journey['policy']] or auth.get('identity_policies')
or not 0 < auth['lease_duration'] <= 900):
raise Refused('journey_policy_or_ttl')
receipt['phase'] = 'journey_capabilities'
others = [BINDS + n for n in ('svc-authelia', 'svc-keycape', 'svc-privacyidea', 'svc-provisioner')]
paths = [journey['data_path'], journey['metadata_path'], write['metadata_path'], *others, 'auth/token/create']
caps = capabilities(token, paths)
if caps[paths[0]] != ['read'] or any(caps[p] != ['deny'] for p in paths[1:]):
raise Refused('journey_capabilities')
receipt['phase'] = 'journey_read'
native = data(bao(['read', '-format=json', journey['data_path']], token=token))['data']
if sorted(native['data']) != sorted(journey['fields']): raise Refused('journey_fields')
receipt['journey_kv_version'] = native['metadata']['version']
finally:
revoke(token)
receipt['phase'] = 'negative_logins'
login('default', 'openbao', journey['role'], True)
login(SA, 'wrong-audience', journey['role'], True)
login(SA, 'openbao', write['role'], True)
receipt.update(phase='done', status='lanes_verified', exact_policies_and_role=True, cas_required=True,
journey_reads_only_its_path=True, journey_lease_at_most_15m=True, write_role_absent=True,
wrong_service_account_denied=True, wrong_audience_denied=True, write_login_denied=True,
coding_agents_denied=True, temporary_token_revoked=True,
source_ccrs={n: c['id'] for n, c in ccrs.items()}, ccr_sha256=digests)
BEA = REPO / 'tools/bounded-execution-admission/validate.py'
WRITE_SCOPE = 'openbao/' + LANES['write']['kv']
def admitted_window(admission_path, now=None):
"""The admission that opens the window: valid, for this executor, naming the write lane, window open now."""
bea = load('bea_validate_lanes', BEA)
doc = bea.load(Path(admission_path))
now = now or __import__('datetime').datetime.now(__import__('datetime').UTC)
if not bea.evaluate(doc, json.loads(bea.SCHEMA.read_text()), as_of=now)['valid']: raise Refused('admission_invalid')
spec = doc['spec']
if spec['executor']['identity'] != 'service:' + SA: raise Refused('admission_other_executor')
if not any(c['mode'] == 'OPERATE' and c['scope'] == WRITE_SCOPE for c in spec['scope_ceiling']):
raise Refused('admission_does_not_name_write_lane')
start, end = (bea.parse_time(spec['window'][k]) for k in ('not_before', 'not_after'))
if not start <= now < end: raise Refused('admission_window_not_open')
return {'admission': spec['id'], 'admission_digest': bea.admission_digest(doc),
'window': dict(spec['window'])}
def window_open(digests, admission_path, receipt, now=None):
"""Create the write role for one admitted window (CCR-2026-0058 lifecycle.role_window). Refuses if it exists."""
receipt['phase'] = 'contract'; ccrs = contracts(digests)
receipt['phase'] = 'admission'; receipt.update(admitted_window(admission_path, now))
receipt['phase'] = 'identity'; attended_platform_admin()
receipt['phase'] = 'preconditions'
write = LANES['write']
policy_check(write)(data(bao(['read', '-format=json', 'sys/policies/acl/' + write['policy']]))['data'])
if not cas_required(): raise Refused('cas_required_not_set')
if not fence_present(): raise Refused('agent_fence_missing')
if not write_role_absent(): raise Refused('write_role_already_open')
receipt['phase'] = 'create_role'
ensure('auth/kubernetes/role/' + write['role'], role_payload(write), lambda actual: check_role(actual, write))
receipt.update(phase='done', status='window_opened', write_role_created=True, exact_role=True,
close_by=receipt['window']['not_after'], source_ccrs={n: c['id'] for n, c in ccrs.items()},
ccr_sha256=digests)
def window_close(admission_path, receipt):
"""Delete the write role after the run's receipt or at window end (whichever comes first) and prove it is gone.
Needs no open window: closing late must always be possible."""
bea = load('bea_validate_lanes', BEA)
spec = bea.load(Path(admission_path))['spec']
receipt.update(admission=spec['id'])
receipt['phase'] = 'identity'; attended_platform_admin()
receipt['phase'] = 'delete_role'
was_open = not write_role_absent()
if was_open:
bao(['delete', 'auth/kubernetes/role/' + LANES['write']['role']])
if not write_role_absent(): raise Refused('write_role_still_present')
receipt.update(phase='done', status='window_closed', write_role_was_present=was_open, write_role_absent=True)
def main():
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument('action', choices=['setup', 'verify', 'window-open', 'window-close'])
p.add_argument('--write-ccr-sha256', help='sha256 of the CCR-2026-0058 file (all but window-close)')
p.add_argument('--journey-ccr-sha256', help='sha256 of the CCR-2026-0059 file (all but window-close)')
p.add_argument('--kubeconfig', help='admin kubeconfig; required for verify')
p.add_argument('--admission', help='the admitted BEA file; required for window-open and window-close')
p.add_argument('--receipt', required=True)
a = p.parse_args()
if a.action == 'verify' and not a.kubeconfig: p.error('--kubeconfig is required for verify')
if a.action.startswith('window-') and not a.admission: p.error('--admission is required for ' + a.action)
if a.action != 'window-close' and not (a.write_ccr_sha256 and a.journey_ccr_sha256):
p.error('--write-ccr-sha256 and --journey-ccr-sha256 are required for ' + a.action)
digests = {'write': a.write_ccr_sha256, 'journey': a.journey_ccr_sha256}
fd = os.open(a.receipt, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
r = {'status': 'failed', 'action': a.action, 'values_recorded': False,
'source_sha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest()}
try:
if a.action == 'setup': setup(digests, r)
elif a.action == 'verify': verify(digests, a.kubeconfig, r)
elif a.action == 'window-open': window_open(digests, a.admission, r)
else: window_close(a.admission, r)
except Exception as error:
r.update(status='failed', reason=str(error) if isinstance(error, Refused) else type(error).__name__)
finally:
with os.fdopen(fd, 'w') as out: json.dump(r, out, indent=2, sort_keys=True); out.write('\n')
print(json.dumps({k: r[k] for k in ('status', 'phase', 'reason') if k in r}))
return int(r['status'] == 'failed')
if __name__ == '__main__':
raise SystemExit(main())