#!/usr/bin/env python3 """Assemble an Alpine-derived, shell-free runtime from an explicit ELF allowlist.""" import os from pathlib import Path import shutil import subprocess import sys root = Path(sys.argv[1]) root.mkdir(parents=True, exist_ok=True) copied = set() def copy(source): source = Path(os.path.normpath(source)) if source in copied: return copied.add(source) target = root / str(source).lstrip('/') target.parent.mkdir(parents=True, exist_ok=True) if source.is_symlink(): link = os.readlink(source) if not target.is_symlink(): target.symlink_to(link) # Preserve every hop, not just the final file in a multi-link chain. copy(link if os.path.isabs(link) else source.parent / link) elif source.is_dir(): target.mkdir(exist_ok=True) for child in source.iterdir(): copy(child) else: shutil.copy2(source, target) for executable in sys.argv[2:]: result = subprocess.run(['lddtreepax', '-l', executable], check=True, text=True, capture_output=True) paths = result.stdout.splitlines() if not paths or any(not p.startswith('/') or not Path(p).exists() for p in paths): raise SystemExit(f'Unresolved runtime dependency for {executable}: {result.stdout}') copy(executable) for path in paths: copy(path) # musl uses this file for nonstandard library directories (for example Lua). for search_path in Path('/etc').glob('ld-musl-*.path'): copy(search_path) # Postfix loads database maps and SASL mechanisms dynamically. for folder in ['/usr/lib/postfix', '/usr/lib/sasl2']: for plugin in Path(folder).glob('*.so*'): result = subprocess.run(['lddtreepax', '-l', str(plugin)], check=True, text=True, capture_output=True, env={**os.environ, 'LD_LIBRARY_PATH': '/lib:/usr/lib:/usr/lib/postfix'}) for dependency in result.stdout.splitlines(): if not dependency.startswith('/') or not Path(dependency).exists(): raise SystemExit(f'Unresolved plugin dependency: {dependency}') copy(dependency) copy(plugin) for data in ['/etc/postfix', '/etc/passwd', '/etc/group', '/etc/aliases', '/etc/aliases.lmdb', '/etc/ssl/certs/ca-certificates.crt', '/etc/ssl/cert.pem', '/etc/ssl/openssl.cnf', '/usr/lib/ossl-modules', '/usr/share/zoneinfo', '/usr/share/icu', '/usr/lib/icu', '/etc/services', '/etc/protocols', '/etc/alpine-release', '/etc/os-release']: if Path(data).exists(): copy(data) for directory in ['tmp', 'root', 'dev', 'var/mail', 'var/lib/postfix', 'var/spool/postfix']: (root / directory).mkdir(parents=True, exist_ok=True) os.chmod(root / 'tmp', 0o1777) # Preserve the Postfix command group and setgid bits for queue submission. for command in ['postdrop', 'postqueue', 'postlog']: source = Path('/usr/sbin') / command target = root / str(source).lstrip('/') stat = source.stat() os.chown(target, stat.st_uid, stat.st_gid) os.chmod(target, stat.st_mode & 0o7777) for forbidden in ['bin/sh', 'bin/bash', 'bin/busybox', 'sbin/apk', 'usr/bin/apk', 'usr/bin/python3', 'usr/bin/go', 'usr/sbin/postfix']: assert not (root / forbidden).exists(), f'Unexpected runtime tool: {forbidden}'