351 lines
25 KiB
Python
351 lines
25 KiB
Python
#!/usr/bin/python3
|
|
"""Private PID1 network composition: real gate, DNS-SD, Podman and relay.
|
|
|
|
The native executable is a test binary running the real gate/DNS-SD components;
|
|
this fixture does not claim full C01 time/history or installed-node acceptance.
|
|
"""
|
|
import ctypes,hashlib,http.client,json,os,shutil,signal,socket,sqlite3,ssl,struct,subprocess,sys,time
|
|
from pathlib import Path
|
|
ROOT=Path('/opt/network-fixture')
|
|
sys.path.insert(0,str(ROOT/'scripts'))
|
|
import c01_network_owner as network
|
|
import c01_network_client as client
|
|
import c01_network_plan as plan
|
|
|
|
def run(*args,check=True,timeout=60):
|
|
r=subprocess.run(args,capture_output=True,text=True,timeout=timeout)
|
|
if check and r.returncode:raise RuntimeError(f'{args[0]} exit={r.returncode}: {r.stderr}')
|
|
return r.stdout
|
|
def put(path,text,mode=0o644):
|
|
path=Path(path);path.parent.mkdir(parents=True,exist_ok=True);path.write_text(text);path.chmod(mode)
|
|
def wait(fn,message,seconds=45):
|
|
end=time.monotonic()+seconds
|
|
while time.monotonic()<end:
|
|
try:
|
|
result=fn()
|
|
if result:return result
|
|
except (FileNotFoundError,ConnectionError,OSError,ValueError,RuntimeError):pass
|
|
time.sleep(.1)
|
|
raise RuntimeError(message)
|
|
def user(*args):
|
|
# Even image import may initialize Podman's retained rootless namespace.
|
|
# Every runtime command must start in the appointed runtime, not fixture host.
|
|
return run('nsenter','--net=/run/netns/archipelago-runtime','--','runuser','-u','archipelago','--','env','XDG_RUNTIME_DIR=/run/user/1000','DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus',*args)
|
|
|
|
def supervisor():
|
|
import c01_network_client
|
|
group=Path('/sys/fs/cgroup/system.slice/archipelago.service/c01-application');group.mkdir()
|
|
io_group=group.with_name('c01-host-io');io_group.mkdir()
|
|
host_listener=socket.socket(socket.AF_UNIX,socket.SOCK_SEQPACKET)
|
|
host_listener.bind('/run/c01-fixture-managed/host.sock');os.chown('/run/c01-fixture-managed/host.sock',61001,61001)
|
|
host_listener.listen(1);host_listener.settimeout(15)
|
|
def child_setup():
|
|
(group/'cgroup.procs').write_text(str(os.getpid()))
|
|
os.setgroups([]);os.setgid(61001);os.setuid(61001)
|
|
env=dict(os.environ,C01_NETWORK_FIXTURE='1',C01_DNS_ROLE='managed')
|
|
child=subprocess.Popen([str(ROOT/'native-test'),'--ignored','--exact','appgate::listener::network_fixture::serve','--nocapture'],env=env,preexec_fn=child_setup)
|
|
lease=None;io_child=None
|
|
try:
|
|
accepted,_=host_listener.accept()
|
|
assert struct.unpack('3i',accepted.getsockopt(socket.SOL_SOCKET,socket.SO_PEERCRED,12))==(child.pid,61001,61001)
|
|
put('/run/c01-fixture-hostio/peer.json',json.dumps({'pid':child.pid,'uid':61001,'gid':61001}),0o600)
|
|
accepted_fd=accepted.fileno()
|
|
def io_setup():
|
|
(io_group/'cgroup.procs').write_text(str(os.getpid()))
|
|
network.namespaces.enter_host_worker(ctypes.CDLL(None,use_errno=True))
|
|
os.dup2(accepted_fd,3,inheritable=True)
|
|
if accepted_fd!=3:os.close(accepted_fd)
|
|
io_child=subprocess.Popen([str(ROOT/'native-test'),'--ignored','--exact','host_access::tests::network_fixture_owner','--nocapture'],
|
|
env=dict(os.environ,C01_NETWORK_FIXTURE='1'),pass_fds=tuple({3,accepted_fd}),preexec_fn=io_setup)
|
|
accepted.close();host_listener.close()
|
|
wait(lambda:json.loads(Path('/run/c01-fixture-managed/ready.json').read_text()).get('pid')==child.pid,'native gate/DNS-SD readiness')
|
|
lease=c01_network_client.NetworkLease(runtime_uid=1000)
|
|
names=[name for name,route in lease.policy.routes.items() if route['target']=='managed']
|
|
end=time.monotonic()+40
|
|
while True:
|
|
if child.poll() is not None:raise RuntimeError('native fixture exited')
|
|
lease.register_managed_listeners(child.pid,names)
|
|
if all(lease.request({'method':'inspect','name':name}).get('producer') for name in names):break
|
|
if time.monotonic()>end:raise RuntimeError('managed producer registration deadline')
|
|
time.sleep(.1)
|
|
lease.require_targets(names)
|
|
put('/run/network-supervisor-ready.json',json.dumps({'pid':child.pid,'listeners':len(lease.listeners)}),0o600)
|
|
while child.poll() is None:
|
|
if io_child.poll() is not None:raise RuntimeError('host fact owner exited')
|
|
lease.check();time.sleep(.1)
|
|
finally:
|
|
if child.poll() is None:child.kill()
|
|
child.wait()
|
|
if io_child is not None:
|
|
if io_child.poll() is None:io_child.kill()
|
|
io_child.wait()
|
|
host_listener.close()
|
|
if lease:lease.close()
|
|
|
|
def echo_fips():
|
|
with socket.socket() as listener:
|
|
listener.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
|
|
listener.bind(('0.0.0.0',8443));listener.listen(16)
|
|
while True:
|
|
connection,_=listener.accept()
|
|
with connection:connection.sendall(b'fips-transport:'+connection.recv(1024))
|
|
|
|
def request(address,port,session=None,tls=False,path='/'):
|
|
raw=socket.create_connection((address,port),timeout=5)
|
|
if tls:
|
|
context=ssl.create_default_context(cafile='/run/c01-fixture-managed/cert.pem')
|
|
raw=context.wrap_socket(raw,server_hostname='localhost')
|
|
with raw:
|
|
headers=f'GET {path} HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n'
|
|
if session:headers+='Cookie: session='+session+'\r\n'
|
|
raw.sendall((headers+'\r\n').encode())
|
|
response=http.client.HTTPResponse(raw);response.begin()
|
|
return response.status,response.read(65536)
|
|
|
|
def main():
|
|
assert os.geteuid()==0 and socket.gethostname()=='c01-network-v4-fixture'
|
|
assert Path('/run/.containerenv').exists() and os.environ.get('C01_NETWORK_FIXTURE')=='1'
|
|
put('/run/c01-network-fixture','private-network-composition\n')
|
|
# Never write through a runtime-provided resolver bind mount. Detach only
|
|
# this disposable container's mount, then create its own resolver file.
|
|
if any(line.split()[4]=='/etc/resolv.conf' for line in Path('/proc/self/mountinfo').read_text().splitlines()):
|
|
run('umount','/etc/resolv.conf')
|
|
if Path('/etc/resolv.conf').is_symlink():Path('/etc/resolv.conf').unlink()
|
|
put('/etc/resolv.conf','nameserver 100.100.100.100\n')
|
|
run('ip','link','add','wan0','type','dummy');run('ip','link','set','wan0','up')
|
|
for address in ('192.0.2.2/24','192.0.2.3/32','198.51.100.2/32'):run('ip','address','add',address,'dev','wan0')
|
|
run('ip','-6','address','add','fd00:10::2/64','dev','wan0')
|
|
run('ip','-6','address','add','fd00:10::3/128','dev','wan0')
|
|
run('ip','route','add','default','via','192.0.2.1','dev','wan0','src','192.0.2.2')
|
|
run('ip','-6','route','add','default','via','fe80::ffff','dev','wan0')
|
|
run('ip','link','add','fips0','type','dummy');run('ip','link','set','fips0','up')
|
|
run('ip','-6','address','add','fd00:20::2/128','dev','fips0')
|
|
put('/etc/fips/fips.pub','79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798\n')
|
|
put('/etc/fips/fips.key','0'*63+'1\n',0o600)
|
|
put('/run/c01-fixture-hostio/data/.fixture','synthetic-empty-host-data\n',0o600)
|
|
put('/home/operator-fixture/private','synthetic-operator-canary\n',0o600)
|
|
put('/run/user/2000/private','synthetic-other-user-canary\n',0o600)
|
|
for role in ('managed','runtime'):
|
|
run('ip','netns','add','archipelago-'+role)
|
|
# Netlink setup needs only the network namespace. Unlike `ip -n`,
|
|
# nsenter does not attempt an unrelated sysfs remount in a user namespace.
|
|
run('nsenter','--net=/run/netns/archipelago-'+role,'ip','link','set','lo','up')
|
|
for name,uid in (('archipelago',1000),('c01-manager',61001)):
|
|
if not run('getent','passwd',name,check=False):run('useradd','--uid',str(uid),'--create-home',name)
|
|
run('systemctl','stop','user@1000.service',check=False)
|
|
run('usermod','-G','','-s','/usr/sbin/nologin','archipelago')
|
|
for name in ('subuid','subgid'):put('/etc/'+name,'archipelago:100000:65536\n')
|
|
for path in ('/var/lib/archipelago/containers/storage','/var/lib/archipelago/fixture-data','/run/c01-fixture-managed'):
|
|
run('install','-d','-o','61001' if path.endswith('managed') else '1000','-g','61001' if path.endswith('managed') else '1000','-m','0700',path)
|
|
put('/run/c01-fixture-host/.placeholder','')
|
|
put('/var/lib/archipelago/fixture-data/marker','backend-retained-marker\n')
|
|
marker=Path('/var/lib/archipelago/fixture-data/marker').stat().st_ino
|
|
run('install','-d','-o','1000','-g','1000','-m','0700','/home/archipelago/.config','/home/archipelago/.config/containers')
|
|
put('/home/archipelago/.config/containers/storage.conf','[storage]\ndriver="vfs"\nrunroot="/run/user/1000/containers"\ngraphroot="/var/lib/archipelago/containers/storage"\n')
|
|
put('/home/archipelago/.config/containers/containers.conf','[containers]\nkeyring=false\n')
|
|
for source in (ROOT/'scripts').glob('*.py'):put('/usr/lib/archipelago/'+source.name,source.read_text())
|
|
for name in ('archipelago-c01-network.service','archipelago-c01-network-provision.service','archipelago-c01-kiosk-endpoints.service'):
|
|
put('/etc/systemd/system/'+name,(ROOT/'configs'/name).read_text())
|
|
shutil.copyfile(ROOT/'nostr-rs-relay','/usr/local/bin/nostr-rs-relay');Path('/usr/local/bin/nostr-rs-relay').chmod(0o755)
|
|
run('install','-d','-o','1000','-g','1000','-m','0700','/var/lib/archipelago/nostr-relay')
|
|
put('/var/lib/archipelago/nostr-relay/config.toml',(ROOT/'configs/nostr-relay-config.toml').read_text())
|
|
put('/etc/systemd/user/archipelago-runtime-nostr-relay.service',(ROOT/'configs/archipelago-runtime-nostr-relay.service').read_text())
|
|
put('/etc/systemd/system/user@1000.service.d/network.conf','[Service]\nNetworkNamespacePath=/run/netns/archipelago-runtime\n')
|
|
put('/etc/systemd/system/archipelago-fips.service',f'[Service]\nExecStart=/usr/bin/python3 {ROOT}/driver.py fips\n')
|
|
runtime=dict(runtime_user='archipelago',runtime_uid=1000,runtime_gid=1000,manager_uid=61001,manager_gid=61001,
|
|
user_cgroup='/user.slice/user-1000.slice/user@1000.service')
|
|
put('/etc/archipelago-c01/runtime.json',json.dumps(runtime),0o600)
|
|
# Import is filesystem-only; no installed-node store or network is used.
|
|
run('systemctl','daemon-reload');run('systemctl','start','user-runtime-dir@1000.service')
|
|
# Fixture creation needs the real user manager for enabled-mode cgroups.
|
|
# No container workload is started until the owned uplinks are ready.
|
|
run('systemctl','start','user@1000.service')
|
|
image=user('podman','import',str(ROOT/'backend.tar'),'localhost/network-fixture:current').strip().splitlines()[-1].removeprefix('sha256:')
|
|
assert len(image)==64,image
|
|
retained_id=user('podman','--cgroup-manager=systemd','create','--name','fixture-retained','--network=host',
|
|
'--cgroups=enabled','--cgroup-parent=user.slice','--security-opt=no-new-privileges',image,'/server','127.0.0.1','19005').strip().splitlines()[-1]
|
|
retained_unit='archipelago-retained-fixture-'+retained_id+'.service'
|
|
with sqlite3.connect('file:/var/lib/archipelago/containers/storage/db.sql?mode=ro',uri=True) as db:
|
|
raw=db.execute('SELECT JSON FROM ContainerConfig WHERE ID=?',(retained_id,)).fetchone()[0]
|
|
retained_digest=hashlib.sha256(json.dumps(json.loads(raw),sort_keys=True,separators=(',',':'),ensure_ascii=False).encode()).hexdigest()
|
|
put('/etc/systemd/user/'+retained_unit,f'''[Service]
|
|
Type=exec
|
|
Delegate=yes
|
|
Restart=no
|
|
KillMode=mixed
|
|
SuccessExitStatus=137 143
|
|
ExecStart=/usr/bin/podman start --attach {retained_id}
|
|
ExecStop=/usr/bin/podman stop --ignore --time 1 {retained_id}
|
|
''')
|
|
manifests=[];routes=[]
|
|
def route(name,source,target,port,addresses,target_address,target_port,uid,group,doc=None):
|
|
projection=None
|
|
if doc:
|
|
if doc not in manifests:manifests.append(doc)
|
|
projection=network.digest(network.canonical(doc))
|
|
routes.append(dict(name=name,source=source,target=target,protocol='udp' if '/udp/' in name else 'tcp',port=port,
|
|
source_addresses=addresses,target_address=target_address,target_port=target_port,target_uid=uid,target_cgroup=group,
|
|
app_id=doc['app_id'] if doc else None,projection_sha256=projection))
|
|
appgroup='/system.slice/archipelago.service/c01-application'
|
|
gate_doc=dict(app_id='fixture',manifest_sha256='a'*64,loopback_ports=[['tcp',18001]],target_cgroup=appgroup)
|
|
# >253 real source sockets exercises the complete production lease protocol.
|
|
for n in range(17):route(f'gate/fixture/tcp/{18001+n}/host','host','managed',18001+n,
|
|
[f'127.20.{n}.{i}' for i in range(1,17)],'127.0.0.2',18001,61001,appgroup,gate_doc)
|
|
route('gate/fixture/tcp/19001/host','host','managed',19001,['192.0.2.2','fd00:10::2'],'127.0.0.2',18001,61001,appgroup,gate_doc)
|
|
for name,port,address in (('fixture',18001,'127.0.0.1'),('fixture-alias',19003,'198.51.100.2')):
|
|
group=runtime['user_cgroup']+'/app.slice/'+name+'.service'
|
|
source='managed' if name=='fixture' else 'host'
|
|
bindings=[source,'tcp',port,'127.0.0.1' if source=='managed' else address]
|
|
doc=dict(app_id=name,manifest_sha256='b'*64,loopback_ports=[['tcp',port]],target_cgroup=group,
|
|
source_bindings=[bindings],target_addresses=[address],producer={'kind':'container-payload','container_name':name,'image_id':image})
|
|
mapped=name=='fixture-alias'
|
|
if mapped:doc['producer']['payload_principal']=[100099,100100]
|
|
route(f'app/{name}/tcp/{port}/{source}',source,'runtime',port,[bindings[3]],address,port,100099 if mapped else 1000,group,doc)
|
|
put('/etc/systemd/user/'+name+'.service',f'''[Service]
|
|
ExecStart=/usr/bin/podman run --pull=never --name={name} {'--user=100:101' if mapped else ''} --network=host --cgroups=split --security-opt=no-new-privileges -v /var/lib/archipelago/fixture-data:/data:ro {image} /server {address} {port}
|
|
Delegate=yes
|
|
ExecStop=/usr/bin/podman stop --time=1 {name}
|
|
ExecStopPost=-/usr/bin/podman rm {name}
|
|
Restart=no
|
|
''')
|
|
route('fips-tcp6','host','host',8443,['::'],'127.0.0.1',8443,0,'/system.slice/archipelago-fips.service')
|
|
retained_group=runtime['user_cgroup']+'/app.slice/'+retained_unit
|
|
retained_doc=dict(app_id='retained-fixture',manifest_sha256=retained_digest,loopback_ports=[['tcp',19005]],target_cgroup=retained_group,
|
|
source_bindings=[['host','tcp',19005,'127.0.0.1']],target_addresses=['127.0.0.1'],
|
|
producer=dict(kind='retained-container-v1',unit=retained_unit,container_id=retained_id,image_id=image,
|
|
config_sha256=retained_digest,scope_kind='bound-libpod-scope',payload_principal=[1000,1000]))
|
|
route('app/retained-fixture/tcp/19005/host','host','runtime',19005,['127.0.0.1'],'127.0.0.1',19005,1000,retained_group,retained_doc)
|
|
relay_group=runtime['user_cgroup']+'/app.slice/archipelago-runtime-nostr-relay.service'
|
|
relay_doc=dict(app_id='nostr-relay',manifest_sha256=hashlib.sha256((ROOT/'configs/archipelago-runtime-nostr-relay.service').read_bytes()).hexdigest(),
|
|
loopback_ports=[['tcp',7777]],target_cgroup=relay_group,source_bindings=[['host','tcp',7777,'0.0.0.0'],['host','tcp',7777,'::']],
|
|
target_addresses=['127.0.0.1'],producer={'kind':'exact'})
|
|
route('app/nostr-relay/tcp/7777/host','host','runtime',7777,['0.0.0.0','::'],'127.0.0.1',7777,1000,relay_group,relay_doc)
|
|
for family,address,target in ((4,'0.0.0.0','127.0.0.1'),(6,'::','::1')):
|
|
route(f'native/mdns{family}/udp/5353/host','host','managed',5353,[address],target,5353,61001,appgroup)
|
|
route('native/website-fips/tcp/32000/host','host','managed',32000,['fd00:20::2'],'127.0.0.1',32000,61001,appgroup)
|
|
route('native/website-tor/tcp/32100/host','host','managed',32100,['127.0.0.1'],'127.0.0.1',32100,61001,appgroup)
|
|
route('native/peer/tcp/5679/host','host','managed',5679,['fd00:20::2'],'127.0.0.1',5679,61001,appgroup)
|
|
namespaces={role:list(network.namespaces.process_namespace(1) if role=='host' else network.namespaces.appointed(role)) for role in ('host','managed','runtime')}
|
|
pasta=Path('/usr/bin/pasta.avx2')
|
|
policy=dict(version=4,namespaces=namespaces,manifests=manifests,routes=routes,
|
|
uplink=dict(executable=str(pasta),sha256=hashlib.sha256(pasta.read_bytes()).hexdigest(),wan=['wan0','192.0.2.1','192.0.2.2'],
|
|
dns=['1.1.1.1'],routing='host-dual-stack',dns_host='100.100.100.100',runtime_addresses=['198.51.100.2']),
|
|
mdns=dict(interface='wan0',ifindex=socket.if_nametoindex('wan0'),ipv4='192.0.2.2'))
|
|
print(json.dumps(plan.validate(policy,runtime)),flush=True)
|
|
put(network.CONFIG,network.canonical(policy).decode(),0o600)
|
|
put('/etc/archipelago-c01/kiosk-endpoints.json',json.dumps(dict(version=1,policy_sha256=network.digest(network.canonical(policy)),
|
|
executables={'/usr/local/bin/nostr-rs-relay':hashlib.sha256(Path('/usr/local/bin/nostr-rs-relay').read_bytes()).hexdigest()},
|
|
routes=['app/nostr-relay/tcp/7777/host'])),0o600)
|
|
put('/etc/systemd/system/archipelago.service',f'''[Service]
|
|
Type=exec
|
|
ExecStart=/usr/bin/python3 {ROOT}/driver.py supervisor
|
|
NetworkNamespacePath=/run/netns/archipelago-managed
|
|
Delegate=yes
|
|
DelegateSubgroup=supervisor
|
|
NoNewPrivileges=yes
|
|
AmbientCapabilities=CAP_SETUID CAP_SETGID
|
|
KillMode=control-group
|
|
TimeoutStopSec=5
|
|
''')
|
|
put('/etc/systemd/system/fixture-mdns-host.service',f'''[Service]
|
|
Environment=C01_NETWORK_FIXTURE=1 C01_DNS_ROLE=host
|
|
ExecStart={ROOT}/native-test --ignored --exact appgate::listener::network_fixture::serve --nocapture
|
|
KillMode=control-group
|
|
''')
|
|
run('openssl','req','-x509','-newkey','rsa:2048','-nodes','-keyout','/run/c01-fixture-managed/key.pem',
|
|
'-out','/run/c01-fixture-managed/cert.pem','-days','1','-subj','/CN=localhost','-addext','subjectAltName=DNS:localhost')
|
|
for name in ('key.pem','cert.pem'):os.chown('/run/c01-fixture-managed/'+name,61001,61001)
|
|
run('systemctl','daemon-reload');run('systemctl','start','archipelago-fips.service','archipelago-c01-network.service')
|
|
run('systemctl','start','user@1000.service');user('systemctl','--user','daemon-reload')
|
|
user('systemctl','--user','start','fixture.service','fixture-alias.service','archipelago-runtime-nostr-relay.service',retained_unit)
|
|
for name,app_id in (('fixture','fixture'),('fixture-alias','fixture-alias'),('fixture-retained','retained-fixture')):
|
|
pid=wait(lambda:int(user('podman','inspect','--format','{{.State.Pid}}',name).strip()),'actual payload PID')
|
|
actual=network.process(pid)
|
|
if name=='fixture-alias':assert actual['uids']==[100099]*4 and actual['gids']==[100100]*4,actual
|
|
assert list(actual['net'])==policy['namespaces']['runtime'],{'container':name,'process':actual,'expected_runtime':policy['namespaces']['runtime']}
|
|
print(json.dumps({'payload':name,'process':actual}),flush=True)
|
|
observed_policy=network.Policy()
|
|
try:
|
|
declared=next(r for r in observed_policy.routes.values() if r['app_id']==app_id and r['target']=='runtime')
|
|
observations=[]
|
|
for entry in Path(f'/proc/{pid}/fd').iterdir():
|
|
if not os.readlink(entry).startswith('socket:['):continue
|
|
try:
|
|
pinned=network.Target(observed_policy,declared,pid,int(entry.name))
|
|
pinned.close();observations.append({'fd':entry.name,'qualified':True})
|
|
except (OSError,ValueError) as error:observations.append({'fd':entry.name,'refusal':str(error)})
|
|
print(json.dumps({'payload_socket_admission':name,'observations':observations}),flush=True)
|
|
finally:observed_policy.close()
|
|
run('systemctl','start','archipelago-c01-network-provision.service','archipelago-c01-kiosk-endpoints.service','fixture-mdns-host.service','archipelago.service')
|
|
assert Path('/home/operator-fixture/private').read_text()=='synthetic-operator-canary\n'
|
|
assert Path('/run/user/2000/private').read_text()=='synthetic-other-user-canary\n'
|
|
for unit in ('archipelago-c01-network.service','archipelago-c01-network-provision.service'):
|
|
pid=int(run('systemctl','show',unit,'--property=MainPID','--value').strip());assert pid>1
|
|
run('nsenter','--target',str(pid),'--mount','--root','--wd=/','--','python3','-c',
|
|
"from pathlib import Path; assert Path('/run/user/1000/systemd/private').is_socket(); assert not Path('/home/operator-fixture/private').exists(); assert not Path('/run/user/2000/private').exists()")
|
|
wait(lambda:Path('/run/network-supervisor-ready.json').exists(),'complete native listener registration')
|
|
authentication_diagnostic=[]
|
|
def authenticated():
|
|
session=json.loads(Path('/run/c01-fixture-managed/ready.json').read_text())['fixture_session']
|
|
status,body=request('192.0.2.2',19001,session)
|
|
if not authentication_diagnostic:
|
|
authentication_diagnostic.append(True)
|
|
print(json.dumps({'first_gate_response':status,'body_prefix':body[:160].decode(errors='replace')}),flush=True)
|
|
return session if status==200 and b'backend-retained-marker' in body else None
|
|
session=wait(authenticated,'authenticated gate reaches actual runtime container')
|
|
for address in ('192.0.2.2','fd00:10::2'):
|
|
for tls in (False,True):
|
|
status,body=request(address,19001,tls=tls)
|
|
assert status==401 and b'backend-retained-marker' not in body and b'archipelago' in body.lower(),(status,body[:100])
|
|
status,body=request(address,19001,session,tls)
|
|
assert status==200 and b'backend-retained-marker' in body,(status,body[:100])
|
|
for role in ('host','managed'):
|
|
seen=wait(lambda:json.loads(Path(f'/run/c01-fixture-{role}/seen.json').read_text()),'genuine DNS-SD '+role)
|
|
assert seen['discovered']!=role and seen['addresses']
|
|
print(json.dumps({'dns_sd_role':role,**seen}),flush=True)
|
|
assert b'backend-retained-marker' in request('198.51.100.2',19003)[1]
|
|
user('systemctl','--user','restart','fixture-alias.service')
|
|
wait(lambda:b'backend-retained-marker' in request('198.51.100.2',19003)[1],'mapped non-root payload recreation and registration')
|
|
assert b'website-fips-marker' in request('fd00:20::2',32000)[1]
|
|
assert b'website-tor-marker' in request('127.0.0.1',32100)[1]
|
|
assert request('fd00:20::2',5679,path='/health')[0]==200
|
|
assert request('fd00:20::2',5679,path='/api/rpc')[0]==403
|
|
wait(lambda:Path('/run/c01-fixture-managed/gate-facts.json').exists(),'actual authenticated gate public facts')
|
|
facts=json.loads(Path('/run/c01-fixture-managed/host-facts.json').read_text())
|
|
assert facts['address']=='fd00:20::2' and facts['service_state']=='active'
|
|
for address in ('127.0.0.1','::1'):
|
|
def relay_ready():
|
|
with socket.create_connection((address,7777),timeout=3) as stream:
|
|
stream.sendall(b'GET / HTTP/1.1\r\nHost: localhost\r\nAccept: application/nostr+json\r\nConnection: close\r\n\r\n')
|
|
response=http.client.HTTPResponse(stream);response.begin()
|
|
return response.status==200 and b'supported_nips' in response.read(65536)
|
|
wait(relay_ready,'real relay IPv4/IPv6 publication')
|
|
with socket.create_connection(('::1',8443),timeout=5) as stream:
|
|
stream.sendall(b'opaque-noise-transport');assert stream.recv(1024)==b'fips-transport:opaque-noise-transport'
|
|
before=user('podman','inspect','--format','{{.Id}}','fixture').strip()
|
|
user('systemctl','--user','restart','fixture.service')
|
|
def recreated_id():
|
|
current=user('podman','inspect','--format','{{.Id}}','fixture').strip()
|
|
return current if current!=before else None
|
|
after=wait(recreated_id,'actual recreated container identity')
|
|
assert before!=after
|
|
wait(authenticated,'actual recreated payload re-registration')
|
|
assert Path('/var/lib/archipelago/fixture-data/marker').stat().st_ino==marker
|
|
wait(lambda:b'backend-retained-marker' in request('127.0.0.1',19005)[1],'exact retained-ID external-scope publication')
|
|
retained_before=user('podman','inspect','--format','{{.Id}}','fixture-retained').strip()
|
|
user('systemctl','--user','restart',retained_unit)
|
|
wait(lambda:b'backend-retained-marker' in request('127.0.0.1',19005)[1],'retained keeper invocation re-registration')
|
|
assert user('podman','inspect','--format','{{.Id}}','fixture-retained').strip()==retained_before==retained_id
|
|
print(json.dumps({'passed':True,'real_gate_http_tls_ipv4_ipv6':True,'actual_podman_recreation':True,
|
|
'mapped_nonroot_payload_recreation':True,
|
|
'data_inode_preserved':True,'private_alias':True,'fips_transport_ipv6':True,'exact_id_external_scope_restart':True,
|
|
'observer_user_bus_visible_operator_homes_hidden':True,
|
|
'complete_listener_lease':json.loads(Path('/run/network-supervisor-ready.json').read_text())['listeners'],
|
|
'seams':['isolated fake WAN','test-only native process, not time/history admission','synthetic backend image','opaque FIPS transport echo']}),flush=True)
|
|
|
|
if __name__=='__main__':
|
|
if sys.argv[1:] == ['supervisor']:supervisor()
|
|
elif sys.argv[1:] == ['fips']:echo_fips()
|
|
elif not sys.argv[1:]:main()
|
|
else:raise ValueError('fixed fixture modes only')
|