#!/usr/bin/python3 """Genuine FIPS/WG/Tor/NM startup in the disposable PID1 network realm. Uses captured public executables/dependencies and new synthetic keys only. Never reads installed-node private keys or changes physical host networking. """ import hashlib import json import os from pathlib import Path import pwd import secrets import socket import struct import time def install(c): assert os.geteuid()==0 and Path('/run/.containerenv').exists() assert os.uname().nodename=='c01-prerequisite-qualification' and os.environ.get('C01_RUNNING_SERVICES')=='1' run,write,dump,sha=c['run'],c['write'],c['dump'],c['sha'] package,evidence,policy=c['PACKAGE'],c['EVIDENCE'],c['policy'] import c01_admission as admission import c01_network_owner as network stack=json.loads((package/'service-stack-manifest.json').read_text())['files'] for executable in ('usr/bin/fips','usr/bin/tor','usr/bin/wg','usr/sbin/NetworkManager'): assert sha('/'+executable)==stack[executable]['sha256'], 'actual installed public program bytes required' launch,_=policy.json_read(policy.LAUNCH) run('systemctl','stop','NetworkManager.service','tor@default.service','tor.service') identity=Path(launch['runtime_data_dir'] if os.environ.get('C01_LAYOUT_MIGRATION')=='1' else launch['manager_backing'])/'identity' secret=secrets.token_bytes(32) der=bytes.fromhex('302e0201010420')+secret+bytes.fromhex('a00706052b8104000a') public=run('openssl','ec','-inform','DER','-pubout','-conv_form','compressed','-outform','DER',input=der).stdout[-33:][1:] assert len(public)==32 and list(public)!=launch['binding']['local_identity'] for name,data,mode in (('fips_key',secret.hex(),0o600),('fips_key.pub',public,0o644)): assert not (identity/name).exists();write(identity/name,data,mode);os.chown(identity/name,1000,1000) write('/etc/fips/fips.key',secret.hex(),0o600);write('/etc/fips/fips.pub',public,0o644) write('/etc/fips/fips.yaml','''node: identity: persistent: true discovery: lan: enabled: false tun: enabled: true name: fips0 mtu: 1280 dns: enabled: true bind_addr: 127.0.0.1 transports: udp: bind_addr: 127.0.0.1:8668 tcp: bind_addr: 127.0.0.1:8668 peers: [] ''',0o644) c['unit']('archipelago-fips.service',(package/'image-recipe/configs/archipelago-fips.service').read_text()) write('/etc/fips/fips.nft','''table inet fips { chain input { type filter hook input priority 0; policy accept; iifname "fips0" jump fips_in; } chain fips_in { ct state established,related accept; include "/etc/fips/fips.d/*.nft" drop; } } ''',0o644) for filename in ('80-web-ui.nft','85-app-ports.nft','90-ssh.nft'): write('/etc/fips/fips.d/'+filename,'# Synthetic preserved FIPS policy: no public inbound ports.\n',0o644) run('/usr/sbin/nft','--check','--file','/etc/fips/fips.nft') run('/usr/sbin/nft','--file','/etc/fips/fips.nft') private=run('wg','genkey').stdout Path('/etc/wireguard').mkdir(mode=0o700,exist_ok=True) write('/etc/wireguard/c01-fixture.key',private,0o600) write('/etc/archipelago-fips-wg/wg.public',run('wg','pubkey',input=private).stdout,0o644) c['unit']('archipelago-wg.service','''[Service] Type=oneshot RemainAfterExit=yes ExecStart=/usr/bin/ip link add wg0 type wireguard ExecStart=/usr/bin/wg set wg0 private-key /etc/wireguard/c01-fixture.key ExecStart=/usr/bin/ip address add 10.44.0.1/24 dev wg0 ExecStart=/usr/bin/ip link set wg0 up ExecStop=/usr/bin/ip link delete wg0 NoNewPrivileges=yes CapabilityBoundingSet=CAP_NET_ADMIN ''') tor_account=pwd.getpwnam('debian-tor') assert tor_account.pw_uid not in (0,1000,1001,61001,61002) os.chown('/var/lib/tor',tor_account.pw_uid,tor_account.pw_gid) hidden=Path('/var/lib/tor/hidden_service_archipelago') hidden.mkdir(mode=0o700,exist_ok=True) assert not list(hidden.iterdir()),'fixture must not replace hidden-service keys' os.chown(hidden,tor_account.pw_uid,tor_account.pw_gid);hidden.chmod(0o700) Path('/run/tor').mkdir(mode=0o700,exist_ok=True);os.chown('/run/tor',tor_account.pw_uid,tor_account.pw_gid) write('/etc/tor/torrc','''User debian-tor DataDirectory /var/lib/tor SocksPort 127.0.0.1:9050 ControlSocket /run/tor/c01-fixture-control CookieAuthentication 1 CookieAuthFile /run/tor/c01-fixture-cookie RunAsDaemon 0 HiddenServiceDir /var/lib/tor/hidden_service_archipelago HiddenServicePort 80 127.0.0.2:80 ''',0o644) commands={'fips':['/usr/bin/fips','--config','/etc/fips/fips.yaml'], 'tor':['/usr/bin/tor','-f','/etc/tor/torrc','--RunAsDaemon','0'], 'network_mutations':['/usr/sbin/NetworkManager','--no-daemon']} write('/usr/lib/systemd/system/tor@default.service','''[Unit] After=network.target [Service] Type=simple ExecStart=/usr/bin/tor -f /etc/tor/torrc --RunAsDaemon 0 Restart=no NoNewPrivileges=yes ''',0o644) write('/etc/NetworkManager/NetworkManager.conf','''[main] plugins=ifupdown,keyfile no-auto-default=* [ifupdown] managed=false [device-fixture] match-device=interface-name:* managed=0 ''',0o600) write('/usr/lib/systemd/system/NetworkManager.service','''[Unit] After=dbus.service Requires=dbus.service [Service] Type=dbus BusName=org.freedesktop.NetworkManager ExecStart=/usr/sbin/NetworkManager --no-daemon Restart=no NoNewPrivileges=yes CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW ''',0o644) run('systemctl','daemon-reload') run('systemctl','start','archipelago-fips.service','archipelago-wg.service','tor@default.service','NetworkManager.service') end=time.monotonic()+180;last='not started' while time.monotonic()=12 and answer[:2]==query[:2] and answer[2]&0x80 # Keep the production runtime guard. Reappoint the network owner before any # access/application lease exists; existing producer identities are measured. run('systemctl','stop','archipelago-c01-network.service') network_value=json.loads(network.CONFIG.read_text()) for name,unit,protocol,port in (('fips-dns','archipelago-fips.service','udp',5354),('tor-socks','tor@default.service','tcp',9050)): info=dict(line.split('=',1) for line in run('systemctl','show',unit,'-p','MainPID','-p','ControlGroup').stdout.decode().splitlines()) pid=int(info['MainPID']);assert pid>1 uid=int(next(line for line in Path(f'/proc/{pid}/status').read_text().splitlines() if line.startswith('Uid:')).split()[1]) network_value['routes'].append(dict(name=name,source='managed',target='host',protocol=protocol,port=port, app_id=None,projection_sha256=None,target_cgroup=info['ControlGroup'],target_uid=uid)) dump(network.CONFIG,network_value) run('systemctl','start','archipelago-c01-network.service') plan=json.loads(Path('/etc/archipelago-fips-wg/plan.json').read_text()) plan.update(fips='running',wg='running',assets={name:sha(path) for name,path in admission.FIPS_ASSETS.items()}, programs={name:sha(admission.ASSETS[name]) for name in ('wg','systemctl')}) dump('/etc/archipelago-fips-wg/plan.json',plan) assets=dict(admission.ASSETS) for mapping in admission.SERVICE_ASSETS.values():assets.update(mapping) contract={'version':2,'platform_sha256':launch['platform_profile_sha256'],'roles':sorted(admission.ROLES), 'assets':{name:sha(path) for name,path in assets.items()},'services':{name:'running' for name in admission.UNITS}, 'optional_unavailable':[],'required_routes':['management','fips-dns','tor-socks'],'fips_public_key':public.hex(), 'service_argv_sha256':{name:hashlib.sha256(b'\0'.join(x.encode() for x in argv)+b'\0').hexdigest() for name,argv in commands.items()}} contract['service_argv_sha256']['wg']=None admission.validate(contract,launch) raw=(json.dumps(contract,sort_keys=True,indent=2)+'\n').encode();digest=hashlib.sha256(raw).hexdigest() write(admission.POLICIES/(digest+'.json'),raw,0o600) launch['admission_sha256']=digest;dump(policy.LAUNCH,launch) held=admission.Contract(launch) try:held.check();states=held.initial finally:held.close() result={'scope':'genuine running-service startup; synthetic keys and private network only', 'policy_sha256':digest,'services':states,'tor_bootstrap':last,'fips_public':public.hex(), 'node_public':bytes(launch['binding']['local_identity']).hex(),'programs':{p:sha('/'+p) for p in ('usr/bin/fips','usr/bin/tor','usr/bin/wg','usr/sbin/NetworkManager')}} dump(evidence/'running-services.json',result) dump(evidence/'conditional-admission.json',{'policy_sha256':digest,'optional_unavailable':[], 'scope':'running-service startup; data/migration and operational mutation qualification recorded separately'}) return result