From 9e658be8e31a3e133bf4938eb659e22078b6c5ce Mon Sep 17 00:00:00 2001 From: codex Date: Wed, 16 Sep 2026 02:12:45 +0200 Subject: [PATCH] Reconcile T03 verification and complete guarded native execution Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a09cbb-87c6-7900-a145-4ce53ba9f1a6 --- .../2026-09-15-t03-attended-delivery.json | 9 +++ ...26-09-16-t03-resume-attended-delivery.json | 10 +++ scripts/t03-attended-delivery.py | 10 ++- scripts/t03-native-execution.py | 66 +++++++++++++++---- tests/test_t03_revocation_probe.py | 38 +++++++++++ 5 files changed, 118 insertions(+), 15 deletions(-) create mode 100644 docs/evidence/2026-09-15-t03-attended-delivery.json create mode 100644 docs/evidence/2026-09-16-t03-resume-attended-delivery.json create mode 100644 tests/test_t03_revocation_probe.py diff --git a/docs/evidence/2026-09-15-t03-attended-delivery.json b/docs/evidence/2026-09-15-t03-attended-delivery.json new file mode 100644 index 0000000..0645d2f --- /dev/null +++ b/docs/evidence/2026-09-15-t03-attended-delivery.json @@ -0,0 +1,9 @@ +{ + "observed_at": "2026-09-16T00:04:53.406447+00:00", + "status": "failed", + "phase": "scoped_reader_delivered", + "credential_values_emitted": false, + "reader_effective_policy_verified": true, + "existing_kv_version": 1, + "failure_code": "attended_admin_failed" +} diff --git a/docs/evidence/2026-09-16-t03-resume-attended-delivery.json b/docs/evidence/2026-09-16-t03-resume-attended-delivery.json new file mode 100644 index 0000000..81d104a --- /dev/null +++ b/docs/evidence/2026-09-16-t03-resume-attended-delivery.json @@ -0,0 +1,10 @@ +{ + "observed_at": "2026-09-16T00:08:16.013097+00:00", + "status": "passed", + "phase": "native_actions_completed", + "credential_values_emitted": false, + "reader_effective_policy_verified": true, + "existing_kv_version": 1, + "admin_warden_exited_0": true, + "private_runtime_removed": true +} diff --git a/scripts/t03-attended-delivery.py b/scripts/t03-attended-delivery.py index 2105a61..f67fa32 100644 --- a/scripts/t03-attended-delivery.py +++ b/scripts/t03-attended-delivery.py @@ -1,5 +1,5 @@ """Scoped reader -> contained attended administration; never exports credentials.""" -import importlib.util,json,os,signal,stat,subprocess,tempfile +import importlib.util,json,os,signal,stat,subprocess,tempfile,sys from pathlib import Path from datetime import datetime,timezone from urllib.request import Request,build_opener,ProxyHandler,HTTPRedirectHandler @@ -13,9 +13,13 @@ def require(value,code): if not value:raise ValueError(code) def private(path,directory=False): st=path.lstat();require(st.st_uid==os.getuid() and stat.S_IMODE(st.st_mode)==(0o700 if directory else 0o600) and (stat.S_ISDIR(st.st_mode) if directory else stat.S_ISREG(st.st_mode)),'private_path_required') +RESUME=sys.argv[1:]==['--resume-exec'] +if sys.argv[1:] and not RESUME:raise SystemExit('unsupported mode') +if RESUME:RECEIPT=ROOT/'docs/evidence/2026-09-16-t03-resume-attended-delivery.json' + def main(receipt): require(Path.home().parent.name=='.warden-attended-login' and not os.getenv('BAO_TOKEN') and not os.getenv('VAULT_TOKEN'),'attended_reader_required') - require(not Path('/home/worsch/secrets-engine/docs/evidence/2026-09-15-t03-native-execution.json').exists(),'execution_receipt_requires_reconciliation') + require(not Path('/home/worsch/secrets-engine/docs/evidence/'+('2026-09-16-t03-resume-exec.json' if RESUME else '2026-09-15-t03-native-execution.json')).exists(),'execution_receipt_requires_reconciliation') identity=pre.bao('token','lookup','-format=json')['data'];pre.validate_identity(identity) for path,expected in pre.EXPECTED.items():require(sorted(pre.bao('token','capabilities','-format=json',path))==expected,'reader_scope_failed') runtime=Path('/run/user')/str(os.getuid());private(runtime,True) @@ -34,7 +38,7 @@ def main(receipt): del value,data,req receipt.update(phase='scoped_reader_delivered',reader_effective_policy_verified=True,existing_kv_version=1) RECEIPT.write_text(json.dumps(receipt,indent=2)+'\n') - p=subprocess.run(['python3',str(ROOT/'scripts/openbao-attended-exec.py'),'--','/home/worsch/secrets-engine/.venv/bin/python','-B',str(ROOT/'scripts/t03-native-execution.py'),'admin','--directory',str(directory),'--negative-token-file',str(helper)],capture_output=True,timeout=780) + p=subprocess.run(['python3',str(ROOT/'scripts/openbao-attended-exec.py'),'--','/home/worsch/secrets-engine/.venv/bin/python','-B',str(ROOT/'scripts/t03-native-execution.py'),'resume-exec' if RESUME else 'admin','--directory',str(directory),'--negative-token-file',str(helper)],capture_output=True,timeout=780) # Warden's output remains inside this envelope, never forwarded or stored. require(p.returncode==0,'attended_admin_failed') receipt.update(status='passed',phase='native_actions_completed',admin_warden_exited_0=True) diff --git a/scripts/t03-native-execution.py b/scripts/t03-native-execution.py index 4feac8f..6e21695 100644 --- a/scripts/t03-native-execution.py +++ b/scripts/t03-native-execution.py @@ -63,7 +63,36 @@ def health(): require(rows,'llm_connect_health_target_missing');result[kind]=rows return result -def admin(directory,negative): +def verify_session_cleanup(client): + # Keep the explicit scoped token in memory: close() clears session.client.token, + # which otherwise makes a subsequent CLI call use the attended admin helper. + from urllib.request import Request,build_opener,ProxyHandler,HTTPRedirectHandler + from urllib.error import HTTPError + class NoRedirect(HTTPRedirectHandler): + def redirect_request(self,*a,**k):return None + with client.approle_session(ROLE) as session: + probe_token=session.client.token + probes=['platform/data/workloads/secrets-engine/approval-client','platform/metadata/workloads/activity-core'] + require(all(session.client.token_capabilities(p,token=probe_token)==['deny'] for p in probes),'unrelated_path_authority') + try: + require(session.revocation_succeeded,'session_revocation_failed') + req=Request(client.addr+'/v1/auth/token/lookup-self',headers={'X-Vault-Token':probe_token}) + try: + with build_opener(ProxyHandler({}),NoRedirect()).open(req,timeout=20) as response: + raise ValueError('revoked_token_still_usable') + except HTTPError as error: + require(error.code==403,'revocation_probe_not_definitive') + finally: + del probe_token + return {'unrelated_path_denied':True,'session_revocation_verified':True,'revoked_token_lookup_status':403} + +def resume_receipt(): + prior=json.loads((ROOT/'docs/evidence/2026-09-15-t03-native-execution.json').read_text()) + require(prior.get('phase')=='verify_attempt_started' and prior.get('failure_code')=='revoked_token_still_usable','unexpected_prior_failure') + require(prior.get('actions')==[{'action':'apply','approval_id':IDS['apply'],'exit_code':0,'limits':{'token_ttl':900,'token_max_ttl':1800,'secret_id_ttl':900,'secret_id_num_uses':1,'token_num_uses':8}}],'prior_apply_not_verified') + return prior + +def admin(directory,negative,resume=False): require(not RECEIPT.exists(),'existing_execution_receipt_requires_reconciliation') receipt={'status':'failed','phase':'preflight','observed_at':datetime.now(timezone.utc).isoformat(),'actions':[]} forwards=[] @@ -93,14 +122,28 @@ def admin(directory,negative): fd=os.open(directory/'pdp-caller',os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600) with os.fdopen(fd,'w') as out:out.write(run(*KUBE,'-n','secrets-engine','create','token','secrets-engine','--audience=flex-auth','--duration=10m').strip()) # Validate real claims and exact current decisions before any consumption. - for action in IDS: + actions=('exec',) if resume else tuple(IDS) + for action in actions: auth=authorize_action(configs[action],entries[action],action,fields=() if action=='apply' else tuple(entries[action].fields),policy_targets=(ROLE,),auth_targets=(ROLE,)) require(auth is not None,'native_authorization_missing') receipt['phase']='all_claims_and_pdp_checks_passed';save(receipt) client=OpenBaoClient.resolve('http://127.0.0.1:18200') existing=client.read_policy(ROLE) - require(existing is None and not client.approle_exists(ROLE),'existing_native_objects_require_reconciliation') - for action in IDS: + if resume: + prior=resume_receipt() + plan=build_plan(entries['apply'],'prod') + require(existing is not None and existing.strip()==plan.policy_hcl.strip(),'applied_policy_drift') + role=bao('read','-format=json','auth/approle/role/'+ROLE)['data'] + require(all(role.get(k)==v for k,v in prior['actions'][0]['limits'].items()) and role.get('token_policies')==[ROLE],'applied_role_drift') + code="import json;from approval_engine.store import Engine;e=Engine('/data/approvals.sqlite');print(json.dumps({k:e.claim(v) for k,v in "+repr(IDS)+".items()}))" + claims=json.loads(run(*KUBE,'-n','approval-engine','exec','statefulset/approval-engine','--','python','-c',code)) + require(all(claims[a]['consumed'] and claims[a]['approval_id']==IDS[a] for a in ('apply','verify')) and claims['exec']['valid_now'] and not claims['exec']['consumed'],'resume_approval_state_mismatch') + receipt['prior_receipt']='2026-09-15-t03-native-execution.json' + receipt['completed_actions_not_replayed']=['apply','verify'] + receipt['supplemental_cleanup']=verify_session_cleanup(client);save(receipt) + else: + require(existing is None and not client.approle_exists(ROLE),'existing_native_objects_require_reconciliation') + for action in actions: receipt['phase']=action+'_attempt_started';save(receipt) if action=='apply':argv=['apply',LANE,'--stage','prod','--auth','env'] elif action=='verify':argv=['verify',LANE,'--auth','env','--negative-token-file',str(negative)] @@ -116,12 +159,7 @@ def admin(directory,negative): limits={'token_ttl':900,'token_max_ttl':1800,'secret_id_ttl':900,'secret_id_num_uses':1,'token_num_uses':8} require(all(role.get(k)==v for k,v in limits.items()) and role.get('token_policies')==[ROLE],'role_limits_mismatch');row['limits']=limits elif action=='verify': - with client.approle_session(ROLE) as session: - probes=['platform/data/workloads/secrets-engine/approval-client','platform/metadata/workloads/activity-core'] - require(all(session.client.token_capabilities(p,token=session.client.token)==['deny'] for p in probes),'unrelated_path_authority') - require(session.revocation_succeeded,'session_revocation_failed') - require(session.client._run(['token','lookup','-format=json']).returncode!=0,'revoked_token_still_usable') - row.update(unrelated_path_denied=True,session_revocation_verified=True) + row.update(verify_session_cleanup(client)) else: result=json.loads(output.getvalue());require(result=={'result':'authenticated','http_status':200},'key_check_result_invalid');row['key_check']=result receipt['actions'].append(row);receipt['phase']=action+'_completed';save(receipt) @@ -142,12 +180,16 @@ def admin(directory,negative): receipt['owner_forwards_closed']=True;save(receipt) if __name__=='__main__': - p=argparse.ArgumentParser();p.add_argument('mode',choices=['preflight','admin']);p.add_argument('--directory',type=Path);p.add_argument('--negative-token-file',type=Path);a=p.parse_args() + p=argparse.ArgumentParser();p.add_argument('mode',choices=['preflight','admin','resume-exec']);p.add_argument('--directory',type=Path);p.add_argument('--negative-token-file',type=Path);a=p.parse_args() try: if a.mode=='preflight': with tempfile.TemporaryDirectory() as t:prepare(Path(t)) print('Three frozen requests and pinned recipient passed; native claims checked at execution.') - else:admin(a.directory,a.negative_token_file) + else: + if a.mode=='resume-exec': + resume_receipt() + RECEIPT=ROOT/'docs/evidence/2026-09-16-t03-resume-exec.json' + admin(a.directory,a.negative_token_file,resume=a.mode=='resume-exec') except Exception: if a.mode=='preflight':raise raise SystemExit(1) from None diff --git a/tests/test_t03_revocation_probe.py b/tests/test_t03_revocation_probe.py new file mode 100644 index 0000000..589fdb1 --- /dev/null +++ b/tests/test_t03_revocation_probe.py @@ -0,0 +1,38 @@ +import importlib.util,unittest,json +from pathlib import Path +from unittest.mock import patch +from contextlib import contextmanager +from types import SimpleNamespace +from urllib.error import HTTPError,URLError +spec=importlib.util.spec_from_file_location('native',Path(__file__).resolve().parents[1]/'scripts/t03-native-execution.py');m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m) +class TestProbe(unittest.TestCase): + def check(self,status): + scoped=SimpleNamespace(token='synthetic-revoked-token',token_capabilities=lambda *a,**k:['deny']) + session=SimpleNamespace(client=scoped,revocation_succeeded=False) + @contextmanager + def session_context(role): + yield session + session.revocation_succeeded=True;scoped.token='' + client=SimpleNamespace(addr='http://127.0.0.1:18200',approle_session=session_context) + class Opener: + def open(self,req,timeout): + self_test.assertEqual(req.get_header('X-vault-token'),'synthetic-revoked-token') + self_test.assertEqual(scoped.token,'') + if status:raise HTTPError(req.full_url,status,'synthetic',{},None) + return unittest.mock.MagicMock() + self_test=self + with patch('urllib.request.build_opener',return_value=Opener()):return m.verify_session_cleanup(client) + def test_revoked_explicit_token_refused(self):self.assertEqual(self.check(403)['revoked_token_lookup_status'],403) + def test_live_token_refused(self): + with self.assertRaisesRegex(ValueError,'revoked_token_still_usable'):self.check(0) + def test_server_failure_is_not_revocation_proof(self): + with self.assertRaisesRegex(ValueError,'revocation_probe_not_definitive'):self.check(500) + def test_reconciled_prior_phase(self): + prior={'phase':'verify_attempt_started','failure_code':'revoked_token_still_usable','actions':[{'action':'apply','approval_id':m.IDS['apply'],'exit_code':0,'limits':{'token_ttl':900,'token_max_ttl':1800,'secret_id_ttl':900,'secret_id_num_uses':1,'token_num_uses':8}}]} + with patch.object(Path,'read_text',return_value=json.dumps(prior)): + self.assertEqual(m.resume_receipt(),prior) + prior['actions'][0]['approval_id']='other' + with patch.object(Path,'read_text',return_value=json.dumps(prior)): + with self.assertRaisesRegex(ValueError,'prior_apply_not_verified'):m.resume_receipt() + +if __name__=='__main__':unittest.main()