74 lines
4.4 KiB
Python
74 lines
4.4 KiB
Python
"""Install and recover the real custody owner in a disposable PID1 realm."""
|
|
import json
|
|
import hashlib
|
|
import os
|
|
from pathlib import Path
|
|
import time
|
|
|
|
def qualify(c):
|
|
assert os.geteuid()==0 and Path('/run/.containerenv').exists()
|
|
run,write,dump=c['run'],c['write'],c['dump']
|
|
root=Path('/var/lib/archipelago');root.mkdir(mode=0o755,exist_ok=True)
|
|
assert not list(root.iterdir()),'new disposable data root required'
|
|
run('mount','-t','tmpfs','-o','size=64m,mode=0755','c01-delivery-data',str(root))
|
|
os.chown(root,1000,1000)
|
|
support=root/'support';support.mkdir(mode=0o755)
|
|
write(support/'old-receipt',b'preserved disposable evidence',0o644)
|
|
from os_update_mount_custody import identity
|
|
fd=os.open(support,os.O_PATH|os.O_DIRECTORY)
|
|
try:expected=identity(fd)
|
|
finally:os.close(fd)
|
|
appointment=Path('/etc/archipelago-os-custody-appointment.json')
|
|
dump(appointment,{'version':1,'node':os.uname().nodename,'source':str(support),
|
|
'source_identity':expected,'alias':'/var/lib/archipelago-delivery-support',
|
|
'os_root':'/var/lib/archipelago-os-update','stage_name':'delivery-os-custody'})
|
|
installed=run('/usr/bin/python3','-B',str(c['PACKAGE']/'scripts/provision-os-custody.py'),
|
|
'--appointment',str(appointment),'--sha256',hashlib.sha256(appointment.read_bytes()).hexdigest())
|
|
dump(c['EVIDENCE']/'durable-os-custody-install.json',json.loads(installed.stdout))
|
|
run('systemctl','daemon-reload');run('systemctl','start','archipelago-os-custody.service')
|
|
first=json.loads(Path('/var/lib/archipelago-delivery-coordinator/mounts.json').read_text())
|
|
target=Path('/var/lib/archipelago-os-update');marker=target/'preserved-journal'
|
|
write(marker,b'never reset',0o600);before=marker.stat()
|
|
run('systemctl','stop','archipelago-os-custody.service')
|
|
run('umount',str(target));run('umount','/var/lib/archipelago-delivery-support')
|
|
run('systemctl','start','archipelago-os-custody.service')
|
|
after=marker.stat()
|
|
assert (before.st_dev,before.st_ino,before.st_uid,before.st_gid)==(after.st_dev,after.st_ino,0,0)
|
|
assert marker.read_bytes()==b'never reset'
|
|
recovered=json.loads(Path('/var/lib/archipelago-delivery-coordinator/mounts.json').read_text())
|
|
assert recovered['objects']==first['objects'] and recovered['sequence']>first['sequence']
|
|
# Root consumer tied to the live owner; product drop-in uses identical unit semantics.
|
|
c['unit']('c01-custody-consumer.service','[Unit]\nBindsTo=archipelago-os-custody.service\nAfter=archipelago-os-custody.service\n'
|
|
'[Service]\nExecStart=/usr/bin/sleep infinity\nRestart=no\n')
|
|
run('systemctl','daemon-reload');run('systemctl','start','c01-custody-consumer.service')
|
|
pid=os.fork()
|
|
if pid==0:
|
|
try:
|
|
os.setgroups([]);os.setgid(1000);os.setuid(1000)
|
|
(root/'runtime-positive').write_text('ok')
|
|
try:(target/'forbidden').write_text('bad')
|
|
except PermissionError:pass
|
|
else:os._exit(10)
|
|
support.rename(root/'retained-support');support.mkdir();os._exit(0)
|
|
except BaseException:os._exit(11)
|
|
assert os.waitpid(pid,0)[1]==0
|
|
end=time.monotonic()+10
|
|
while time.monotonic()<end:
|
|
state=run('systemctl','show','c01-custody-consumer.service','-p','MainPID','--value').stdout.strip()
|
|
if state==b'0':break
|
|
time.sleep(.1)
|
|
else:raise AssertionError('custody loss did not retire dependent consumer')
|
|
failed=run('systemctl','start','archipelago-os-custody.service',check=False)
|
|
assert failed.returncode!=0 and marker.read_bytes()==b'never reset'
|
|
# Test-owned path restored by root with the original inode, then a new
|
|
# explicit start; no product search/adoption of renamed sources is allowed.
|
|
support.rmdir();(root/'retained-support').rename(support)
|
|
run('systemctl','reset-failed','archipelago-os-custody.service')
|
|
run('systemctl','start','archipelago-os-custody.service')
|
|
result={'passed':True,'actual_systemd_owner':True,'mount_state_recovered_in_new_process':True,
|
|
'uid1000_substitution_revoked_owner':True,'dependent_consumer_retired':True,'existing_journal_preserved':True,
|
|
'original_data_uid_gid':[root.stat().st_uid,root.stat().st_gid],'objects':recovered['objects'],
|
|
'scope':'disposable PID1 data-only coordinator integration; no installed private data'}
|
|
dump(c['EVIDENCE']/'durable-os-custody.json',result)
|
|
return result
|