#!/usr/bin/env python3 """Resume configuration of the one reviewed, powered-off partial M9 VM.""" from __future__ import annotations import argparse import hashlib import json import os from pathlib import Path import re import stat import subprocess import sys from typing import Sequence import uuid import guard import create LAB_ROOT = guard.LAB_ROOT ISO_PATH = LAB_ROOT / guard.EXPECTED_ISO VM_NAME = "wga-m9-debian137-20261025" TARGET_UUID = "b14c850b-a21e-4a56-acca-785590f0c4ac" PINNED_ISO_SHA256 = "a7ef94ac2fb9a7fec454552abd629b7cc9d5155c886165a45649f5ce6167e355" class ResumeError(Exception): """The partial VM could not safely be resumed or configured.""" Runner = create.Runner def _require_absent(path: Path, description: str) -> None: try: path.lstat() except FileNotFoundError: return except OSError as exc: raise ResumeError(f"could not inspect {description}; refusing resume") from exc raise ResumeError(f"{description} already exists; refusing resume") def _check_iso(iso_path: Path) -> None: if guard.EXPECTED_ISO_SHA256 != PINNED_ISO_SHA256: raise ResumeError("configured ISO digest differs from the pinned digest") if iso_path != ISO_PATH: raise ResumeError("installer ISO path is not the pinned lab path") try: if iso_path.parent.resolve(strict=True) != LAB_ROOT.resolve(strict=True): raise ResumeError("installer ISO must be directly inside the exact lab root") info = iso_path.lstat() except OSError as exc: raise ResumeError("pinned installer ISO is unavailable") from exc if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode): raise ResumeError("installer ISO must be a regular non-symlink file") digest = hashlib.sha256() try: with iso_path.open("rb") as source: for chunk in iter(lambda: source.read(1024 * 1024), b""): digest.update(chunk) except OSError as exc: raise ResumeError("pinned installer ISO cannot be read") from exc if digest.hexdigest() != PINNED_ISO_SHA256: raise ResumeError("installer ISO SHA-256 does not match the pinned digest") def _inventory(runner: Runner) -> None: output = guard._invoke(runner, ("VBoxManage", "list", "vms")) names: set[str] = set() uuids: set[str] = set() matches: list[tuple[str, str]] = [] for line in output.splitlines(): if not line.strip(): continue match = re.fullmatch(r'"([^"\r\n]+)" \{([0-9A-Fa-f-]{36})\}', line) if not match: raise ResumeError("malformed VBoxManage inventory; refusing resume") name, raw_uuid = match.groups() try: vm_uuid = str(uuid.UUID(raw_uuid)) except ValueError as exc: raise ResumeError("malformed VBoxManage inventory UUID; refusing resume") from exc if name in names or vm_uuid in uuids: raise ResumeError("duplicate VBoxManage inventory identity; refusing resume") names.add(name) uuids.add(vm_uuid) if name == VM_NAME or vm_uuid == TARGET_UUID: matches.append((name, vm_uuid)) if matches != [(VM_NAME, TARGET_UUID)]: raise ResumeError("registered VM inventory does not match the pinned partial target") def _preflight_info(info: dict[str, str], lab_root: Path, owner_uid: int) -> None: if info.get("name") != VM_NAME or info.get("UUID", "").lower() != TARGET_UUID: raise ResumeError("showvminfo identity does not match the pinned partial target") if info.get("VMState") != "poweroff": raise ResumeError("partial target must be powered off") if info.get("description", ""): raise ResumeError("partial target already has a host marker; refusing resume") if info.get("memory") != "1024" or info.get("cpus") != "1": raise ResumeError("partial target default memory or CPU state is unexpected") cfg_file = Path(info.get("CfgFile", "")) if cfg_file != lab_root / VM_NAME / f"{VM_NAME}.vbox" or cfg_file.is_symlink(): raise ResumeError("partial target configuration path is not the exact lab path") try: folder_info = (lab_root / VM_NAME).lstat() config_info = cfg_file.lstat() except OSError as exc: raise ResumeError("partial target configuration file is unavailable") from exc if (not stat.S_ISDIR(folder_info.st_mode) or stat.S_ISLNK(folder_info.st_mode) or folder_info.st_uid != owner_uid or (lab_root / VM_NAME).resolve(strict=True) != lab_root / VM_NAME): raise ResumeError("partial target folder must be a real directory at the exact lab path") if (not stat.S_ISREG(config_info.st_mode) or stat.S_ISLNK(config_info.st_mode) or config_info.st_uid != owner_uid): raise ResumeError("partial target configuration must be a current-user-owned regular file") nics = {int(match.group(1)): value for key, value in info.items() if (match := re.fullmatch(r"nic([1-8])", key))} if (len(nics) != 8 or nics.get(1) != "nat" or any(nics.get(slot) not in {"none", "null"} for slot in range(2, 9))): raise ResumeError("partial target NIC state must be NIC 1 in NAT with NICs 2-8 disabled") if any((key.lower().startswith("natpf") or key.lower().startswith("forwarding(")) and value for key, value in info.items()): raise ResumeError("partial target has NAT forwarding; refusing resume") if any(key.startswith("SharedFolder") and value for key, value in info.items()): raise ResumeError("partial target has shared folders; refusing resume") storage_rows = [ (key, value) for key, value in info.items() if re.fullmatch(r"[A-Za-z0-9_-]+-[0-9]+-[0-9]+", key) ] if any(value not in {"", "emptydrive", "none"} for _, value in storage_rows): raise ResumeError("partial target has attached storage; refusing resume") if any(key.startswith("storagecontroller") for key in info): raise ResumeError("partial target has a storage controller; refusing resume") def _preflight( *, lab_root: Path, iso_path: Path, owner_uid: int, runner: Runner, ) -> None: if lab_root != LAB_ROOT: raise ResumeError("resume is restricted to the pinned lab root") try: root_info = lab_root.lstat() except OSError as exc: raise ResumeError("private lab root is unavailable") from exc if (not stat.S_ISDIR(root_info.st_mode) or stat.S_ISLNK(root_info.st_mode) or root_info.st_uid != owner_uid or stat.S_IMODE(root_info.st_mode) != 0o700): raise ResumeError("lab root must be a real current-user directory with mode 0700") _check_iso(iso_path) _require_absent(lab_root / guard.IDENTITY_FILE, "target identity manifest") _require_absent(lab_root / VM_NAME / f"{VM_NAME}.vdi", "target VDI") _inventory(runner) info_text = guard._invoke(runner, ( "VBoxManage", "showvminfo", TARGET_UUID, "--machinereadable", )) try: info = guard._machine_values(info_text) except guard.GuardError as exc: raise ResumeError("partial target machine-readable state is invalid") from exc _preflight_info(info, lab_root, owner_uid) def _write_manifest(lab_root: Path, owner_uid: int) -> None: marker = f"{VM_NAME}:{TARGET_UUID}" data = { "schema": 1, "name": VM_NAME, "owner_uid": owner_uid, "uuid": TARGET_UUID, "host_marker": marker, } path = lab_root / guard.IDENTITY_FILE flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL if hasattr(os, "O_NOFOLLOW"): flags |= os.O_NOFOLLOW try: fd = os.open(path, flags, 0o600) with os.fdopen(fd, "w", encoding="utf-8") as destination: os.fchmod(destination.fileno(), 0o600) json.dump(data, destination, sort_keys=True) destination.write("\n") destination.flush() os.fsync(destination.fileno()) except OSError as exc: raise ResumeError(f"could not write identity manifest; retained partial VM {VM_NAME} ({TARGET_UUID})") from exc def _call(runner: Runner, argv: Sequence[str]) -> str: try: result = runner(tuple(argv)) except (OSError, subprocess.TimeoutExpired, create.CreateError, guard.GuardError) as exc: raise ResumeError(f"bounded VBoxManage call failed: {argv[1]}") from exc if len(result.stdout) > create.MAX_CAPTURE or len(result.stderr) > create.MAX_CAPTURE: raise ResumeError("VBoxManage output exceeded the resume limit") if result.returncode != 0: raise ResumeError(f"VBoxManage {argv[1]} failed with status {result.returncode}") return result.stdout def resume_target( *, lab_root: Path = LAB_ROOT, iso_path: Path = ISO_PATH, owner_uid: int | None = None, runner: Runner = create.run_command, ) -> str: """Preflight the exact partial VM, finish setup, and run the read-only guard.""" owner_uid = os.getuid() if owner_uid is None else owner_uid try: _preflight(lab_root=lab_root, iso_path=iso_path, owner_uid=owner_uid, runner=runner) except (OSError, guard.GuardError) as exc: raise ResumeError("resume preflight failed closed") from exc disk_path = lab_root / VM_NAME / f"{VM_NAME}.vdi" try: _call(runner, ( "VBoxManage", "modifyvm", TARGET_UUID, "--memory", str(create.MEMORY_MIB), "--cpus", str(create.CPUS), "--nic1", "nat", "--nic2", "none", "--nic3", "none", "--nic4", "none", "--nic5", "none", "--nic6", "none", "--nic7", "none", "--nic8", "none", "--audio", "none", "--usb", "off", "--boot1", "dvd", "--boot2", "disk", "--boot3", "none", "--boot4", "none", )) _call(runner, ( "VBoxManage", "createmedium", "disk", "--filename", str(disk_path), "--size", str(create.DISK_SIZE_MIB), "--format", "VDI", "--variant", "Standard", )) _call(runner, ( "VBoxManage", "storagectl", TARGET_UUID, "--name", "SATA", "--add", "sata", "--controller", "IntelAhci", )) _call(runner, ( "VBoxManage", "storageattach", TARGET_UUID, "--storagectl", "SATA", "--port", "0", "--device", "0", "--type", "hdd", "--medium", str(disk_path), )) _call(runner, ( "VBoxManage", "storageattach", TARGET_UUID, "--storagectl", "SATA", "--port", "1", "--device", "0", "--type", "dvddrive", "--medium", str(iso_path), )) _write_manifest(lab_root, owner_uid) _call(runner, ( "VBoxManage", "modifyvm", TARGET_UUID, "--description", f"{VM_NAME}:{TARGET_UUID}", )) try: selected_uuid, _ = guard.inspect_target( lab_root=lab_root, owner_uid=owner_uid, runner=runner, ) except (guard.GuardError, OSError) as exc: raise ResumeError("final read-only guard failed") from exc if selected_uuid != TARGET_UUID: raise ResumeError("final guard returned an unexpected UUID") return TARGET_UUID except ResumeError as exc: if "retained partial VM" in str(exc): raise raise ResumeError(f"{exc}; retained partial VM {VM_NAME} ({TARGET_UUID})") from exc except (OSError, ValueError, KeyError, create.CreateError, guard.GuardError) as exc: raise ResumeError(f"resume configuration failed; retained partial VM {VM_NAME} ({TARGET_UUID})") from exc def main(argv: Sequence[str] | None = None) -> int: parser = argparse.ArgumentParser(description=__doc__) parser.parse_args(argv) try: target_uuid = resume_target() except ResumeError as exc: print(f"VM resume refused/stopped: {exc}", file=sys.stderr) return 2 print(f"Resumed and guarded {VM_NAME} ({target_uuid}); VM remains powered off") return 0 if __name__ == "__main__": raise SystemExit(main())