Add integration tests based on VM
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check and make the bounded first headless start of the pinned M9 installer VM."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Sequence
|
||||
import uuid
|
||||
|
||||
import guard
|
||||
|
||||
|
||||
TARGET_UUID = "b14c850b-a21e-4a56-acca-785590f0c4ac"
|
||||
TARGET_NAME = guard.TARGET_NAME
|
||||
LAB_ROOT = guard.LAB_ROOT
|
||||
TARGET_FOLDER = LAB_ROOT / TARGET_NAME
|
||||
EXPECTED_VDI = TARGET_FOLDER / f"{TARGET_NAME}.vdi"
|
||||
EXPECTED_VISO = TARGET_FOLDER / (
|
||||
f"Unattended-{TARGET_UUID}-aux-iso.viso"
|
||||
)
|
||||
MAX_CAPTURE = guard.MAX_CAPTURE
|
||||
|
||||
|
||||
class InstallBootError(Exception):
|
||||
"""Pinned installer state failed validation or first start was uncertain."""
|
||||
|
||||
|
||||
def _regular_owned_file(
|
||||
path: Path, *, owner_uid: int, mode: int | None, label: str,
|
||||
) -> Path:
|
||||
try:
|
||||
info = path.lstat()
|
||||
except OSError as exc:
|
||||
raise InstallBootError(f"{label} is unavailable") from exc
|
||||
if (not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode)
|
||||
or info.st_uid != owner_uid
|
||||
or (mode is not None and stat.S_IMODE(info.st_mode) != mode)):
|
||||
permission = "a regular current-user-owned file" if mode is None else (
|
||||
f"current-user-owned mode {mode:04o}"
|
||||
)
|
||||
raise InstallBootError(f"{label} must be {permission}")
|
||||
if info.st_nlink != 1:
|
||||
raise InstallBootError(f"{label} must have exactly one hard link")
|
||||
try:
|
||||
return path.resolve(strict=True)
|
||||
except OSError as exc:
|
||||
raise InstallBootError(f"{label} is unavailable") from exc
|
||||
|
||||
|
||||
def _check_installer_private_files(target_folder: Path, owner_uid: int) -> None:
|
||||
"""Check installer material permissions without opening or displaying contents."""
|
||||
def walk_error(error: OSError) -> None:
|
||||
raise InstallBootError("private target folder cannot be inspected") from error
|
||||
|
||||
try:
|
||||
walk = tuple(os.walk(target_folder, followlinks=False, onerror=walk_error))
|
||||
except OSError as exc:
|
||||
raise InstallBootError("private target folder cannot be inspected") from exc
|
||||
for directory, subdirectories, filenames in walk:
|
||||
directory_path = Path(directory)
|
||||
if any((directory_path / name).is_symlink() for name in subdirectories):
|
||||
raise InstallBootError("symlink in private target folder is prohibited")
|
||||
for name in subdirectories:
|
||||
child = directory_path / name
|
||||
child_info = child.lstat()
|
||||
if (not stat.S_ISDIR(child_info.st_mode) or child_info.st_uid != owner_uid
|
||||
or stat.S_IMODE(child_info.st_mode) != 0o700):
|
||||
raise InstallBootError("nested target directories must be current-user-owned mode 0700")
|
||||
for filename in filenames:
|
||||
entry = directory_path / filename
|
||||
if entry.is_symlink():
|
||||
raise InstallBootError("symlink in private target folder is prohibited")
|
||||
# VirtualBox's unattended installer generates the sensitive auxiliary
|
||||
# media and installer scripts here. Match names only; never read content.
|
||||
if re.search(r"(?:viso|isolinux|preseed|postinstall)", entry.name, re.IGNORECASE):
|
||||
_regular_owned_file(
|
||||
entry, owner_uid=owner_uid, mode=0o600,
|
||||
label="generated unattended installer file",
|
||||
)
|
||||
|
||||
|
||||
def _machine_info(*, lab_root: Path, runner: guard.Runner, owner_uid: int) -> tuple[str, dict[str, str]]:
|
||||
"""Apply strict host identity checks while allowing only the known installer .viso."""
|
||||
try:
|
||||
guard._owned_private_directory(lab_root, owner_uid)
|
||||
manifest = guard._read_manifest(lab_root / guard.IDENTITY_FILE, owner_uid)
|
||||
selected_uuid = str(manifest["uuid"])
|
||||
if selected_uuid != TARGET_UUID:
|
||||
raise InstallBootError("identity manifest UUID is not the pinned installer target")
|
||||
|
||||
listed = guard._invoke(runner, ("VBoxManage", "list", "vms"))
|
||||
rows: list[tuple[str, str]] = []
|
||||
for line in listed.splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
match = re.fullmatch(r'"([^"\r\n]+)" \{([0-9A-Fa-f-]{36})\}', line)
|
||||
if match is None:
|
||||
raise InstallBootError("malformed VirtualBox VM inventory")
|
||||
name, raw_uuid = match.groups()
|
||||
try:
|
||||
vm_uuid = str(uuid.UUID(raw_uuid))
|
||||
except ValueError as exc:
|
||||
raise InstallBootError("malformed VirtualBox VM UUID") from exc
|
||||
rows.append((name, vm_uuid))
|
||||
if (len({name for name, _ in rows}) != len(rows)
|
||||
or len({vm_uuid for _, vm_uuid in rows}) != len(rows)
|
||||
or [(name, vm_uuid) for name, vm_uuid in rows
|
||||
if name == TARGET_NAME or vm_uuid == TARGET_UUID]
|
||||
!= [(TARGET_NAME, TARGET_UUID)]):
|
||||
raise InstallBootError("pinned VM name and UUID are missing, duplicated, or mismatched")
|
||||
|
||||
info = guard._machine_values(guard._invoke(
|
||||
runner, ("VBoxManage", "showvminfo", TARGET_UUID, "--machinereadable"),
|
||||
))
|
||||
except guard.GuardError as exc:
|
||||
raise InstallBootError(f"strict host identity check failed: {exc}") from None
|
||||
|
||||
if info.get("name") != TARGET_NAME or info.get("UUID", "").lower() != TARGET_UUID:
|
||||
raise InstallBootError("showvminfo identity does not match the pinned target")
|
||||
marker = f"{TARGET_NAME}:{TARGET_UUID}"
|
||||
if manifest.get("host_marker") != marker or info.get("description") != marker:
|
||||
raise InstallBootError("manifest and VirtualBox host marker do not match")
|
||||
if info.get("VMState") != "poweroff":
|
||||
raise InstallBootError("pinned target must be powered off before first start")
|
||||
|
||||
folder = lab_root / TARGET_NAME
|
||||
try:
|
||||
folder_info = folder.lstat()
|
||||
except OSError as exc:
|
||||
raise InstallBootError("exact target folder 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 stat.S_IMODE(folder_info.st_mode) != 0o700):
|
||||
raise InstallBootError("exact target folder must be current-user-owned mode 0700")
|
||||
resolved_root = lab_root.resolve(strict=True)
|
||||
resolved_folder = folder.resolve(strict=True)
|
||||
if resolved_folder != resolved_root / TARGET_NAME:
|
||||
raise InstallBootError("exact target folder resolves outside the lab root")
|
||||
|
||||
cfg_file = Path(info.get("CfgFile", ""))
|
||||
if cfg_file.is_symlink() or cfg_file.name != f"{TARGET_NAME}.vbox":
|
||||
raise InstallBootError("VM configuration path is unexpected")
|
||||
config = _regular_owned_file(
|
||||
cfg_file, owner_uid=owner_uid, mode=None,
|
||||
label="VM configuration file",
|
||||
)
|
||||
if config.parent != resolved_folder:
|
||||
raise InstallBootError("VM configuration is outside the exact target folder")
|
||||
|
||||
expected_disk = _regular_owned_file(
|
||||
EXPECTED_VDI, owner_uid=owner_uid, mode=None,
|
||||
label="pinned target VDI",
|
||||
)
|
||||
expected_viso = _regular_owned_file(
|
||||
EXPECTED_VISO, owner_uid=owner_uid, mode=0o600, label="generated auxiliary .viso",
|
||||
)
|
||||
if expected_disk.parent != resolved_folder or expected_viso.parent != resolved_folder:
|
||||
raise InstallBootError("expected VM media resolves outside the exact target folder")
|
||||
if info.get("storagecontrollername0") != "SATA" or any(
|
||||
key.startswith("storagecontrollername") and key != "storagecontrollername0"
|
||||
for key in info
|
||||
):
|
||||
raise InstallBootError("storage controller inventory is not the pinned SATA controller")
|
||||
attachments = {
|
||||
key: value for key, value in info.items()
|
||||
if re.fullmatch(r"SATA-[0-9]+-[0-9]+", key)
|
||||
}
|
||||
if (attachments.get("SATA-0-0") != str(expected_disk)
|
||||
or attachments.get("SATA-1-0") != str(expected_viso)
|
||||
or any(attachments.get(f"SATA-{port}-0") != "none" for port in range(2, 30))
|
||||
or set(attachments) != {f"SATA-{port}-0" for port in range(30)}):
|
||||
raise InstallBootError("SATA media do not exactly match the pinned installer state")
|
||||
for key, value in info.items():
|
||||
if (key.startswith("SATA-") and not re.fullmatch(
|
||||
r"SATA-(?:[0-9]+-[0-9]+|(?:ImageUUID|IsEjected|hot-pluggable|nonrotational|discard|tempeject)-[0-9]+-[0-9]+)", key)):
|
||||
raise InstallBootError("unrecognized SATA attachment metadata")
|
||||
if (not key.startswith("SATA-")
|
||||
and re.fullmatch(r".+-[0-9]+-[0-9]+", key)):
|
||||
raise InstallBootError("unexpected attached storage controller or media")
|
||||
if re.fullmatch(r"SATA-[0-9]+-[0-9]+", key) and value not in {"none", str(expected_disk), str(expected_viso)}:
|
||||
raise InstallBootError("unexpected attached media")
|
||||
|
||||
if {key for key in info if re.fullmatch(r"nic[0-9]+", key)} != {
|
||||
f"nic{slot}" for slot in range(1, 9)
|
||||
}:
|
||||
raise InstallBootError("network adapter inventory is incomplete or has extra devices")
|
||||
nics = {f"nic{slot}": info.get(f"nic{slot}") for slot in range(1, 9)}
|
||||
if nics != {"nic1": "nat", **{f"nic{slot}": "none" for slot in range(2, 9)}}:
|
||||
raise InstallBootError("network inventory is not the pinned NAT-only configuration")
|
||||
if any(key.lower().startswith(("natpf", "forwarding(")) and value
|
||||
for key, value in info.items()):
|
||||
raise InstallBootError("NAT port forwarding is prohibited")
|
||||
if any(key.startswith("SharedFolder") and value for key, value in info.items()):
|
||||
raise InstallBootError("VirtualBox shared folders are prohibited")
|
||||
|
||||
_check_installer_private_files(resolved_folder, owner_uid)
|
||||
return TARGET_UUID, info
|
||||
|
||||
|
||||
def inspect_installer_state(
|
||||
*, lab_root: Path = LAB_ROOT,
|
||||
owner_uid: int | None = None,
|
||||
runner: guard.Runner = guard.run_command,
|
||||
) -> tuple[str, dict[str, str]]:
|
||||
owner_uid = os.getuid() if owner_uid is None else owner_uid
|
||||
return _machine_info(lab_root=lab_root, runner=runner, owner_uid=owner_uid)
|
||||
|
||||
|
||||
def start_installer(
|
||||
*, lab_root: Path = LAB_ROOT,
|
||||
owner_uid: int | None = None,
|
||||
runner: guard.Runner = guard.run_command,
|
||||
) -> str:
|
||||
target_uuid, _ = inspect_installer_state(
|
||||
lab_root=lab_root, owner_uid=owner_uid, runner=runner,
|
||||
)
|
||||
try:
|
||||
guard._invoke(runner, ("VBoxManage", "startvm", target_uuid, "--type=headless"))
|
||||
except guard.GuardError:
|
||||
raise InstallBootError(
|
||||
"bounded first-start request failed or timed out; the VM may have started; "
|
||||
"run read-only inspection before any further action"
|
||||
) from None
|
||||
return target_uuid
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("action", choices=("check", "start"))
|
||||
args = parser.parse_args(argv)
|
||||
try:
|
||||
if args.action == "check":
|
||||
target_uuid, _ = inspect_installer_state()
|
||||
else:
|
||||
target_uuid = start_installer()
|
||||
except (InstallBootError, guard.GuardError) as exc:
|
||||
print(f"Installer-state guard refused: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
if args.action == "check":
|
||||
print(f"installer state checked for {TARGET_NAME} ({target_uuid}); VM remains powered off")
|
||||
else:
|
||||
print(
|
||||
f"bounded first-start request completed for {TARGET_NAME} ({target_uuid}); "
|
||||
"guest installation and boot are not verified"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user