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
This commit is contained in:
tegwick 2026-11-04 01:49:40 +01:00
parent 9bd7bf0bdc
commit c43afd46a7
7 changed files with 368 additions and 6 deletions

View file

@ -1,8 +1,8 @@
#!/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,
never here. Also sets cas_required on the svc-authelia key.
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:
@ -11,6 +11,11 @@ setup: under both approved, sha256-bound CCRs (binding confirmed) and an attend
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.
"""
@ -229,22 +234,81 @@ def verify(digests, kubeconfig, receipt):
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'])
p.add_argument('--write-ccr-sha256', required=True, help='sha256 of the CCR-2026-0058 file')
p.add_argument('--journey-ccr-sha256', required=True, help='sha256 of the CCR-2026-0059 file')
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)
else: verify(digests, a.kubeconfig, 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: