Prepare scoped factory audit custody and enforce receiver compatibility

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
codex 2026-09-11 02:01:57 +02:00
parent d3a502b45c
commit bfe65a5b05
11 changed files with 1053 additions and 1 deletions

View file

@ -0,0 +1,268 @@
#!/usr/bin/env python3
"""Attended first-provision custody for the two factory audit senders.
Plan and receiver-check never read credential values. Seed needs the reviewed
CCRs and a compatible receiver; it does not deploy or reload any workload.
"""
from __future__ import annotations
import argparse
import copy
import hashlib
import importlib.util
import json
import os
from pathlib import Path
import re
import secrets
from datetime import datetime, timezone
from state_hub_preflight_lane import ROOT, LaneError, assert_cluster, bao, command, data
from keycape_approval_custody import read_optional, role_matches
REGISTRY = 'platform/data/workloads/audit-core/senders'
BOUNDARY = 'sys/policies/acl/agent-high-risk-boundary'
LANES = (('CCR-2026-0021', 'approval-engine', 'audit-token'),
('CCR-2026-0022', 'informed-decision', 'token'))
CONFIRM = 'PROVISION CCR-2026-0021 CCR-2026-0022'
# This probes synthetic identities only; no environment-backed registry is read.
RECEIVER_PROBE = '''import json
from audit_core.senders import SenderRegistry
entry={"name":"compatibility-fixture","tokens":["fixture-only"],"sources":["compatibility-fixture"],"tenants":["tenant:platform"],"may_write":True,"may_read":False,"evidence_kind":"load-bearing","secret_policy":"redact"}
r=SenderRegistry.from_env({"AUDIT_CORE_SENDERS":json.dumps([entry])})
s=r.authenticate("Bearer fixture-only")
print(json.dumps({"load_bearing":getattr(s,"evidence_kind",None)=="load-bearing","redact":s.secret_policy=="redact","write_only":s.may_write and not s.may_read,"source_exact":s.permits_source("compatibility-fixture") and not s.permits_source("sibling"),"tenant_exact":s.permits_tenant("tenant:platform") and not s.permits_tenant("tenant:sibling")}))
'''
def require(condition, code):
if not condition:
raise LaneError(code)
def credential_module():
spec = importlib.util.spec_from_file_location('factory_ccr', ROOT / 'scripts/credential-change.py')
module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module)
return module
def contracts(*, approved=False):
module = credential_module()
result = []
for ref, name, secret_key in LANES:
path = module.resolve_ccr(ref)
ccr, errors, _ = module.validate_ccr(path)
require(not errors, 'invalid_credential_request')
if approved:
require(ccr['status'] in {'approved', 'applied', 'verified', 'active'}, 'approved_ccrs_required')
for owner in ('platform-operator', 'audit-core-owner', name + '-owner'):
require(any(x.get('decision') == 'approved' and owner in x.get('reviewer', '')
for x in ccr['review']['comments']), 'named_owner_reviews_required')
suffix = name + '-audit'; policy = 'workload-kv-read-' + suffix
expected_role = {'bound_service_account_names':['external-secrets'],
'bound_service_account_namespaces':['external-secrets'],
'policies':policy,'ttl':'15m'}
expected_path = f'platform/workloads/{name}/audit-sender'
require(ccr['openbao']['kv_path'] == expected_path
and ccr['openbao']['fields'] == ['AUDIT_TOKEN', 'CUSTODY_REQUEST']
and ccr['openbao']['policy_name'] == policy
and ccr['openbao']['metadata_read'] is False
and ccr['openbao']['auth']['role'] == 'external-secrets-' + suffix
and ccr['openbao']['auth']['mount'] == 'kubernetes'
and ccr['openbao']['auth']['bound_claims_confirmed'] is True
and module.auth_payload(ccr) == expected_role, 'exact_contract_required')
hcl = module.generated_policy_hcl(ccr)
require((ROOT / ccr['openbao']['policy_file']).read_text() == hcl, 'policy_source_drift')
result.append({'ccr':ref,'name':name,'kv':expected_path.replace('platform/','platform/data/',1),
'metadata':expected_path.replace('platform/','platform/metadata/',1),
'policy':policy,'hcl':hcl,'role':'external-secrets-'+suffix,
'role_payload':expected_role,'store':'openbao-'+suffix,'secret':suffix,
'secret_key':secret_key,'source_sha256':hashlib.sha256(path.read_bytes()).hexdigest()})
return result
def receiver_check(kube, expected_image):
require(re.fullmatch(r'forgejo\.coulomb\.social/coulomb/audit-core@sha256:[0-9a-f]{64}', expected_image), 'receiver_digest_required')
assert_cluster(kube)
dep = data(command(kube + ['-n','audit-core','get','deployment','audit-core','-o','json']))
require(dep['spec']['replicas'] == 1 and dep.get('status',{}).get('readyReplicas') == 1
and dep['status'].get('observedGeneration',0) >= dep['metadata']['generation'], 'receiver_not_ready')
containers = dep['spec']['template']['spec']['containers']
require(len(containers) == 1 and containers[0]['image'] == expected_image, 'receiver_image_mismatch')
pods = data(command(kube + ['-n','audit-core','get','pods','-l','app.kubernetes.io/name=audit-core','-o','json']))['items']
require(len(pods) == 1 and not pods[0]['metadata'].get('deletionTimestamp'), 'receiver_rollout_in_progress')
pod = pods[0]
require(pod['spec']['containers'][0]['image'] == expected_image
and all(x.get('ready') for x in pod.get('status',{}).get('containerStatuses',[]))
and pod.get('status',{}).get('containerStatuses'), 'receiver_pod_not_current')
proof = data(command(kube + ['-n','audit-core','exec',pod['metadata']['name'],'-c',containers[0]['name'],
'--','python','-c',RECEIVER_PROBE]))
require(set(proof) == {'load_bearing','redact','write_only','source_exact','tenant_exact'}
and all(v is True for v in proof.values()), 'receiver_lacks_sender_contract')
return {'image':expected_image,'deployment_uid':dep['metadata']['uid'],
'deployment_resource_version':dep['metadata']['resourceVersion'],'pod_uid':pod['metadata']['uid'],
'capabilities':proof,'synthetic_probe_only':True,'credential_reads':0}
def snapshot():
raw = data(bao(['read','-format=json',REGISTRY]))['data']
require(type(raw['metadata']['version']) is int and raw['metadata']['version'] > 0, 'registry_version_missing')
body = raw['data']; require(isinstance(body,dict) and isinstance(body.get('senders.json'),str), 'registry_shape_invalid')
try:
rows = json.loads(body['senders.json'])
except (ValueError, TypeError):
raise LaneError('registry_shape_invalid') from None
require(isinstance(rows,list) and bool(rows), 'registry_must_preserve_existing_senders')
names = set(); tokens = set()
for row in rows:
require(isinstance(row,dict) and isinstance(row.get('name'),str) and row['name']
and row['name'] not in names, 'registry_identity_invalid_or_duplicate')
names.add(row['name'])
values = row.get('tokens') or ([row['token']] if row.get('token') else [])
require(isinstance(values,list) and bool(values), 'registry_tokens_invalid')
for value in values:
require(isinstance(value,str) and bool(value) and value not in tokens, 'registry_token_collision')
tokens.add(value)
return raw['metadata']['version'], body, rows, tokens
def desired(lane, token):
return {'name':lane['name'],'tokens':[token],'sources':[lane['name']],
'tenants':['tenant:platform'],'may_write':True,'may_read':False,
'evidence_kind':'load-bearing','secret_policy':'redact'}
def seed(lanes, receipt, save, *, resume=False):
version, body, rows, used_tokens = snapshot()
originals = copy.deepcopy(rows)
found = {r['name']:r for r in rows}
values = {}; pending = []
# Validate every existing custody object before any write. Only this exact
# request's version-1 values can be reused after interruption.
for lane in lanes:
old = read_optional(lane['metadata'])
if old is None:
require(lane['name'] not in found, 'registry_sender_without_custody')
pending.append(lane)
else:
require(resume and old['current_version'] == 1
and not old['versions']['1'].get('destroyed')
and not old['versions']['1'].get('deletion_time'), 'existing_custody_requires_reviewed_resume')
existing = data(bao(['read','-format=json',lane['kv']]))['data']
require(existing['metadata']['version'] == 1
and set(existing['data']) == {'AUDIT_TOKEN','CUSTODY_REQUEST'}
and existing['data']['CUSTODY_REQUEST'] == lane['ccr'], 'custody_provenance_mismatch')
token = existing['data']['AUDIT_TOKEN']
require(isinstance(token,str) and re.fullmatch(r'[A-Za-z0-9_-]{64}',token), 'custody_token_shape')
require(token not in values.values(), 'sender_token_collision')
if lane['name'] in found:
require(found[lane['name']] == desired(lane,token), 'registered_sender_drift')
else:
require(token not in used_tokens, 'sender_token_collision')
values[lane['name']] = token
receipt.update(registry_version_before=version,phase='custody_seed')
save()
for lane in pending:
token = secrets.token_urlsafe(48)
require(token not in used_tokens and token not in values.values(), 'sender_token_collision')
result = data(bao(['write','-format=json',lane['kv'],'-'],payload={
'options':{'cas':0},'data':{'AUDIT_TOKEN':token,'CUSTODY_REQUEST':lane['ccr']}}))
require(result['data']['version'] == 1,'initial_custody_version_mismatch')
values[lane['name']] = token
receipt.setdefault('seeded',[]).append({'ccr':lane['ccr'],'version':1,'request_id':result.get('request_id')})
save()
updated = list(rows)
for lane in lanes:
if lane['name'] not in found:
updated.append(desired(lane,values[lane['name']]))
if updated != rows:
receipt['phase']='registry_cas'; save()
payload = dict(body, **{'senders.json':json.dumps(updated,separators=(',',':'))})
result = data(bao(['write','-format=json',REGISTRY,'-'],payload={'options':{'cas':version},'data':payload}))
receipt['registry_write_request_id']=result.get('request_id')
# Readback also catches a concurrent removal rather than claiming a success.
after_version, after_body, after_rows, _ = snapshot()
require(all(x in after_rows for x in originals)
and all(desired(lane,values[lane['name']]) in after_rows for lane in lanes)
and all(after_body.get(k)==v for k,v in body.items() if k!='senders.json'), 'registry_readback_mismatch')
receipt.update(registry_version_after=after_version,unchanged_other_senders=True,
unchanged_other_registry_fields=True,credentials_reused=resume,
status='custody_seeded_pending_delivery_and_receiver_reload')
save()
def prepare_metadata(lanes):
# Avoid overwriting an unrelated policy or role. Do all drift checks first.
for lane in lanes:
policy=read_optional('sys/policies/acl/'+lane['policy'])
role=read_optional('auth/kubernetes/role/'+lane['role'])
require(policy is None or policy['policy']==lane['hcl'],'existing_policy_drift')
require(role is None or role_matches(role,lane),'existing_role_drift')
boundary=read_optional(BOUNDARY); require(boundary is not None,'coding_agent_boundary_required')
current=boundary['policy']; additions=''
for lane in lanes:
for path in (lane['kv'],lane['metadata']):
if '"'+path+'"' in current:
require(re.search(r'path\s+"'+re.escape(path)+r'"\s*\{\s*capabilities\s*=\s*\["deny"\]\s*\}',current),'boundary_path_drift')
else:
additions+=f'path "{path}" {{ capabilities = ["deny"] }}\n'
if additions:
require(read_optional(BOUNDARY)['policy']==current,'boundary_revision_changed')
bao(['write',BOUNDARY,'-'],payload={'policy':current+'\n'+additions})
require(read_optional(BOUNDARY)['policy']==current+'\n'+additions,'boundary_readback_failed')
for lane in lanes:
bao(['write','sys/policies/acl/'+lane['policy'],'-'],payload={'policy':lane['hcl']})
bao(['write','auth/kubernetes/role/'+lane['role'],'-'],payload=lane['role_payload'])
require(read_optional('sys/policies/acl/'+lane['policy'])['policy']==lane['hcl']
and role_matches(read_optional('auth/kubernetes/role/'+lane['role']),lane),'metadata_readback_failed')
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('action',choices=['plan','receiver-check','seed'])
parser.add_argument('--kubeconfig'); parser.add_argument('--server')
parser.add_argument('--expected-receiver-image'); parser.add_argument('--confirm')
parser.add_argument('--receipt',type=Path); parser.add_argument('--resume',action='store_true')
args=parser.parse_args()
receipt={'schema':'platform.factory-audit-custody.v1','status':'refused','credential_values_emitted':False,
'started_at':datetime.now(timezone.utc).isoformat()}
fd=None
try:
lanes=contracts(approved=args.action=='seed')
receipt['lanes']=[{k:v for k,v in x.items() if k in ('ccr','name','kv','store','secret','secret_key','source_sha256')} for x in lanes]
if args.action=='plan':
receipt['status']='proposed'; print(json.dumps(receipt,indent=2)); return 0
require(args.kubeconfig and args.expected_receiver_image and args.receipt,'receiver_inputs_required')
fd=os.open(args.receipt,os.O_RDWR|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600)
def save():
os.lseek(fd,0,os.SEEK_SET);os.ftruncate(fd,0)
os.write(fd,(json.dumps(receipt,indent=2)+'\n').encode());os.fsync(fd)
kube=['kubectl','--kubeconfig',args.kubeconfig,'--request-timeout=20s']
if args.server:kube+=['--server',args.server]
receipt['receiver']=receiver_check(kube,args.expected_receiver_image)
if args.action=='receiver-check':
receipt['status']='receiver_contract_supported';save();return 0
require(args.confirm==CONFIRM,'exact_confirmation_required')
require(Path.home().parent.name=='.warden-attended-login'
and not os.environ.get('BAO_TOKEN') and not os.environ.get('VAULT_TOKEN'),'attended_warden_envelope_required')
identity=data(bao(['token','lookup','-format=json']))['data']
require('platform-admin' in identity['policies'] and 'root' not in identity['policies'],'attended_platform_admin_required')
for lane in lanes:
command(kube+['get','namespace',lane['name'],'-o','name'])
# Refuse malformed registry before changing policy/auth metadata.
snapshot()
receipt['phase']='metadata';save();prepare_metadata(lanes)
seed(lanes,receipt,save,resume=args.resume)
return 0
except Exception as exc:
receipt['status']='refused'
receipt['error']=str(exc) if isinstance(exc,LaneError) else 'contained_operation_failed'
if fd is not None:
save()
print(json.dumps({'status':'refused','error':receipt['error']}))
return 1
finally:
if fd is not None:os.close(fd)
if __name__=='__main__':raise SystemExit(main())