Files
archy/tests/regression/os-ota-crypto-held-postboot.py
T

61 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Cold-boot evidence for an interrupted crypto/D09 transaction; never promote it.
Observe real installed kernel/package effects and native signatures, but require
reconciliation and refuse replay/postboot success after the owner's anchor loss.
"""
import json
from pathlib import Path
import platform
import subprocess
import sys
assert 'QEMU' in Path('/sys/class/dmi/id/sys_vendor').read_text()
assert subprocess.check_output(['findmnt','-n','-o','SOURCE','/'],text=True).strip()=='/dev/vda'
sys.path.insert(0,'/usr/libexec')
import os_update_executor as executor
assert executor.ROOT==Path('/var/lib/archipelago-os-update')
binding=json.loads(Path('/etc/archipelago-d09/binding.json').read_text())
instance=executor.Executor(binding['os_update'])
assert instance.status['phase']=='RECONCILIATION_REQUIRED'
assert platform.release()==instance.plan['boot']['expected_release']
before=instance.status_path.read_bytes()
inventory=executor.inventory()
instance.verify_installed()
boot=instance.boot_provenance()
evidence=Path('/var/log/os-ota-stage')
expected=json.loads((evidence/'canaries-before.json').read_text())
assert {p.name:executor.file_hash(p) for p in Path('/var/lib/archipelago/ota-synthetic-canaries').iterdir()}==expected
assert Path('/var/lib/archipelago').stat().st_uid==1000 and executor.ROOT.stat().st_uid==0
subprocess.run(['runuser','-u','archipelago','--','touch','/var/lib/archipelago/held-postboot-positive-probe'],check=True)
denials=[]
for args in (['touch',str(executor.ROOT/'app-write-denied')],
['python3','-c',"import os;os.rename('/var/lib/archipelago-os-update','/var/lib/app-rename-denied')"],
['python3','-c',"open('/root/os-ota-crypto-fixture/test.seed','rb').read()"]):
r=subprocess.run(['runuser','-u','archipelago','--',*args],capture_output=True,text=True)
assert r.returncode!=0
denials.append(dict(command=args,exit=r.returncode))
refusals=[]
for phase in ('apply','postboot'):
try:
executor.stage(binding,'APPLY',phase)
except (executor.ReconciliationRequired,ValueError) as error:
refusals.append(dict(operation=phase,error=str(error)))
else:
raise AssertionError('uncertain transaction promoted/replayed')
assert executor.inventory()==inventory and instance.status_path.read_bytes()==before
public=subprocess.run(['/usr/libexec/archipelago-d09ctl','inspect'],capture_output=True,text=True)
assert public.returncode==0,public.stderr
status=json.loads(public.stdout)
assert status['state']=='UNKNOWN' and not (status.get('os_update') or {}).get('post_boot_verified',False)
result=dict(passed=True,scope='held transaction coldboot/crypto/custody/refusal proof ONLY',
full_ota_passed=False,postboot_confirmed=False,production_approved=False,candidate_qualified=False,
running_kernel=platform.release(),kernel_build=platform.version(),boot_id=executor.boot_id(),
authenticated_archive_sha256=boot['archive_sha256'],verified_kernel_files=len(boot['files']),
native_crypto_verified=True,all_planned_packages_configured=True,canaries_preserved=True,
os_phase=instance.status['phase'],status_unchanged=True,package_replay=False,
app_data_uid=1000,os_root=str(executor.ROOT),custody_denials=denials,refusals=refusals,
authenticated_owner_inspect=status)
(evidence/'crypto-held-postboot-result.json').write_text(json.dumps(result,indent=2)+'\n')
print(json.dumps(result,indent=2),flush=True)