Files
kefu/deploy/patient-session-20260918/server-bundle/patient_server_guard.py
T
2026-09-21 10:34:06 +08:00

43 lines
3.2 KiB
Python

"""Read-only baseline or deployed-code verification; prints hashes/status only."""
from pathlib import Path
import argparse, hashlib, json, subprocess, urllib.request, urllib.error
parser=argparse.ArgumentParser();parser.add_argument('mode',choices=['baseline','candidate','deployed']);args=parser.parse_args()
folder=Path(__file__).resolve().parent;project=Path('/opt/im-admin')
base=json.loads((folder/'server-baseline-patient.json').read_text())
manifest=json.loads((folder/'patient-manifest.json').read_text())
image=lambda ref:subprocess.check_output(['docker','image','inspect',ref,'--format','{{.Id}}'],text=True).strip()
expected=dict(base['expected'])
if args.mode=='baseline':
assert image('zyt/wecom-admin:current')==base['image'],'Live image changed'
for container in ['im-admin-api-1','im-admin-gateway-1','im-admin-knowledge-worker-1']:
actual_image=subprocess.check_output(['docker','inspect',container,'--format','{{.Image}}'],text=True).strip()
assert actual_image==base['image'],'Running image changed: '+container
else:
for row in manifest:
if row['path'].startswith('wechat_rpa/'):expected[Path(row['path']).name]=row['sha256']
for row in manifest:
path=folder/('payload/'+row['path'] if row['path'].startswith('wechat_rpa/') else row['path'])
assert path.resolve().is_relative_to(folder.resolve())
assert path.stat().st_size==row['bytes'] and hashlib.sha256(path.read_bytes()).hexdigest()==row['sha256'],row['path']
if args.mode!='candidate':
for name,wanted in expected.items():
assert hashlib.sha256((project/'wechat_rpa'/name).read_bytes()).hexdigest()==wanted,'Host drift: '+name
code='from pathlib import Path;import hashlib,json,sys;names=json.load(sys.stdin);print(json.dumps({n:hashlib.sha256((Path("/app/wechat_rpa")/n).read_bytes()).hexdigest() for n in names}))'
actual=json.loads(subprocess.check_output(['docker','exec','-i','im-admin-api-1','python','-c',code],input=json.dumps(list(expected)).encode()))
assert actual==expected,'Container code differs from expected'
if args.mode=='deployed':
wanted=image('zyt/wecom-admin:patient-session-20260918')
assert image('zyt/wecom-admin:current')==wanted
for name in ['im-admin-api-1','im-admin-gateway-1','im-admin-knowledge-worker-1']:
raw=json.loads(subprocess.check_output(['docker','inspect',name]))[0]
assert raw['Image']==wanted and raw['State']['Running'] and raw['RestartCount']==0,name
assert raw['State'].get('Health',{}).get('Status','not_configured') in ('healthy','not_configured'),name
for url in ['http://127.0.0.1:18766/api/v2/health','http://127.0.0.1:18770/health']:
with urllib.request.urlopen(url,timeout=15) as response:assert response.status==200
# Existing patient route must still reject anonymous reads; do not query real patients.
try:
urllib.request.urlopen('http://127.0.0.1:18766/api/v2/archive/desktop/patient-context?external_account_id=synthetic&conversation_external_id=synthetic',timeout=15)
except urllib.error.HTTPError as exc:assert exc.code in (401,403),exc.code
else:raise AssertionError('Patient API unexpectedly accessible without authentication')
print(json.dumps({'mode':args.mode,'files':len(expected),'status':'passed','patientDataRead':False}))