"""Real Windows worker lifecycle validation. Requires a vendor DPAPI key; never shipped.""" import argparse import base64 import configparser from datetime import datetime, timezone, timedelta import json import os from pathlib import Path import secrets import socket import subprocess import sys import threading import time from urllib.parse import urlsplit ROOT=Path(__file__).resolve().parents[1] sys.path.insert(0,str(ROOT)) def main(): parser=argparse.ArgumentParser() parser.add_argument('--executable',type=Path,required=True) parser.add_argument('--resources',type=Path,required=True) parser.add_argument('--private-key',type=Path,required=True) parser.add_argument('--data',type=Path,required=True) args=parser.parse_args() target=args.data.resolve() if target.exists(): raise RuntimeError('Use a fresh test data directory') target.mkdir(parents=True) os.environ['MONITOR_DATA_DIR']=str(target) os.environ['MONITOR_DESKTOP']='1' import psutil import requests from multiprocessing.connection import Listener from cryptography.hazmat.primitives import serialization from monitor_runtime.licensing import canonical from monitor_runtime.windows import protect,Job from monitor_runtime.bootstrap import local_addresses cfg=json.loads((args.resources/'deploy/windows/default-config.json').read_text(encoding='utf-8')) def free_port(): with socket.socket() as sock: sock.bind(('127.0.0.1',0)) return sock.getsockname()[1] ports=[free_port() for _ in range(5)] cfg.update(adminPort=ports[0],mediaHttpPort=ports[1],mediaRtspPort=ports[2],mediaRtmpPort=ports[3], isEnableLoginCaptcha=False,autoAddStreamProxy=False) cfg['sipServer']['sipServerPort']=ports[4] (target/'config.json').write_text(json.dumps(cfg),encoding='utf-8') ini=configparser.ConfigParser(interpolation=None) ini.optionxform=str ini.read(args.resources/'deploy/windows/zlm-template.ini',encoding='utf-8') for section,port in [('http',ports[1]),('rtsp',ports[2]),('rtmp',ports[3]),('rtp_proxy',0)]: ini[section]['port']=str(port) with (target/'zlm-template.ini').open('w',encoding='utf-8') as stream:ini.write(stream) raw=args.private_key.read_bytes() assert raw.startswith(b'MONITOR-DPAPI-V1\n') key=serialization.load_pem_private_key(protect(base64.b64decode(raw.split(b'\n',1)[1]),decrypt=True),None) command=[str(args.executable.resolve())] if args.executable.suffix=='.py':command=[sys.executable]+command env=os.environ.copy() env['MONITOR_SERVICE_MODE']='disabled' if args.executable.suffix.lower()=='.exe': env.pop('PYTHONPATH',None);env.pop('PYTHONHOME',None) windows=env['SYSTEMROOT'] env['PATH']=windows+'/System32;'+windows+';'+windows+'/System32/WindowsPowerShell/v1.0' session=requests.Session() session.trust_env=False base='http://127.0.0.1:'+str(ports[0]) checks=[] def start(): auth=secrets.token_bytes(32) address=r'\\.\pipe\MonitorValidation-'+secrets.token_hex(12) listener=Listener(address,family='AF_PIPE',authkey=auth) job=Job() log=(target/'worker.log').open('ab',buffering=0) child=subprocess.Popen(command+['--worker',address,auth.hex()],env=env,cwd=target, stdout=log,stderr=log,creationflags=subprocess.CREATE_NO_WINDOW) log.close() job.assign(child) accepted=[] threading.Thread(target=lambda:accepted.append(listener.accept()),daemon=True).start() deadline=time.monotonic()+60 while not accepted: if child.poll() is not None or time.monotonic()>deadline: job.close();listener.close() raise RuntimeError('Worker failed before pipe connection') time.sleep(.1) conn=accepted[0];listener.close();conn.send('run') if not conn.poll(180): job.close();raise RuntimeError('Worker startup timeout') message=conn.recv() return child,conn,job,message def finish(child,conn,job,expected=0,stop=True): descendants=psutil.Process(child.pid).children(recursive=True) if child.poll() is None else [] if stop:conn.send('stop') try: code=child.wait(timeout=40) assert code==expected,(code,expected) finally: conn.close();job.close() gone,alive=psutil.wait_procs(descendants,timeout=10) assert not alive,[p.pid for p in alive] child=conn=job=None try: child,conn,job,message=start() assert message['phase']=='setup',message assert session.get(base+'/setup',timeout=10).status_code==200 token=urlsplit(message['url']).fragment csrf=session.cookies['csrftoken'] session.headers['X-CSRFToken']=csrf assert session.post(base+'/license/request',data={'setup_token':'wrong'},timeout=10).status_code==403 request=session.post(base+'/license/request',data={'setup_token':token},timeout=30).json() payload={'schema_version':1,'product':'monitor','license_id':'lifecycle-validation', 'customer':'local build validation only','machine_fingerprint':request['fingerprint'], 'issued_at':datetime.now(timezone.utc).isoformat(), 'expires_at':(datetime.now(timezone.utc)+timedelta(hours=2)).isoformat()} doc={'payload':payload,'signature':base64.b64encode(key.sign(canonical(payload))).decode()} response=session.post(base+'/license/import',data={'setup_token':token}, files={'license':('test.json',json.dumps(doc),'application/json')},timeout=15) assert response.status_code==200,response.text[:200] address=local_addresses()[0] response=session.post(base+'/setup',data={'setup_token':token,'username':'validation-admin', 'password':'Test-setup!'+secrets.token_hex(12),'address':address,'confirm':'unused'},timeout=10) assert response.status_code==400 password='Test-setup!'+secrets.token_hex(12) response=session.post(base+'/setup',data={'setup_token':token,'username':'validation-admin', 'password':password,'confirm':password,'address':address},timeout=10) assert response.status_code==200,response.text[:200] finish(child,conn,job,expected=20,stop=False);child=conn=job=None checks.append('real HTTP bootstrap, CSRF, signed license, fresh admin and restart request') child,conn,job,message=start() assert message['phase']=='ready',message assert (target/'log/zlm').is_dir() assert not (args.resources/'zlm/log').exists(), 'ZLM wrote into program resources' assert session.get(base+'/login',timeout=15).status_code==200 assert session.get(base+'/static/lib/js/app.js',timeout=15).status_code==200 assert session.get(base+'/setup',timeout=15).status_code==403 assert session.get(base+'/static/storage/private.jpg',timeout=15).status_code==403 with socket.create_connection(('127.0.0.1',ports[1]),timeout=5):pass listeners=psutil.net_connections(kind='udp') owned={child.pid}|{p.pid for p in psutil.Process(child.pid).children(recursive=True)} assert any(c.pid in owned and c.laddr.port==ports[4] for c in listeners) children=psutil.Process(child.pid).children(recursive=True) assert any(p.name().lower()=='monitor_zlm.exe' for p in children) assert not any('spawn_main' in ' '.join(p.cmdline()) for p in children) response=session.post(base+'/login',data={'username':'validation-admin','password':password},timeout=15) assert response.status_code==200,response.text[:200] assert session.get(base+'/license/status',timeout=15).status_code==200,response.text[:200] checks.append('Waitress login/static; ZLM and SIP ready; analysis not auto-started') expiry_payload=dict(payload,expires_at=(datetime.now(timezone.utc)+timedelta(seconds=7)).isoformat()) expiry_doc={'payload':expiry_payload,'signature':base64.b64encode(key.sign(canonical(expiry_payload))).decode()} (target/'license.json').write_text(json.dumps(expiry_doc),encoding='utf-8') finish(child,conn,job,expected=20,stop=False);child=conn=job=None child,conn,job,message=start() assert message['phase']=='license',message assert not any(p.name().lower()=='monitor_zlm.exe' for p in psutil.Process(child.pid).children(recursive=True)) assert session.get(base+'/license/status',timeout=10).json()['valid'] is False session.headers['X-CSRFToken']=session.cookies['csrftoken'] response=session.post(base+'/license/import',files={'license':('renew.json',json.dumps(doc),'application/json')},timeout=15) assert response.status_code==200,response.text[:200] finish(child,conn,job,expected=20,stop=False);child=conn=job=None child,conn,job,message=start() assert message['phase']=='ready',message checks.append('runtime expiry stops services; authenticated renewal restores service') finish(child,conn,job);child=conn=job=None checks.append('graceful shutdown and owned descendant cleanup') with socket.socket() as occupied: occupied.bind(('0.0.0.0',ports[0]));occupied.listen() child,conn,job,message=start() assert message['phase']=='failed',message finish(child,conn,job,expected=1,stop=False);child=conn=job=None assert occupied.getsockname()[1]==ports[0] checks.append('port conflict fails without killing the listener') child,conn,job,message=start() assert message['phase']=='ready',message children=psutil.Process(child.pid).children(recursive=True) job.close();job=None child.wait(timeout=15) _,alive=psutil.wait_procs(children,timeout=10) assert not alive conn.close();child=conn=None checks.append('Job Object kills owned children on forced shutdown') (target/'validation.json').write_text(json.dumps({'status':'passed','checks':checks},indent=2),encoding='utf-8') print(json.dumps(checks,indent=2)) finally: if child and child.poll() is None: if conn: try:conn.send('stop');child.wait(timeout=30) except (OSError,subprocess.TimeoutExpired):pass if job:job.close() if conn:conn.close() if __name__=='__main__':main()