"""Offline adversarial evidence/transport/resume tests; no network or Laravel env.""" import copy import hashlib import importlib.util import json from pathlib import Path import sys import tempfile import threading import time import unittest from unittest.mock import patch SCRIPTS=Path(__file__).resolve().parents[2]/'scripts/node-onboarding' sys.path.insert(0,str(SCRIPTS)) from acceptance import audit,digest,snapshot,service_change,provider_state,verification_scope from ssh_pool import Pool,private_json,credentials import batch class EvidenceTests(unittest.TestCase): def fixture(self,count): nodes=[dict(id=i,type='anytls',tag='node-'+str(i),relay='relay.example.invalid',relay_port=47000+i,sni=f'n{i}.example.invalid',expected_exit=f'192.0.2.{i}',country='JP') for i in range(1,count+1)] report={'state':'live_verified','verified_at':1000,'results':[dict(tag=n['tag'],relay=n['relay'],relay_port=n['relay_port'],sni=n['sni'],exit_ip=n['expected_exit'],country=n['country'],live=True,strict_tls=True) for n in nodes]} qas={'captured_at':1000,'plan_sha256':'plan','users':{'new':dict(id=9,http_status=403,banned=True,excluded=True)}} return nodes,report,qas def check(self,nodes,report,qas,after=None,**bindings): raw=json.dumps(report).encode();qraw=json.dumps(qas).encode() binding=dict(plan_sha256='plan',snapshot_sha256=digest(snapshot(nodes)),report_sha256=hashlib.sha256(raw).hexdigest(),qa_rejections_sha256=hashlib.sha256(qraw).hexdigest(),verified_at=1000) binding.update(bindings) return audit(nodes,nodes if after is None else after,raw,qraw,binding,'plan',{'new':9},now=1100) def test_arbitrary_node_counts_and_reordering(self): for count in [1,2,7,23]: nodes,report,qas=self.fixture(count) self.assertEqual(self.check(nodes,report,qas,after=list(reversed(nodes)))['nodes'],count) def test_snapshot_can_be_revalidated_by_formal_audit(self): nodes,report,qas=self.fixture(2) normalized=snapshot(nodes) self.assertEqual(snapshot(normalized),normalized) self.assertTrue(all(n['type']=='anytls' for n in normalized)) self.assertEqual(self.check(normalized,report,qas)['nodes'],2) def test_topology_change_including_old_node_is_blocked(self): nodes,r,q=self.fixture(7) for after in [nodes[:-1],nodes+self.fixture(8)[0][-1:],list(reversed(nodes[:-1]))]: with self.assertRaises(ValueError):self.check(nodes,r,q,after=after) altered=copy.deepcopy(nodes);altered[0]['expected_exit']='198.51.100.9' with self.assertRaises(ValueError):self.check(nodes,r,q,after=altered) def test_missing_extra_duplicate_or_insecure_results_fail(self): nodes,r,q=self.fixture(7) for results in [r['results'][:-1],r['results']+[r['results'][0]],r['results'][1:]+[r['results'][1]]]: with self.assertRaises(ValueError):self.check(nodes,dict(r,results=results),q) for field,value in [('exit_ip','198.51.100.1'),('country','US'),('strict_tls',False),('live',False),('sni','wrong.example.invalid')]: wrong=copy.deepcopy(r);wrong['results'][0][field]=value with self.assertRaises(ValueError):self.check(nodes,wrong,q) def test_qa_identity_status_revocation_and_hashes_are_bound(self): nodes,r,q=self.fixture(2) for field,value in [('id',10),('http_status',200),('banned',False),('excluded',False)]: wrong=copy.deepcopy(q);wrong['users']['new'][field]=value with self.assertRaises(ValueError):self.check(nodes,r,wrong) for field in ['snapshot_sha256','plan_sha256','report_sha256','qa_rejections_sha256']: with self.assertRaises(ValueError):self.check(nodes,r,q,**{field:'wrong'}) for stamp in [-1,1200,None,'1000']: with self.assertRaises(ValueError):self.check(nodes,dict(r,verified_at=stamp),q) with self.assertRaises(ValueError):self.check(nodes,r,dict(q,captured_at=stamp)) def test_provider_observations_are_exact_and_not_language_or_substring_dependent(self): node=dict(key='new',tls_domain='a.example.invalid',relay_host='relay.example.invalid',relay_port=47001,node_address='192.0.2.1',listen_port=24443) observed={'captured_at':1000,'dns':[{'type':'A','name':node['tls_domain'],'address':node['node_address'],'proxy':False}],'relays':[{'name':'dpl-new-anytls','host':node['relay_host'],'port':47001,'target':node['node_address'],'listen_port':24443,'proxy_protocol_in':False,'proxy_protocol_out':False}]} self.assertTrue(provider_state(observed,node,1100)['dns']) for section,field,value in [('dns','proxy',True),('dns','address','192.0.2.11'),('relays','proxy_protocol_in',True),('relays','target','192.0.2.11')]: wrong=copy.deepcopy(observed);wrong[section][0][field]=value with self.assertRaises(ValueError):provider_state(wrong,node,1100) self.assertEqual(provider_state(dict(observed,dns=[],relays=[]),node,1100),{'dns':[],'relays':[]}) with self.assertRaises(ValueError):provider_state(observed,node,2000) def test_registry_and_service_changes_do_not_use_pid_alone(self): for values in [[],[dict(self.fixture(1)[0][0],type='vless')],self.fixture(1)[0]*2]: with self.assertRaises(ValueError):snapshot(values) before=dict(unit_sha256='a',binary_sha256='b',config_sha256='c',listeners=['*:443'],active=True,pid=1,started='yesterday',restarts=0) self.assertEqual(service_change(before,before),'ready') self.assertEqual(service_change(before,dict(before,pid=2)),'investigate_restart') self.assertEqual(service_change(before,dict(before,config_sha256='changed')),'conflict') self.assertEqual(service_change(before,dict(before,active=False)),'conflict') self.assertEqual(service_change(before,dict(before,binary_sha256=None)),'unknown') class ImpactAcceptanceTests(unittest.TestCase): fixture=EvidenceTests.fixture check=EvidenceTests.check def scoped(self, nodes, report, qas, scope, after=None, binding_scope=None): raw=json.dumps(report).encode();qa_raw=json.dumps(qas).encode() binding=dict(plan_sha256='plan',snapshot_sha256=digest(snapshot(nodes)),report_sha256=hashlib.sha256(raw).hexdigest(),qa_rejections_sha256=hashlib.sha256(qa_raw).hexdigest(),verified_at=1000,scope_sha256=digest(scope if binding_scope is None else binding_scope)) return audit(nodes,nodes if after is None else after,raw,qa_raw,binding,'plan',{'new':9},now=1100,scope=scope) def test_unknown_and_shared_impact_require_full_while_append_uses_tls(self): nodes,report,qas=self.fixture(7) review={'shared_changes':[],'affected_snis':[],'review':'task/impact-review.json'} self.assertEqual(set(verification_scope(nodes).values()),{'full'}) self.assertEqual(set(verification_scope(nodes,review).values()),{'tls'}) self.assertEqual(set(verification_scope(nodes,dict(review,shared_changes=['subscription renderer'])).values()),{'full'}) scope=verification_scope(nodes,dict(review,affected_snis=[nodes[0]['sni']])) self.assertEqual(scope['node-1'],'full');self.assertEqual(scope['node-7'],'tls') for bad in [{},dict(review,review=''),dict(review,affected_snis=['unknown.invalid']),dict(review,shared_changes='none')]: with self.assertRaises(ValueError):verification_scope(nodes,bad) def test_light_results_cannot_claim_exit_and_do_not_skip_set_or_qa_checks(self): nodes,report,qas=self.fixture(7) scope={n['tag']:'tls' for n in nodes} for result in report['results']: result['verification']='tls';result.pop('exit_ip');result.pop('country') self.assertEqual(self.scoped(nodes,report,qas,scope)['nodes'],7) with self.assertRaises(ValueError):self.scoped(nodes,report,qas,scope,after=nodes[:-1]) with self.assertRaises(ValueError):self.scoped(nodes,dict(report,results=report['results'][:-1]),qas,scope) with self.assertRaises(ValueError):self.scoped(nodes,report,qas,scope,binding_scope={'node-1':'tls'}) wrong=copy.deepcopy(qas);wrong['users']['new']['banned']=False with self.assertRaises(ValueError):self.scoped(nodes,report,wrong,scope) full=dict(scope,**{'node-1':'full'}) with self.assertRaises(ValueError):self.scoped(nodes,report,qas,full) with self.assertRaises(ValueError):self.check(nodes,report,qas) # Old/default audits remain full. def test_mixed_scope_requires_full_exit_validation_only_where_affected(self): nodes,report,qas=self.fixture(2);scope={'node-1':'full','node-2':'tls'} report['results'][1].update(verification='tls');report['results'][1].pop('exit_ip');report['results'][1].pop('country') self.assertEqual(self.scoped(nodes,report,qas,scope)['nodes'],2) report['results'][0]['exit_ip']='198.51.100.10' with self.assertRaises(ValueError):self.scoped(nodes,report,qas,scope) class ProbeDispatchTests(unittest.TestCase): def verifier(self): spec=importlib.util.spec_from_file_location('scope_verifier',SCRIPTS/'verify-node.py') module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module);return module def test_mixed_probes_dispatch_without_starting_client_for_tls_nodes(self): verifier=self.verifier();specs=[{'tag':'old','verification':'tls'},{'tag':'affected'}] with patch.object(verifier,'run_tls',return_value={'verification':'tls'}) as light,patch.object(verifier,'run_node',return_value={'live':True}) as full: self.assertEqual(len(verifier.run_specs({},specs,'binary',19080,'private')),2) light.assert_called_once_with(specs[0]);full.assert_called_once_with({},specs[1],'binary',19080,'private') def test_tls_probe_checks_hostname_and_emits_no_exit_claim(self): from unittest.mock import MagicMock verifier=self.verifier();context=MagicMock();stream=MagicMock() context.wrap_socket.return_value.__enter__.return_value.version.return_value='TLSv1.3' with patch.object(verifier.ssl,'create_default_context',return_value=context),patch.object(verifier.socket,'create_connection',return_value=stream): report=verifier.run_tls({'tag':'old','host':'relay.invalid','port':1234,'sni':'node.invalid'}) self.assertEqual(context.wrap_socket.call_args.kwargs['server_hostname'],'node.invalid') self.assertEqual(report['verification'],'tls');self.assertNotIn('exit_ip',report);self.assertNotIn('country',report) with patch.object(verifier.ssl,'create_default_context',return_value=context),patch.object(verifier.socket,'create_connection',side_effect=OSError('offline')): with self.assertRaises(OSError):verifier.run_tls({'host':'relay.invalid','port':1234,'sni':'node.invalid'}) class TransportTests(unittest.TestCase): def test_reuse_and_every_nonwhite_host_has_jump(self): calls=[] class Client: active=True def get_transport(self):return self def is_active(self):return self.active def close(self):self.active=False def factory(rid,spec,jump):calls.append((rid,jump));return Client() config={'hosts':{'white':{'role':'white_jump'},'panel':{},'node':{}}} pool=Pool(config,'white',factory) panel=pool.get('panel');self.assertIs(panel,pool.get('panel')) node=pool.get('node');self.assertIs(calls[1][1],calls[2][1]);self.assertIsNone(calls[0][1]);self.assertEqual(len(calls),3) panel.active=False with self.assertRaises(RuntimeError):pool.get('panel') self.assertEqual(len(calls),3) # Never automatically replay after disconnect. pool.close();self.assertFalse(node.active) def test_sessions_are_not_shared_unsafely_between_workers(self): barrier=threading.Barrier(2);made=[] class Client: def get_transport(self):return self def is_active(self):return True def close(self):pass pool=Pool({'hosts':{'w':{'role':'white_jump'},'n':{}}},'w',lambda *args:Client()) def worker(): n=pool.get('n');barrier.wait();made.append(n);self.assertIs(n,pool.get('n')) threads=[threading.Thread(target=worker) for _ in range(2)] for t in threads:t.start() for t in threads:t.join(timeout=5) self.assertEqual(len(made),2);self.assertIsNot(made[0],made[1]);pool.close() def test_existing_codocs_credentials_are_read_without_copy_or_rewrite(self): with tempfile.TemporaryDirectory() as d: p=Path(d)/'vps.md';body='# Fixture\n密码:test-only-phrase\n';p.write_text(body);p.chmod(0o600) self.assertEqual(credentials({'credential_ref':str(p),'credential_format':'codocs-password'}),{'password':'test-only-phrase'}) self.assertEqual(p.read_text(),body);self.assertEqual(len(list(Path(d).iterdir())),1) p.write_text(body+'密码:ambiguous\n') with self.assertRaises(ValueError):credentials({'credential_ref':str(p),'credential_format':'codocs-password'}) def test_private_config_and_explicit_jump_enforced(self): with self.assertRaises(ValueError):Pool({'hosts':{'n':{}}},'w') with self.assertRaises(ValueError):Pool({'hosts':{'w':{}}},'w') with tempfile.TemporaryDirectory() as d: p=Path(d)/'auth.json';p.write_text('{}');p.chmod(0o644) with self.assertRaises(ValueError):private_json(p) p.chmod(0o600);self.assertEqual(private_json(p),{}) link=Path(d)/'link';link.symlink_to(p) with self.assertRaises(ValueError):private_json(link) class ResumeTests(unittest.TestCase): def setUp(self): batch.STOP.clear();self.tmp=tempfile.TemporaryDirectory();self.root=Path(self.tmp.name) spec=importlib.util.spec_from_file_location('batch_fixtures',Path(__file__).with_name('test_batch.py'));m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m) fixture=m.BatchTests();fixture.setUp();self.fixture=fixture;self.plan=fixture.plan self.calls=[];self.generation='a';self.finished=False owner=self class Runner(batch.Coordinator): def call(self,n,phase,action): owner.calls.append((phase,action)) r=dict(key=n['key'],phase=phase,request_sha256='irrelevant-in-unit-fixture',status='waiting' if phase=='audit' and not owner.finished else 'ready',valid_until=time.time()+180) if phase=='preflight':r['resume_dependencies']={k:hashlib.sha256((owner.generation if k=='provision' else k).encode()).hexdigest() for k in ['stage','dns','provision','relay','verify']} return r self.Runner=Runner def tearDown(self):self.fixture.tearDown();self.tmp.cleanup();batch.STOP.clear() def run_batch(self):return self.Runner(self.plan,self.root/'ledger',jobs=1).run() def test_fresh_proofs_skip_only_completed_unchanged_phases(self): self.assertFalse(self.run_batch());self.calls.clear();self.finished=True self.assertTrue(self.run_batch());self.assertEqual(self.calls,[('preflight','probe'),('publish','probe'),('audit','probe')]) def test_dependency_change_rechecks_downstream_and_not_unrelated_stage(self): self.run_batch();self.calls.clear();self.generation='b';self.finished=True;self.run_batch() self.assertEqual([p for p,a in self.calls],['preflight','provision','relay','verify','publish','audit']) def test_expired_and_unknown_proofs_never_skip(self): self.run_batch();runner=self.Runner(self.plan,self.root/'ledger',jobs=1) with runner.ledger.db() as c:c.execute('UPDATE evidence SET expires=0 WHERE phase="dns"') runner.ledger.mark(self.plan['batch_id'],self.plan['nodes'][0]['key'],'stage','unknown') self.calls.clear();self.finished=True;self.run_batch() self.assertIn(('stage','probe'),self.calls);self.assertIn(('dns','probe'),self.calls) self.assertTrue((self.root/'ledger/events.jsonl').is_file()) class SiteAdapterTests(unittest.TestCase): def test_qa_export_binds_raw_bytes_and_latest_traffic_for_independent_review(self): from site_adapter import SiteAdapter with tempfile.TemporaryDirectory() as d: adapter=object.__new__(SiteAdapter);adapter.directory=Path(d);adapter.atomic_json=batch.atomic_json raw=b'{ "state": "live_verified", "qa_user_id": 9 }' latest=dict(node_online=True,u=12,d=34,manifest={'test_user_id':9}) adapter.qa_evidence(dict(key='new',request_sha256='request'),raw,latest) path=Path(d)/'new-evidence.json';value=json.loads(path.read_text()) self.assertEqual(value['report_sha256'],hashlib.sha256(raw).hexdigest()) self.assertEqual(value['panel'],latest);self.assertEqual(path.stat().st_mode & 0o077,0) def test_new_live_qa_exports_new_raw_report_after_transfer_not_prior_report(self): import io from site_adapter import SiteAdapter with tempfile.TemporaryDirectory() as d: adapter=object.__new__(SiteAdapter);adapter.directory=Path(d);adapter.atomic_json=batch.atomic_json ctx=dict(panel_vps='panel',gateway_vps='gateway',node_root='/node',ops_root='/ops',app_root='/app',node_backup_root='/backups',qa_root='/qa',client_binary='/client',subscription_url='https://sub.example.invalid') adapter.plan={'context':ctx};adapter.ctx=ctx raw=json.dumps(dict(state='live_verified',qa_user_id=9,verified_at=int(time.time()),results=[dict(tag='new',live=True,strict_tls=True,exit_ip='192.0.2.11',country='JP')])).encode() class Channel: def shutdown_write(self):pass def recv_exit_status(self):return 0 class In: channel=Channel() def write(self,data):pass class Out(io.BytesIO):channel=Channel() class Writable(io.BytesIO): def __init__(self,files,path):super().__init__();self.files,self.path=files,path def close(self):self.files[self.path]=self.getvalue();super().close() class Sftp: def __init__(self,files):self.files=files def __enter__(self):return self def __exit__(self,*args):pass def open(self,path,mode): if mode=='w':return Writable(self.files,path) if path not in self.files:raise FileNotFoundError(path) return io.BytesIO(self.files[path]) def chmod(self,*args):pass def posix_rename(self,old,new):self.files[new]=self.files.pop(old) state=dict(manifest={'test_user_id':9},node_online=True,groups=['2'],qa_banned=False,u=12,d=34) qa_programs=[] class Remote: def __init__(self,kind):self.kind=kind;self.files={} def open_sftp(self):return Sftp(self.files) def exec_command(self,cmd,timeout=0): if cmd.endswith(' && php'):return In(),Out(json.dumps(state).encode()),Out() if cmd.startswith("python3 - <<'DPLPY'\n"): program=cmd.split('\n',1)[1].rsplit('\nDPLPY',1)[0] compile(program,'actual-fetch-qa-remote','exec') qa_programs.append(program) if 'verify-node.py' in cmd:self.files['/qa/new/qa-report.json']=raw return In(),Out(b'{}'),Out() remotes={key:Remote(key) for key in ['panel','gateway','node']} remotes['panel'].files.update({'/backups/new/node-config.yml':b'fixture-config','/backups/new/node-secret.env':b'fixture-env','/backups/new/qa-private.json':b'{}'}) class Pool: def get(self,key):return remotes[key] adapter.pool=Pool() node=dict(key='new',request_sha256='request',formal_group_id=1,vps_id='node',socks_port=19011,expected_exit='192.0.2.11',country='JP') result=adapter.call(node,'verify','probe');self.assertEqual(result['status'],'ready') self.assertEqual(len(qa_programs),1) self.assertIn('urllib.request.urlopen',qa_programs[0]) exported=json.loads((Path(d)/'new-evidence.json').read_text());self.assertEqual(exported['report_sha256'],hashlib.sha256(raw).hexdigest());self.assertEqual(exported['panel']['d'],34) self.assertEqual(remotes['panel'].files['/backups/new/qa-report.json'],raw) def test_formal_source_snapshot_audit_and_cached_audit_call_chain(self, impact=None, owned=False): import contextlib import io from site_adapter import SiteAdapter adapter=object.__new__(SiteAdapter) ctx=dict(gateway_vps='gateway',qa_root='/qa',ops_root='/ops',subscription_url='https://sub.example.invalid',client_binary='/client',formal_socks_port=19999,node_backup_root='/backups',batch_backup_root='/batch') if impact is not None:ctx['acceptance_impact']=impact candidate=dict(key='new',formal_group_id=7,relay_host='relay.example.invalid',relay_port=47002,tls_domain='n2.example.invalid') adapter.plan={'context':ctx,'nodes':[candidate]};adapter.ctx=ctx adapter.directory=Path('/private/tmp/formal-chain-fixture/ledger/batch') adapter.lock=threading.RLock();adapter.resource_lock=lambda *args:contextlib.nullcontext() adapter.formal_done={};adapter.exit_registry={f'n{i}.example.invalid':dict(expected_exit=f'192.0.2.{i}',country='JP') for i in [1,2]} source={'user_id':1,'owned':owned,'token':'fixture-only','nodes':[dict(id=i,type='anytls',tag='node-'+str(i),relay='relay.example.invalid',relay_port=47000+i,sni=f'n{i}.example.invalid',uuid='fixture-only') for i in [1,2]]} accounts=[] def account_command(group,action,apply=False,client=None): accounts.append(action) return copy.deepcopy(source) if action in ['source','snapshot'] else {'state':'closed'} adapter.account_command=account_command now=int(time.time()) raw=json.dumps({'state':'live_verified','verified_at':now,'results':[dict(tag='node-'+str(i),relay='relay.example.invalid',relay_port=47000+i,sni=f'n{i}.example.invalid',exit_ip=f'192.0.2.{i}',country='JP',live=True,strict_tls=True) for i in [1,2]]}).encode() expected_scope=verification_scope([dict(x,**adapter.exit_registry[x['sni']]) for x in source['nodes']],None if owned else impact) payload=json.loads(raw) for result in payload['results']: if expected_scope[result['tag']]=='tls': result['verification']='tls';result.pop('exit_ip');result.pop('country') raw=json.dumps(payload).encode() files={};persisted=[];programs=[] def execute(client,command,timeout=120): if command.startswith("python3 - <<'DPLFORMAL'\n"): program=command.split('\n',1)[1].rsplit('\nDPLFORMAL',1)[0] compile(program,'actual-formal-remote','exec');programs.append(program) files['/qa/formal-group-7/report.json']=raw return '' def php(client,body): return {} if '$out=[]' in body else {'id':9,'token':'fixture-only','banned':True,'excluded':True} def write(client,path,value):files[path]=value.encode() if isinstance(value,str) else value adapter.atomic_json=lambda path,value:persisted.append(value) class Channel: def shutdown_write(self):pass def recv_exit_status(self):return 0 class In: channel=Channel() def write(self,data):pass class Out(io.BytesIO):channel=Channel() class Gateway: def exec_command(self,command,timeout=0): if command!='python3 -':raise AssertionError('Unexpected QA probe command') return In(),Out(b'403\n'),Out() gateway=Gateway() adapter.formal(candidate,None,gateway,php,execute,lambda client,path:files.get(path),write) self.assertEqual(len(programs),1) self.assertEqual(len(persisted),1) self.assertEqual(persisted[0]['verification_scope'],expected_scope) private=json.loads(files['/qa/formal-group-7/source-private.json']) self.assertEqual({n['tag']:n['verification'] for n in private['expectations']},expected_scope) self.assertEqual(len(persisted[0]['snapshot']),2,'The old node must remain in complete acceptance') self.assertTrue(all(n['type']=='anytls' for n in persisted[0]['snapshot'])) self.assertEqual(persisted[0]['qas']['users']['new']['http_status'],403) self.assertIn('cleanup',accounts) self.assertEqual(adapter.formal(candidate,None,gateway,php,execute,lambda client,path:files.get(path),write)['nodes'],2) self.assertEqual(len(programs),1,'A cached audit rechecks topology without rerunning the client') def test_reviewed_append_formal_chain_uses_light_checks_and_preserves_all_nodes(self): self.test_formal_source_snapshot_audit_and_cached_audit_call_chain({'shared_changes':[],'affected_snis':[],'review':'task/review'}) def test_owned_formal_account_forces_full_tests_even_for_reviewed_append(self): self.test_formal_source_snapshot_audit_and_cached_audit_call_chain({'shared_changes':[],'affected_snis':[],'review':'task/review'},owned=True) def test_preserved_service_review_binds_original_and_exact_observation(self): from site_adapter import SiteAdapter adapter=object.__new__(SiteAdapter);adapter.ctx={'preserved_service':dict(unit='XrayR',binary='/bin/xrayr',config='/etc/xrayr.yml',port=443)} current=dict(unit_sha256='u',binary_sha256='b',config_sha256='c',listeners=['*:443'],active=True,pid=1,started='a',restarts=0) files={};read=lambda client,path:files.get(path) def write(client,path,value):files[path]=value.encode() if isinstance(value,str) else value def execute(client,cmd):return json.dumps(current) if cmd.startswith('python3') else '' adapter.guard_service(None,'/backup',read,write,execute) original=copy.deepcopy(current);current.update(pid=2,started='b',restarts=1) with self.assertRaisesRegex(RuntimeError,'investigate_'):adapter.guard_service(None,'/backup',read,write,execute) files['/backup/preserved-service-review.json']=json.dumps(dict(before_sha256=digest(original),after_sha256=digest(current),reason='Independently reviewed lifecycle panic',evidence_sha256='e'*64)).encode() adapter.guard_service(None,'/backup',read,write,execute) current['config_sha256']='changed' with self.assertRaisesRegex(RuntimeError,'conflict'):adapter.guard_service(None,'/backup',read,write,execute) self.assertEqual(json.loads(files['/backup/preserved-service-original.json']),original) def test_generated_remote_python_and_group_independent_php_compile(self): import ast import subprocess import io from site_adapter import SiteAdapter tree=ast.parse((SCRIPTS/'site_adapter.py').read_text()) for method in [n for n in ast.walk(tree) if isinstance(n,ast.FunctionDef) and n.name in ('formal','guard_service','certificate_gate')]: for c in ast.walk(method): if isinstance(c,ast.Assign) and any(isinstance(t,ast.Name) and t.id=='script' for t in c.targets): program=eval(compile(ast.Expression(c.value),'script-builder','eval'),{'repr':repr,'spec':{},'ctx':{'subscription_url':'https://sub.example.invalid'},'value':{'token':'test-only'}}) compile(program,'remote-'+method.name,'exec') with tempfile.TemporaryDirectory() as d: a=object.__new__(SiteAdapter);a.plan={'context':dict(panel_vps='panel',gateway_vps='gateway',node_root='/node',ops_root='/ops',app_root='/app',batch_backup_root='/batch',node_backup_root='/backups',qa_root='/qa',gateway_containers=['gateway-a','gateway-b'])};a.ctx=a.plan['context'];a.directory=Path(d) php_sources=[];calls=[] class Channel: def shutdown_write(self):pass def recv_exit_status(self):return 0 class In: channel=Channel() def write(self,body):php_sources.append(body) class Out: channel=Channel() def read(self): body=php_sources[-1] if '$base=json_decode' in body:return b'true' return json.dumps({'manifest':{},'node_online':True,'groups':['7'],'qa_banned':True,'u':1,'d':1}).encode() class DockerOut: channel=Channel() def read(self):return json.dumps([dict(Name='/gateway-a',Id='id-a',State={'StartedAt':'then'},RestartCount=0)]).encode() class Sftp: def __enter__(self):return self def __exit__(self,*args):pass def open(self,path,mode):return io.BytesIO(json.dumps({'/gateway-a':{'id':'id-a','started':'then','restarts':0}}).encode()) class Remote: def exec_command(self,cmd,timeout=0): calls.append(cmd) if cmd.startswith('docker inspect'): if cmd!='docker inspect gateway-a gateway-b':raise RuntimeError('Invalid gateway inventory command') return In(),DockerOut(),io.BytesIO() return In(),Out(),io.BytesIO() def open_sftp(self):return Sftp() class Pool: def get(self,rid):return Remote() a.pool=Pool();a.guard_service=lambda *args:None;a.formal=lambda *args:None n=dict(key='sg-new',request_sha256='a'*64,formal_group_id=7,vps_id='node') result=a.call(n,'audit','probe');self.assertEqual(result['status'],'ready');self.assertGreaterEqual(len(php_sources),2) for php in php_sources: result=subprocess.run(['php','-l'],input=php,text=True,capture_output=True) self.assertEqual(result.returncode,0,result.stderr) def test_pool_real_connect_interface_keeps_strict_keys_and_jump_sock(self): import types recorded=[] class FakeClient: def load_host_keys(self,path):recorded.append(('keys',path)) def set_missing_host_key_policy(self,p):recorded.append(('reject',p)) def get_host_keys(self):return self def add(self,*args):recorded.append(('approved-key',args)) def connect(self,host,**kw):recorded.append(('connect',host,kw)) def get_transport(self):return self def is_active(self):return True def open_channel(self,kind,target,origin):recorded.append(('jump',kind,target));return 'through-white' def close(self):pass class Key: def asbytes(self):return b'approved-public-key-fixture' def get_name(self):return 'ssh-ed25519' class Keys: def __init__(self,path):pass def lookup(self,name):return {'ssh-ed25519':Key()} module=types.SimpleNamespace(SSHClient=FakeClient,RejectPolicy=lambda:'reject-unknown',HostKeys=Keys) pin='SHA256:'+__import__('base64').b64encode(hashlib.sha256(Key().asbytes()).digest()).decode().rstrip('=') with tempfile.TemporaryDirectory() as d,patch.dict(sys.modules,{'paramiko':module}): keys=Path(d)/'known_hosts';keys.write_text('public fixture');keys.chmod(0o600) credentials=Path(d)/'credentials.json';credentials.write_text(json.dumps({'password':'test-only-fixture'}));credentials.chmod(0o600) hosts={'w':dict(role='white_jump',host='192.0.2.1',username='root',credential_ref=str(credentials)),'n':dict(host='192.0.2.2',username='root',credential_ref=str(credentials))} for host in hosts.values():host['host_key_sha256']=pin p=Pool(dict(hosts=hosts,known_hosts=str(keys)),'w');p.get('n');p.get('n');p.close() before_connects=len([x for x in recorded if x[0]=='connect']) hosts['w']['host_key_sha256']='SHA256:'+'b'*43 blocked=Pool(dict(hosts=hosts,known_hosts=str(keys)),'w') with self.assertRaises(ValueError):blocked.get('n') self.assertEqual(len([x for x in recorded if x[0]=='connect']),before_connects) blocked.close() connections=[x for x in recorded if x[0]=='connect'];self.assertEqual(len(connections),2);self.assertIsNone(connections[0][2]['sock']);self.assertEqual(connections[1][2]['sock'],'through-white') self.assertEqual(len([x for x in recorded if x[0]=='reject']),2) for _,_,kw in connections:self.assertFalse(kw['allow_agent']);self.assertFalse(kw['look_for_keys']) def test_configure_is_local_only_and_pins_code_context_and_interpreter(self): import types spec=importlib.util.spec_from_file_location('configure_site',SCRIPTS/'configure-site.py');module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module) with tempfile.TemporaryDirectory() as d: root=Path(d);ssh=root/'ssh.json';registry=root/'exits.json';(root/'known_hosts').write_text('public fixture');(root/'known_hosts').chmod(0o600) hosts={'VPS-001':dict(role='white_jump',host='192.0.2.1',username='root'),'VPS-002':dict(host='192.0.2.2',username='root'),'VPS-003':dict(host='192.0.2.3',username='root'),'VPS-011':dict(host='192.0.2.11',username='root')} for host in hosts.values():host['host_key_sha256']='SHA256:'+'a'*43 ssh.write_text(json.dumps({'hosts':hosts,'known_hosts':str(root/'known_hosts')}));ssh.chmod(0o600);registry.write_text('{}');registry.chmod(0o600) fixture_spec=importlib.util.spec_from_file_location('base_fixture',Path(__file__).with_name('test_batch.py'));f=importlib.util.module_from_spec(fixture_spec);fixture_spec.loader.exec_module(f) original=f.BatchTests();original.setUp() try: base=original.plan;node=base['nodes'][0];node['node_address']='192.0.2.11';node['expected_exit']='192.0.2.11';base['context'].update(jump_vps='VPS-001',panel_vps='VPS-002',gateway_vps='VPS-003');registry.write_text(json.dumps({node['tls_domain']:{'expected_exit':node['expected_exit'],'country':node['country']}})) site=dict(client_sha256='d'*64,ops_root='/ops',node_root='/node',batch_backup_root='/backup/batch',node_backup_root='/backup/nodes',qa_root='/qa/batch',provider_snapshot=str(root/'provider.json'),exit_registry=str(registry),ssh_config=str(ssh),preserved_service=dict(unit='XrayR',binary='/bin/xrayr',config='/etc/xrayr.yml',port=443),formal_socks_port=19999,gateway_containers=['gateway-a']) with patch.dict(sys.modules,{'paramiko':types.SimpleNamespace(__version__='5.0.0')}): plan=module.configure(base,site);self.assertEqual(plan['runtime']['kind'],'ssh-site-v1');self.assertFalse((root/'ledger').exists()) runner=batch.Coordinator(plan,root/'ledger',jobs=1);runner.runtime.close();runner.check_runtime() ssh.write_text(ssh.read_text()+'\n') with self.assertRaises(ValueError):runner.check_runtime() finally:original.tearDown() class PackagingTests(unittest.TestCase): def test_committed_flat_snapshot_preserves_worktree_index_and_base(self): import subprocess spec=importlib.util.spec_from_file_location('bundle_ops',SCRIPTS/'bundle-ops.py');module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module) with tempfile.TemporaryDirectory() as d: root=Path(d);repo=root/'repo';repo.mkdir() def git(*args):return subprocess.check_output(['git','-C',str(repo),*args],stderr=subprocess.DEVNULL) git('init');git('config','user.name','Offline Test');git('config','user.email','test@example.invalid');git('config','commit.gpgsign','false') (repo/'base-only.txt').write_text('preserved operations base');git('add','.');git('commit','-m','base');base=git('rev-parse','HEAD').decode().strip() source_dir=repo/'scripts/node-onboarding';source_dir.mkdir(parents=True) for name in module.FILES:(source_dir/name).write_text('committed '+name) git('add','.');git('commit','-m','source');source=git('rev-parse','HEAD').decode().strip() (repo/'base-only.txt').write_text('unrelated user modification');(repo/'unrelated.txt').write_text('staged unrelated');git('add','unrelated.txt') index_before=git('diff','--cached','--binary');status_before=git('status','--porcelain') output=root/'operations.bundle';result=module.build(repo,source,base,'codex/offline-ops',output) self.assertTrue(output.exists());self.assertEqual(git('diff','--cached','--binary'),index_before);self.assertEqual(git('status','--porcelain'),status_before) self.assertEqual(git('show',result['commit']+':panel-acceptance.php'),b'committed panel-acceptance.php') self.assertEqual(git('show',result['commit']+':base-only.txt'),b'preserved operations base') self.assertEqual(git('rev-parse','HEAD').decode().strip(),source) git('bundle','verify',str(output)) with self.assertRaises(ValueError):module.build(repo,source,base,'codex/offline-ops',root/'second.bundle') advanced=module.build(repo,source,result['commit'],'codex/offline-ops',root/'advanced.bundle',advance=True) self.assertEqual(git('rev-parse',advanced['commit']+'^').decode().strip(),result['commit']) with self.assertRaises(ValueError):module.build(repo,source,base,'codex/offline-ops',root/'stale.bundle',advance=True) def test_event_stream_preserves_final_result_and_avoids_status_polling(self): import subprocess spec=importlib.util.spec_from_file_location('event_fixture',Path(__file__).with_name('test_batch.py'));f=importlib.util.module_from_spec(spec);spec.loader.exec_module(f) test=f.BatchTests();test.setUp() try: result=subprocess.run(test.argv()+['--events'],capture_output=True,text=True,timeout=30) self.assertEqual(result.returncode,0,result.stderr) lines=[json.loads(x) for x in result.stdout.splitlines()] self.assertTrue(lines[-1]['complete']);self.assertTrue(any(x.get('phase')=='provision' for x in lines[:-1])) for x in lines[:-1]:self.assertEqual(set(x),{'batch','node','phase','status','time'}) finally:test.tearDown() class CertificateHookGateTests(unittest.TestCase): """Execute certificate_gate's actual remote Python against an isolated filesystem.""" def check_gate(self, target='', global_hook='', owned=True, directory_hook=None, conflict=None, global_file='/etc/letsencrypt/cli.ini'): import types from site_adapter import SiteAdapter adapter=object.__new__(SiteAdapter);adapter.ctx={'node_root':'/opt/dpl-node/provision'} node={'tls_domain':'qa.example.invalid','request_sha256':'owner'} with tempfile.TemporaryDirectory() as tmp: root=Path(tmp) def mapped(value):return root/str(value).lstrip('/') if target: path=mapped('/etc/letsencrypt/renewal/qa.example.invalid.conf');path.parent.mkdir(parents=True);path.write_text(target) if global_hook: path=mapped(global_file);path.parent.mkdir(parents=True,exist_ok=True);path.write_text(global_hook) if directory_hook: path=mapped(directory_hook);path.parent.mkdir(parents=True,exist_ok=True);path.write_text('# hook') if conflict: path=mapped('/etc/letsencrypt/'+conflict+'/qa.example.invalid');path.parent.mkdir(parents=True,exist_ok=True);path.mkdir() calls=[] def execute(_,command): calls.append(command) if command.startswith("python3 - <<'DPLCERTGATE'\n"): program=command.split('\n',1)[1].rsplit('\nDPLCERTGATE',1)[0] compile(program,'actual-generated-certificate-gate','exec') with patch.dict(sys.modules,{'pathlib':types.SimpleNamespace(Path=mapped)}): exec(program,{'__builtins__':__builtins__}) owner=json.dumps({'request_sha256':'owner'}).encode() if owned else None adapter.certificate_gate(node,None,lambda *args:owner,execute) self.assertEqual(len(calls),2) # Tool readiness is reached only after gate success. def test_renew_and_deploy_hook_aliases_accept_only_exact_command(self): command='/bin/sh /opt/dpl-node/provision/renew-node.sh' for name in ['renew_hook','renew-hook','deploy_hook','deploy-hook']: with self.subTest(name=name):self.check_gate(target='[renewalparams]\n'+name+' = '+command+'\n') for second in ['renew_hook','deploy_hook']: with self.subTest(duplicate=second),self.assertRaises(SystemExit): self.check_gate(target='renew_hook = '+command+'\n'+second+' = '+command+'\n') def test_malicious_unknown_and_additional_hooks_block_before_tools(self): command='/bin/sh /opt/dpl-node/provision/renew-node.sh' for hook in ['renew_hook = /bin/false','renew-hook = '+command+'; touch /tmp/side-effect','deploy_hook = '+command+' extra','renew_hook = ','pre_hook = /bin/true','post-hook = /bin/true','manual_auth_hook = /bin/true','custom_hook = /bin/true','renew_hook = '+command+'\nrenew_hook = /bin/false','renew_hook = '+command+'\npost_hook = /bin/true']: with self.subTest(hook=hook),self.assertRaises(SystemExit):self.check_gate(target=hook) def test_global_renew_alias_and_other_executable_hooks_block(self): for file in ['/etc/letsencrypt/cli.ini','/root/.config/letsencrypt/cli.ini']: for name in ['renew-hook','renew_hook','deploy_hook','pre_hook','post_hook','manual_cleanup_hook','unknown_hook']: with self.subTest(file=file,name=name),self.assertRaises(SystemExit): self.check_gate(global_hook=name+' = /bin/true',global_file=file) def test_directory_hooks_and_unowned_certificate_material_still_block(self): for hook in ['/etc/letsencrypt/renewal-hooks/deploy/unrelated','/var/snap/certbot/common/letsencrypt/renewal-hooks/pre/unrelated']: with self.subTest(hook=hook),self.assertRaises(SystemExit):self.check_gate(directory_hook=hook) for kind in ['live','archive']: with self.subTest(kind=kind),self.assertRaises(SystemExit):self.check_gate(owned=False,conflict=kind) with self.assertRaises(SystemExit):self.check_gate(owned=False,target='authenticator = standalone') def test_comments_and_non_hook_options_do_not_create_false_conflicts(self): self.check_gate(target='# renew_hook = /bin/false\n[renewalparams]\nauthenticator = standalone\nrun_deploy_hooks = True\n',global_hook='email = qa@example.invalid\n# renew-hook = /bin/false\n') if __name__=='__main__':unittest.main()