"""Root operator file installation, independent of migrations and service start.""" import os import re import secrets import shutil import stat import subprocess from pathlib import Path from . import manifest, preflight from .policy import DeliveryError, canonical, digest, read_json, sha256 RELEASES = Path("/opt/kf-onlinejudge/releases") CURRENT = Path("/opt/kf-onlinejudge/current") IMAGES = Path("/var/lib/kf-judge/images") LEGACY_HEADER = {"image_kind": "linux_gnu_cxx_x86_64_v1", "architecture": "x86_64", "revision": 1} def directory(path: Path, mode: int) -> None: if not path.exists() and not path.is_symlink(): preflight.protected(path.parent, directory=True) path.mkdir(mode=mode) preflight.protected(path, directory=True) if stat.S_IMODE(path.stat().st_mode) != mode: raise DeliveryError("existing directory mode differs; operator review required") def exclusive_file(path: Path, data: bytes, mode: int) -> None: preflight.protected(path.parent, directory=True) if path.exists() or path.is_symlink(): preflight.protected(path) if stat.S_IMODE(path.stat().st_mode) != mode or path.read_bytes() != data: raise DeliveryError("existing installed file differs; refused overwrite") return fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY | os.O_NOFOLLOW, mode) try: with os.fdopen(fd, "wb", closefd=False) as output: output.write(data) output.flush() os.fsync(fd) finally: os.close(fd) sync_directory(path.parent) def sync_directory(path: Path) -> None: fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW) try: os.fsync(fd) finally: os.close(fd) def copy_verified(source: Path, target: Path, expected: str, mode: int) -> None: preflight.protected(source) if digest(source) != sha256(expected): raise DeliveryError("source changed after bundle verification") if target.exists() or target.is_symlink(): preflight.protected(target) if digest(target) != expected or stat.S_IMODE(target.stat().st_mode) != mode: raise DeliveryError("existing release differs; refused overwrite") return preflight.protected(target.parent, directory=True) incoming = os.open(source, os.O_RDONLY | os.O_NOFOLLOW) outgoing = os.open(target, os.O_CREAT | os.O_EXCL | os.O_WRONLY | os.O_NOFOLLOW, 0o600) try: with ( os.fdopen(incoming, "rb", closefd=False) as src, os.fdopen(outgoing, "wb", closefd=False) as dst, ): shutil.copyfileobj(src, dst, 1024 * 1024) dst.flush() os.fsync(outgoing) if digest(target) != expected: raise DeliveryError("installed bytes differ; incomplete release retained") os.fchmod(outgoing, mode) finally: os.close(incoming) os.close(outgoing) def ensure_accounts() -> None: if preflight.identity_conflicts(): raise DeliveryError("OS identity conflicts") import grp import pwd for name, identity in preflight.ACCOUNTS.items(): try: grp.getgrnam(name) except KeyError: subprocess.run( ["/usr/bin/groupadd", "--gid", str(identity), name], check=True, timeout=10, capture_output=True, ) try: pwd.getpwnam(name) except KeyError: subprocess.run( [ "/usr/bin/useradd", "--uid", str(identity), "--gid", str(identity), "--no-create-home", "--no-user-group", "--key", "SUB_UID_COUNT=0", "--key", "SUB_GID_COUNT=0", "--home-dir", "/nonexistent", "--shell", "/usr/bin/nologin", name, ], check=True, timeout=10, capture_output=True, ) if preflight.identity_conflicts(): raise DeliveryError("OS identity readback differs") def data_directory(path: Path, owner: int) -> None: preflight.protected(path.parent, directory=True) if not path.exists() and not path.is_symlink(): path.mkdir(mode=0o700) os.chown(path, owner, owner) info = path.lstat() if ( not stat.S_ISDIR(info.st_mode) or info.st_uid != owner or info.st_gid != owner or stat.S_IMODE(info.st_mode) != 0o700 ): raise DeliveryError("existing private data directory identity/mode differs") def frozen_copy(source: Path, target: Path, *, path_prefix: str = "") -> None: """For already verified release images, never accept external archive members.""" records = manifest.inventory(source, path_prefix=path_prefix) directories = manifest.directories(source, path_prefix=path_prefix) if target.exists() or target.is_symlink(): preflight.protected(target, directory=True) if ( manifest.inventory(target, path_prefix=path_prefix) != records or manifest.directories(target, path_prefix=path_prefix) != directories ): raise DeliveryError("existing protected image differs") for record in records: preflight.protected(target / record["path"]) return target.mkdir(mode=0o700) for name in sorted(directories, key=lambda name: (name.count("/"), name)): (target / name).mkdir(mode=0o700) for record in records: copy_verified( source / record["path"], target / record["path"], record["sha256"], int(record["mode"], 8), ) for name in sorted(directories, key=lambda name: name.count("/"), reverse=True): (target / name).chmod(0o555) sync_directory(target / name) target.chmod(0o555) sync_directory(target) if ( manifest.inventory(target, path_prefix=path_prefix) != records or manifest.directories(target, path_prefix=path_prefix) != directories ): raise DeliveryError("installed image readback differs") def files(bundle: Path, expected: str) -> dict: if os.geteuid() != 0: raise DeliveryError("installer requires root") preflight.protected(bundle, directory=True) data = manifest.verify(bundle, expected) observation = preflight.observe(bundle / "payload") if not observation["eligible"]: raise DeliveryError("installation preflight refused: " + "; ".join(observation["errors"])) # Per-entry ownership matters even when the inventory is identical. for record in data["files"]: preflight.protected(bundle / "payload" / record["path"]) for name in data["directories"]: preflight.protected(bundle / "payload" / name, directory=True) ensure_accounts() for name, mode in ( ("/opt/kf-onlinejudge", 0o755), (str(RELEASES), 0o755), ("/etc/kf-onlinejudge", 0o700), ("/etc/kf-judge", 0o700), ("/var/lib/kf-onlinejudge", 0o755), ("/var/lib/kf-judge", 0o755), ("/var/lib/kf-judge/tasks", 0o755), (str(IMAGES), 0o755), ): directory(Path(name), mode) for name in ("artifacts", "imports"): data_directory(Path("/var/lib/kf-onlinejudge") / name, preflight.ACCOUNTS["kf-oj"]) data_directory(Path("/var/lib/kf-onlinejudge/worker"), preflight.ACCOUNTS["kf-worker"]) release = RELEASES / data["release_id"] if release.exists(): preflight.protected(release, directory=True) manifest.verify(release, expected) for record in data["files"]: preflight.protected(release / "payload" / record["path"]) else: stage = RELEASES / ("." + data["release_id"] + ".installing") if stage.exists() or stage.is_symlink(): raise DeliveryError("incomplete release requires explicit operator inspection") stage.mkdir(mode=0o700) # `frozen_copy` accepts names through the same bounded allowlist. frozen_copy(bundle / "payload", stage / "payload") copy_verified(bundle / "manifest.json", stage / "manifest.json", expected, 0o444) manifest.verify(stage, expected) stage.chmod(0o555) os.rename(stage, release) sync_directory(RELEASES) image_base = IMAGES / data["release_id"] directory(image_base, 0o755) for name in data["images"]: frozen_copy( release / "payload/images" / name, image_base / name, path_prefix="images/catalog/" ) legacy = image_base / "legacy-gnu" if legacy.exists(): preflight.protected(legacy, directory=True) if read_json(legacy / "manifest.json") != LEGACY_HEADER: raise DeliveryError("legacy compatibility image header differs") wanted = { r["path"]: r for r in manifest.inventory(image_base / "gnu", path_prefix="images/catalog/") if r["path"] != "manifest.json" } actual = { r["path"]: r for r in manifest.inventory(legacy, path_prefix="images/catalog/") if r["path"] != "manifest.json" } if wanted != actual or ( manifest.directories(legacy, path_prefix="images/catalog/") != manifest.directories(image_base / "gnu", path_prefix="images/catalog/") ): raise DeliveryError("legacy compatibility image bytes differ") else: # This is an explicit v1 compatibility image, not a catalog rewrite. # All executable/library bytes are identical to the verified GNU image. frozen_copy(image_base / "gnu", legacy, path_prefix="images/catalog/") legacy.chmod(0o700) old = legacy / "manifest.json" old.chmod(0o600) old.write_bytes(canonical(LEGACY_HEADER)) old.chmod(0o444) legacy.chmod(0o555) receipt = { "schema_version": 1, "release_id": data["release_id"], "source_sha": data["source_sha"], "manifest_sha256": expected, "images": data["images"], "legacy_header_sha256": digest(legacy / "manifest.json"), "files_installed": True, "activated": False, "migrated": False, "execution_verified": False, } exclusive_file( Path("/etc/kf-onlinejudge") / (data["release_id"] + ".files.json"), canonical(receipt), 0o600, ) return receipt def activate(release_id: str, expected: str) -> dict: """Only files + separately installed exact configuration may be selected. This command starts no service, migrates no table, and removes no old version. Runtime admission/isolation/health remains a separate bounded operation. """ if os.geteuid() != 0 or not re.fullmatch(r"sd-[0-9a-f]{12}", release_id): raise DeliveryError("invalid activation identity or privilege") release = RELEASES / release_id preflight.protected(release, directory=True) data = manifest.verify(release, expected) config_receipt = Path("/etc/kf-onlinejudge") / (release_id + ".configuration.json") from .configuration import check_installed binding = check_installed(config_receipt) if ( binding.get("manifest_sha256") != expected or binding.get("source_sha") != data["source_sha"] ): raise DeliveryError("configuration not bound to selected release") for port in (8000, 8100): import socket with socket.socket() as check: try: check.bind(("127.0.0.1", port)) except OSError as error: raise DeliveryError("stop and drain services before activation") from error if Path("/var/lib/kf-judge/factory/control.sock").exists(): raise DeliveryError("factory socket present; explicit stop/cleanup required") for name in ("tasks", "identities", "captures"): path = Path("/var/lib/kf-judge") / name if path.exists(): preflight.protected(path, directory=True) if next(path.iterdir(), None) is not None: raise DeliveryError("execution leases or task resources remain; activation refused") if CURRENT.exists() or CURRENT.is_symlink(): info = CURRENT.lstat() if not stat.S_ISLNK(info.st_mode) or info.st_uid != 0: raise DeliveryError("unknown current entry refused") old = Path(os.readlink(CURRENT)) if old.parent.parent != RELEASES or old.name != "payload": raise DeliveryError("current link outside release layout") preflight.protected(old, directory=True) temporary = CURRENT.parent / (".current-" + secrets.token_hex(16)) temporary.symlink_to(release / "payload") os.replace(temporary, CURRENT) sync_directory(CURRENT.parent) return { "selected_source_sha": data["source_sha"], "activated": True, "services_started": False, "execution_verified": False, }