from __future__ import annotations import ast import importlib.util import io import json import os import stat import subprocess import sys import tarfile import types from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] spec = importlib.util.spec_from_file_location( "controller_hub_volume", ROOT / "scripts/prepare_controller_hub_release_volume.py" ) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) REVISION = "a" * 40 IMAGE_ID = "sha256:" + "b" * 64 ORIGINAL_COMPOSE = b"name: gitea-deploy-runners\nservices:\n deploy-control:\n image: immutable-controller\n" HISTORICAL_CONFIG_ROOT_RELEASE_FILES = ( "controller_paths.py", "mcp_hub_release_store.py", "hub_permission_preparation.py", "deploy.py", "managed_appdata_paths.py", "prepare_managed_appdata_root.py", "runner_fleet.py", "runner_capacity_controller.py", "runner_capacity_policy.py", "runner_fleet_upgrade_v2.py", "runner_profiles.py", "runner_admission_policy.py", "runner_admission_controller.py", "runner_resource_envelope.py", "runner_fleet_specialize.py", "runner_statistics_binding.py", "status_server.py", "repopulse_adoption_store.py", "repopulse_registry_restore.py", "repopulse_adoption_executor.py", "repopulse_adoption_effects.py", "repopulse_adoption_storage_effects.py", "repopulse_target_attestation.py", "stage_repopulse_target_attestation.py", "repopulse_adoption_cli.py", "stage_repopulse_maintenance_environment.py", "run_repopulse_adoption.py", ) def volume(): return { "Name": module.VOLUME, "Driver": "local", "Scope": "local", "Options": None, "Labels": dict(module.LABELS), "Mountpoint": module.MOUNTPOINT.as_posix(), } @pytest.mark.parametrize( ("key", "value"), [ ("Name", "operator-volume"), ("Driver", "nfs"), ("Scope", "global"), ("Options", {"device": "/mnt/cache/appdata"}), ("Labels", {}), ("Mountpoint", "/mnt/cache/unsafe"), ], ) def test_unknown_or_redirected_volume_is_never_adopted(key, value): item = volume() item[key] = value with pytest.raises(module.VolumeTransitionError, match="identity_invalid"): module.validate_volume(item) def test_only_the_exact_local_volume_is_accepted(): module.validate_volume(volume()) def backup_volume(): return { "Name": "itworx-forge-master-key-backup", "Driver": "local", "Scope": "local", "Options": None, "Labels": { "itworx.managed-by": "deployment-controller", "itworx.purpose": "forge-master-key-backup", }, "Mountpoint": "/var/lib/docker/volumes/itworx-forge-master-key-backup/_data", } def test_backup_volume_identity_is_exact_and_never_repairs_existing_metadata(): module.validate_backup_volume(backup_volume()) bad = backup_volume() bad["Labels"]["itworx.purpose"] = "foreign" with pytest.raises(module.VolumeTransitionError): module.validate_backup_volume(bad) def test_promotion_preparer_creates_only_the_fixed_backup_volume_if_absent(monkeypatch, tmp_path): commands = [] present = False def run(arguments, **_kwargs): nonlocal present commands.append(arguments) if arguments == ["docker", "info", "--format", "{{json .DockerRootDir}}"]: return b'"/var/lib/docker"' if arguments == ["docker", "volume", "ls", "--format", "{{.Name}}"]: return b"" if not present else b"itworx-forge-master-key-backup\n" if arguments[:3] == ["docker", "volume", "create"]: present = True return b"itworx-forge-master-key-backup\n" if arguments == ["docker", "volume", "inspect", "itworx-forge-master-key-backup"]: return json.dumps([backup_volume()]).encode() raise AssertionError(arguments) directory_calls = [] monkeypatch.setattr(module, "_run", run) monkeypatch.setattr(module.os, "close", lambda _descriptor: None) def directory(path, anchor): directory_calls.append((path, anchor)) return 23 monkeypatch.setattr(module, "_directory", directory) module.ensure_backup_volume(create=True) create = [arguments for arguments in commands if arguments[:3] == ["docker", "volume", "create"]] assert create == [[ "docker", "volume", "create", "--driver", "local", "--label", "itworx.managed-by=deployment-controller", "--label", "itworx.purpose=forge-master-key-backup", "itworx-forge-master-key-backup", ]] assert directory_calls == [ (module.DOCKER_ROOT / "volumes", module.DOCKER_ROOT), (module.BACKUP_MOUNTPOINT, module.DOCKER_ROOT), ] def test_existing_backup_volume_is_validated_without_relabel_or_recreation(monkeypatch, tmp_path): commands = [] def run(arguments, **_kwargs): commands.append(arguments) if arguments == ["docker", "info", "--format", "{{json .DockerRootDir}}"]: return b'"/var/lib/docker"' if arguments == ["docker", "volume", "ls", "--format", "{{.Name}}"]: return b"itworx-forge-master-key-backup\n" if arguments == ["docker", "volume", "inspect", "itworx-forge-master-key-backup"]: item = backup_volume() item["CreatedAt"] = "2000-01-01T00:00:00Z" return json.dumps([item]).encode() raise AssertionError(arguments) monkeypatch.setattr(module, "_run", run) monkeypatch.setattr(module.os, "close", lambda _descriptor: None) monkeypatch.setattr( module, "_directory", lambda _path, _anchor: 23, ) module.ensure_backup_volume(create=False) assert not any(arguments[:3] == ["docker", "volume", "create"] for arguments in commands) def test_missing_backup_volume_is_not_created_outside_promotion(monkeypatch, tmp_path): commands = [] def run(arguments, **_kwargs): commands.append(arguments) if arguments == ["docker", "info", "--format", "{{json .DockerRootDir}}"]: return b'"/var/lib/docker"' if arguments == ["docker", "volume", "ls", "--format", "{{.Name}}"]: return b"" raise AssertionError(arguments) monkeypatch.setattr(module, "_run", run) monkeypatch.setattr(module.os, "close", lambda _descriptor: None) monkeypatch.setattr( module, "_directory", lambda _path, _anchor: 23, ) with pytest.raises(module.VolumeTransitionError, match="backup_volume_missing"): module.ensure_backup_volume(create=False) assert not any(arguments[:3] == ["docker", "volume", "create"] for arguments in commands) def _prepare_promotion_doubles(monkeypatch, tmp_path, *, config_valid=True): events = [] controller_id = "c" * 64 current = { "Id": controller_id, "Mounts": [{ "Destination": module.EXECUTABLE_TARGET, "Source": module.CONTROLLER_DIR.as_posix(), }], } previous_identity = { "sourceRevision": "b" * 40, "sourceTree": "c" * 40, "controllerVersion": "2.1.0", } config_tree = ({}, {"": 0o755}) monkeypatch.setattr(module, "_validate_private_stage", lambda _stage: events.append("stage_validated")) monkeypatch.setattr(module, "ensure_volume", lambda **_kwargs: None) monkeypatch.setattr( module, "_controller_image_inspect", lambda _image_id: (events.append("controller_inspected") or current), ) monkeypatch.setattr( module, "_snapshot_attested_controller", lambda *_args, **_kwargs: ({}, {"": 0o755}, previous_identity), ) monkeypatch.setattr(module, "_validate_previous_private_tls", lambda _stage: None) monkeypatch.setattr(module, "_release_tree", lambda *_args, **_kwargs: ({}, {"": 0o755})) monkeypatch.setattr(module, "_config_tree", lambda *_args, **_kwargs: config_tree) monkeypatch.setattr(module, "_read_config_leaf", lambda: config_tree) monkeypatch.setattr(module, "_prepare_merged_tree", lambda *_args, **_kwargs: ({}, {"": 0o755})) def validate_staged(*_args, **_kwargs): events.append("config_validated") if not config_valid: raise module.VolumeTransitionError("staged_config_invalid") monkeypatch.setattr(module, "_validate_staged_configuration", validate_staged) monkeypatch.setattr(module, "_install_release_leaf", lambda *_args, **_kwargs: None) monkeypatch.setattr(module, "_read_root_compose", lambda: (ORIGINAL_COMPOSE, 0o644)) monkeypatch.setattr(module, "_mirror_snapshot_tar", lambda: b"mirror") monkeypatch.setattr( module, "rendered_model", lambda *_args, **_kwargs: (events.append("compose_rendered") or b"rendered-compose"), ) monkeypatch.setattr( module, "_verify_compose_image", lambda *_args, **_kwargs: events.append("compose_verified"), ) monkeypatch.setattr( module, "ensure_backup_volume", lambda **_kwargs: events.append("backup_volume_prepared"), raising=False, ) monkeypatch.setattr(module, "_release_tar_bytes", lambda *_args, **_kwargs: b"tar") backup = tmp_path / "promotion-backup" backup.mkdir(mode=0o700) backup_fd = 23 monkeypatch.setattr(module, "_create_backup", lambda: (backup, backup_fd)) monkeypatch.setattr(module, "_write_backup", lambda *_args, **_kwargs: None) monkeypatch.setattr(module.os, "close", lambda _descriptor: None) return events def test_promotion_prepares_backup_volume_after_source_stage_controller_and_config_checks( monkeypatch, tmp_path ): events = _prepare_promotion_doubles(monkeypatch, tmp_path) module.prepare_promotion( tmp_path / "stage", source_revision="d" * 40, source_tree="e" * 40, controller_version="2.2.0", image_id=IMAGE_ID, ) assert events.index("stage_validated") < events.index("config_validated") assert events.index("config_validated") < events.index("backup_volume_prepared") assert events.index("compose_rendered") < events.index("backup_volume_prepared") assert events.count("compose_verified") == 2 assert events.index("compose_verified") < events.index("backup_volume_prepared") def test_promotion_does_not_prepare_backup_volume_when_staged_configuration_fails( monkeypatch, tmp_path ): events = _prepare_promotion_doubles(monkeypatch, tmp_path, config_valid=False) with pytest.raises(module.VolumeTransitionError, match="staged_config_invalid"): module.prepare_promotion( tmp_path / "stage", source_revision="d" * 40, source_tree="e" * 40, controller_version="2.2.0", image_id=IMAGE_ID, ) assert "backup_volume_prepared" not in events def writer_inventory(inherited_labels): identifier = "c" * 64 spec = module._writer_spec(IMAGE_ID, REVISION, kind="config") return { "Id": identifier, "Name": f"/{module.WRITER_NAME}", "Image": IMAGE_ID, "Config": { "Image": IMAGE_ID, "User": "0:0", "Entrypoint": ["python3"], "Cmd": spec[-2:], "Labels": {**inherited_labels, module.WRITER_LABEL: "true", module.WRITER_TARGET_LABEL: "config", module.WRITER_REVISION_LABEL: REVISION}, }, "HostConfig": { "NetworkMode": "none", "Binds": [], "Privileged": False, "ReadonlyRootfs": True, "CpuQuota": module.WRITER_CPU_QUOTA, "CpuPeriod": module.WRITER_CPU_PERIOD, "Memory": module.WRITER_MEMORY_BYTES, "PidsLimit": module.WRITER_PIDS_LIMIT, "CapDrop": ["ALL"], "Tmpfs": {"/tmp": "rw,nosuid,nodev,size=16777216,mode=1777"}, "SecurityOpt": ["no-new-privileges:true"], }, "Mounts": [{"Type": "volume", "Name": module.VOLUME, "Source": module.MOUNTPOINT.as_posix(), "Destination": module.TARGET, "RW": True}], } @pytest.mark.parametrize("damage", [None, "changed_image_label", "extra_label", "reserved_image_label", "falsey_labels"]) def test_writer_labels_are_bound_to_exact_image_inheritance(monkeypatch, damage): inherited = {"org.opencontainers.image.revision": "d" * 40, "io.buildah.version": "1.40.0"} item = writer_inventory(inherited) if damage == "changed_image_label": item["Config"]["Labels"]["org.opencontainers.image.revision"] = "e" * 40 elif damage == "extra_label": item["Config"]["Labels"]["unexpected"] = "value" elif damage == "reserved_image_label": inherited[module.WRITER_LABEL] = "inherited-marker" elif damage == "falsey_labels": inherited = [] def run(arguments, **kwargs): if arguments == ["docker", "inspect", item["Id"]]: return json.dumps([item]).encode() assert arguments == ["docker", "image", "inspect", IMAGE_ID] return json.dumps([{"Id": IMAGE_ID, "Config": {"Labels": inherited}}]).encode() monkeypatch.setattr(module, "_run", run) if damage: with pytest.raises(module.VolumeTransitionError, match="writer_.*invalid"): module._inspect_writer(item["Id"], IMAGE_ID, REVISION, kind="config") else: assert module._inspect_writer(item["Id"], IMAGE_ID, REVISION, kind="config") == item @pytest.mark.parametrize("damage", [None, "tmpfs_projection", "extra_bind", "wrong_volume", "read_only"]) def test_writer_accepts_declared_tmpfs_without_inspect_mount_projection(monkeypatch, damage): item = writer_inventory({}) if damage == "tmpfs_projection": item["Mounts"].append({"Type": "tmpfs", "Destination": "/tmp"}) elif damage == "extra_bind": item["Mounts"].append({"Type": "bind", "Destination": "/foreign"}) elif damage == "wrong_volume": item["Mounts"][0]["Name"] = "foreign-volume" elif damage == "read_only": item["Mounts"][0]["RW"] = False def run(arguments, **kwargs): if arguments == ["docker", "inspect", item["Id"]]: return json.dumps([item]).encode() assert arguments == ["docker", "image", "inspect", IMAGE_ID] return json.dumps([{"Id": IMAGE_ID, "Config": {"Labels": None}}]).encode() monkeypatch.setattr(module, "_run", run) if damage in {"extra_bind", "wrong_volume", "read_only"}: with pytest.raises(module.VolumeTransitionError, match="writer_mount_invalid"): module._inspect_writer(item["Id"], IMAGE_ID, REVISION, kind="config") else: module._inspect_writer(item["Id"], IMAGE_ID, REVISION, kind="config") @pytest.mark.parametrize("name", ["foreign-project", "", None, "${COMPOSE_PROJECT_NAME}"]) def test_source_compose_name_cannot_be_overridden_by_fixed_project_flag(monkeypatch, name): def never_compose(*args, **kwargs): pytest.fail("Foreign source project reached Compose") monkeypatch.setattr(module, "_run", never_compose) source = f"name: {json.dumps(name)}\nservices:\n deploy-control:\n image: immutable-controller\n".encode() with pytest.raises(module.VolumeTransitionError, match="project_invalid"): module.rendered_model(source, REVISION, IMAGE_ID) def test_compose_without_explicit_name_retains_canonical_project(monkeypatch): def run(arguments, *, data=None, **kwargs): if arguments[:3] == ["docker", "image", "inspect"]: return IMAGE_ID.encode() normalized = model() normalized["name"] = ( arguments[arguments.index("--project-name") + 1] if "--project-name" in arguments else "data" ) return json.dumps(normalized).encode() monkeypatch.setattr(module, "_run", run) original = b"services:\n deploy-control:\n image: immutable-controller\n" rendered = json.loads(module.rendered_model(original, REVISION, IMAGE_ID)) assert rendered["name"] == "gitea-deploy-runners" def model(): return { "name": "gitea-deploy-runners", "services": { "deploy-control": { "container_name": module.CONTAINER, "image": "immutable-controller", "volumes": [ { "type": "bind", "source": module.DOCKER_ROOT.as_posix(), "target": module.DOCKER_ROOT.as_posix(), "read_only": True, }, { "type": "bind", "source": module.CONTROLLER_DIR.as_posix(), "target": module.EXECUTABLE_TARGET, "read_only": True, }, ], "environment": {"PRIVATE_SETTING": "unchanged"}, }, "runner": { "image": "pinned-runner", "volumes": [ {"type": "bind", "source": "/fixed/data", "target": "/data"} ], }, }, } @pytest.fixture def compose(monkeypatch): baseline = model() calls = [] def run(arguments, *, data=None, **_kwargs): calls.append((arguments, data)) if arguments[:3] == ["docker", "image", "inspect"]: return IMAGE_ID.encode("ascii") if arguments[-2:] == ["--format", "json"]: return data if data and data.startswith(b"{") else json.dumps(baseline).encode() return b"" monkeypatch.setattr(module, "_run", run) return baseline, calls def test_compose_uses_protected_code_and_two_config_views(compose): baseline, calls = compose rendered = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)) service = rendered["services"]["deploy-control"] assert service["image"] == IMAGE_ID assert service["pull_policy"] == "never" volumes = service["volumes"] assert { "type": "bind", "source": module._expected_revision_path(REVISION).as_posix(), "target": module.EXECUTABLE_TARGET, "read_only": True, } in volumes assert { "type": "volume", "source": module.VOLUME, "target": module.TARGET, "volume": {"nocopy": True}, } in volumes assert { "type": "volume", "source": "itworx-forge-master-key-backup", "target": "/forge-master-key-backup", "volume": {"nocopy": True}, } in volumes assert { "type": "bind", "source": module._expected_config_path().as_posix(), "target": module.CONFIG_TARGET, "read_only": True, } in volumes assert len(volumes) == 5 volumes.remove(next(item for item in volumes if item["target"] == module.EXECUTABLE_TARGET)) volumes.remove(next(item for item in volumes if item["target"] == module.TARGET)) volumes.remove(next(item for item in volumes if item["target"] == "/forge-master-key-backup")) volumes.remove(next(item for item in volumes if item["target"] == module.CONFIG_TARGET)) assert rendered["volumes"] == { module.VOLUME: {"name": module.VOLUME, "external": True}, "itworx-forge-master-key-backup": { "name": "itworx-forge-master-key-backup", "external": True }, } assert rendered["services"]["runner"] == baseline["services"]["runner"] assert service["environment"] == baseline["services"]["deploy-control"]["environment"] compose_calls = [ arguments for arguments, _ in calls if arguments[:2] == ["docker", "compose"] ] assert compose_calls assert all("--project-directory" in arguments for arguments in compose_calls) assert module.ROOT.as_posix() in compose_calls[0] assert all(module.MOUNTPOINT.as_posix() in arguments for arguments in compose_calls[1:]) assert all("--env-file" in arguments and "/dev/null" in arguments for arguments in compose_calls) assert all("-f" in arguments and "-" in arguments for arguments in compose_calls) assert calls[0][1] == ORIGINAL_COMPOSE def test_compose_allows_only_the_fixed_managed_appdata_volume_mount(compose): baseline, _calls = compose mount = { "type": "volume", "source": module.MANAGED_APPDATA_VOLUME, "target": module.MANAGED_APPDATA_MOUNTPOINT.as_posix(), } baseline["services"][module.SERVICE]["volumes"].append(mount) baseline["volumes"] = { module.MANAGED_APPDATA_VOLUME: { "name": module.MANAGED_APPDATA_VOLUME, "external": True, } } rendered = json.loads(module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID)) assert mount in rendered["services"][module.SERVICE]["volumes"] assert rendered["volumes"][module.MANAGED_APPDATA_VOLUME] == { "name": module.MANAGED_APPDATA_VOLUME, "external": True, } def test_compose_rejects_a_managed_appdata_target_alias(compose): baseline, _calls = compose baseline["services"][module.SERVICE]["volumes"].append({ "type": "volume", "source": module.MANAGED_APPDATA_VOLUME, "target": "/var/lib/docker/volumes/foreign/_data", }) baseline["volumes"] = { module.MANAGED_APPDATA_VOLUME: { "name": module.MANAGED_APPDATA_VOLUME, "external": True, } } with pytest.raises(module.VolumeTransitionError, match="managed_appdata"): module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID) def test_source_relative_controller_bind_uses_original_compose_directory(monkeypatch): def run(arguments, *, data=None, **kwargs): if arguments[:3] == ["docker", "image", "inspect"]: return IMAGE_ID.encode() normalized = model() directory = arguments[arguments.index("--project-directory") + 1] if arguments[-2:] == ["--format", "json"]: normalized["services"][module.SERVICE]["volumes"][1]["source"] = directory + "/controller" return json.dumps(normalized).encode() monkeypatch.setattr(module, "_run", run) original = ORIGINAL_COMPOSE + b" volumes:\n - ./controller:/opt/gitea-deploy:ro\n" rendered = json.loads(module.rendered_model(original, REVISION, IMAGE_ID)) mounts = rendered["services"][module.SERVICE]["volumes"] assert next(m["source"] for m in mounts if m["target"] == module.EXECUTABLE_TARGET) == module._mount_source_for_revision(REVISION) @pytest.mark.parametrize( "damage", [ "target_collision", "foreign_volume", "graph_rw", "relative_bind", "env_file", "backup_target_collision", "backup_volume_alias", "backup_target_child_alias", "backup_definition_collision", ], ) def test_compose_rejects_unsafe_or_unpinned_activation_inputs(compose, damage): baseline, _ = compose if damage == "target_collision": baseline["services"]["deploy-control"]["volumes"].append( {"type": "bind", "source": "/unsafe", "target": module.TARGET} ) elif damage == "foreign_volume": baseline["services"]["runner"]["volumes"].append( {"type": "volume", "source": module.VOLUME, "target": "/foreign"} ) elif damage == "graph_rw": baseline["services"]["deploy-control"]["volumes"][0]["read_only"] = False elif damage == "relative_bind": baseline["services"]["runner"]["volumes"][0]["source"] = "./unsafe" elif damage == "env_file": baseline["services"]["deploy-control"]["env_file"] = [".env"] elif damage == "backup_target_collision": baseline["services"]["deploy-control"]["volumes"].append( {"type": "bind", "source": "/foreign", "target": "/forge-master-key-backup"} ) elif damage == "backup_volume_alias": baseline["services"]["runner"]["volumes"].append( { "type": "volume", "source": "itworx-forge-master-key-backup", "target": "/foreign-backup", } ) elif damage == "backup_target_child_alias": baseline["services"]["runner"]["volumes"].append( {"type": "bind", "source": "/foreign", "target": "/forge-master-key-backup/state"} ) else: baseline["volumes"] = { "itworx-forge-master-key-backup": {"name": "foreign", "external": True} } with pytest.raises(module.VolumeTransitionError): module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID) def test_pre_promotion_accepts_the_healthy_controller_without_the_new_mount(compose): baseline, _calls = compose reference = "registry.example/controller:stable" baseline["services"][module.SERVICE]["image"] = reference current = { "Image": IMAGE_ID, "Config": { "Image": reference, "Labels": { "com.docker.compose.project": "gitea-deploy-runners", "com.docker.compose.service": module.SERVICE, }, }, "State": {"Health": {"Status": "healthy"}}, "Mounts": [ { "Type": "volume", "Name": module.VOLUME, "Source": module.MOUNTPOINT.as_posix(), "Destination": module.TARGET, "RW": True, } ], } original = f"name: gitea-deploy-runners\nservices:\n deploy-control:\n image: {reference}\n".encode() rendered = json.loads( module.rendered_model(original, REVISION, IMAGE_ID, current_container=current) ) assert not any(mount.get("Destination") == "/forge-master-key-backup" for mount in current["Mounts"]) assert any( mount.get("target") == "/forge-master-key-backup" and mount.get("source") == "itworx-forge-master-key-backup" for mount in rendered["services"][module.SERVICE]["volumes"] ) def test_compose_tag_replacement_cannot_change_controller_startup_image(compose, monkeypatch): _baseline, calls = compose original_run = module._run def replaced_tag(arguments, **kwargs): if arguments[:3] == ["docker", "image", "inspect"]: return ("sha256:" + "c" * 64).encode("ascii") return original_run(arguments, **kwargs) monkeypatch.setattr(module, "_run", replaced_tag) with pytest.raises(module.VolumeTransitionError, match="image_reference_changed"): module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID) assert not any( arguments[-1:] == ["deploy-control"] and "up" in arguments for arguments, _ in calls ) @pytest.mark.parametrize("damage", [None, "foreign_reference", "wrong_project", "wrong_service", "unhealthy", "digest_reference", "wrong_image", "normalized_reference", "interpolation"]) def test_drifted_tag_requires_healthy_current_controller_provenance(compose, monkeypatch, damage): baseline, calls = compose reference = "registry.example/controller:stable" if damage == "digest_reference": reference = "registry.example/controller@sha256:" + "c" * 64 baseline["services"][module.SERVICE]["image"] = reference current = { "Image": IMAGE_ID, "Config": {"Image": reference, "Labels": { "com.docker.compose.project": "gitea-deploy-runners", "com.docker.compose.service": module.SERVICE, }}, "State": {"Health": {"Status": "healthy"}}, } if damage == "foreign_reference": current["Config"]["Image"] = "registry.example/foreign:stable" elif damage == "wrong_project": current["Config"]["Labels"]["com.docker.compose.project"] = "foreign" elif damage == "wrong_service": current["Config"]["Labels"]["com.docker.compose.service"] = "foreign" elif damage == "unhealthy": current["State"]["Health"]["Status"] = "unhealthy" elif damage == "wrong_image": current["Image"] = "sha256:" + "c" * 64 original_run = module._run def drifted_tag(arguments, **kwargs): if arguments[:3] == ["docker", "image", "inspect"]: return ("sha256:" + "c" * 64).encode("ascii") return original_run(arguments, **kwargs) monkeypatch.setattr(module, "_run", drifted_tag) source_reference = reference if damage == "normalized_reference": source_reference = "registry.example/foreign:stable" elif damage == "interpolation": source_reference = "${CONTROLLER_IMAGE}" original = f"name: gitea-deploy-runners\nservices:\n deploy-control:\n image: {source_reference}\n".encode() if damage: with pytest.raises(module.VolumeTransitionError, match="image_reference_(changed|invalid)"): module.rendered_model(original, REVISION, IMAGE_ID, current_container=current) else: rendered = json.loads(module.rendered_model(original, REVISION, IMAGE_ID, current_container=current)) assert rendered["services"][module.SERVICE]["image"] == IMAGE_ID assert rendered["services"][module.SERVICE]["pull_policy"] == "never" assert not any("up" in arguments or "pull" in arguments for arguments, _ in calls) def test_config_snapshot_has_exact_allowlist_and_broker_token(): entries = { "projects.json": (b"{}", 0o600), "broker-token": (b"secret", 0o600), "gitea-validation.json": (b"{}", 0o600), "gitea-validation-token": (b"secret", 0o600), "private-tls.json": (b"{}", 0o600), "appops-enrollment-bootstrap": (b"secret", 0o600), "webhook-secrets/forge": (b"secret", 0o600), "appops-deployment-secrets/repopulse": (b"secret", 0o600), "status_server.py": (b"code", 0o644), } directories = { "": 0o755, "webhook-secrets": 0o700, "appops-deployment-secrets": 0o700, } config, config_dirs = module._config_tree(entries, directories) assert set(config) == set(entries) - {"status_server.py"} assert "broker-token" in config assert config_dirs == directories def _forge_secret_entries(): return { name: (f'fixture:{index}'.encode(), module.FORGE_SECRET_MODE) for index, name in enumerate(sorted(module.FORGE_SECRET_OWNERS)) } def test_complete_forge_secret_bundle_has_exact_export_modes_and_owners(): entries = { 'projects.json': (b'{}', 0o600), 'broker-token': (b'fixture-token', 0o600), 'gitea-validation.json': (b'{}', 0o600), 'gitea-validation-token': (b'fixture-validation-token', 0o600), } | _forge_secret_entries() directories = {'': 0o755, module.FORGE_SECRET_DIRECTORY: 0o700} config, config_dirs = module._config_tree(entries, directories) archive_bytes = module._release_tar_bytes(REVISION, (config, config_dirs)) restored, restored_dirs = module._read_tar_tree(archive_bytes) assert restored == config assert restored_dirs == config_dirs with tarfile.open(fileobj=io.BytesIO(archive_bytes), mode='r:') as archive: for name, owner in module.FORGE_SECRET_OWNERS.items(): member = archive.getmember(name) assert (member.uid, member.gid) == owner assert member.mode == module.FORGE_SECRET_MODE assert archive.getmember('broker-token').uid == 0 assert archive.getmember('broker-token').gid == 0 @pytest.mark.parametrize('damage', ['partial', 'unknown', 'wrong_mode', 'nested']) def test_forge_secret_snapshot_requires_the_complete_exact_private_tree(damage): entries = { 'projects.json': (b'{}', 0o600), 'broker-token': (b'fixture-token', 0o600), 'gitea-validation.json': (b'{}', 0o600), 'gitea-validation-token': (b'fixture-validation-token', 0o600), } | _forge_secret_entries() directories = {'': 0o755, module.FORGE_SECRET_DIRECTORY: 0o700} if damage == 'partial': entries.pop(min(module.FORGE_SECRET_OWNERS)) elif damage == 'unknown': entries[f'{module.FORGE_SECRET_DIRECTORY}/unexpected.secret'] = (b'x', 0o400) elif damage == 'wrong_mode': name = min(module.FORGE_SECRET_OWNERS) entries[name] = (entries[name][0], 0o600) else: directories[f'{module.FORGE_SECRET_DIRECTORY}/nested'] = 0o700 with pytest.raises(module.VolumeTransitionError): module._config_tree(entries, directories) def test_forge_secret_tar_owner_drift_is_rejected(): output = io.BytesIO() with tarfile.open(fileobj=output, mode='w', format=tarfile.USTAR_FORMAT) as archive: member = tarfile.TarInfo( f'{module.FORGE_SECRET_DIRECTORY}/runtime_database_url.secret' ) member.size = 1 member.mode = module.FORGE_SECRET_MODE member.uid = member.gid = 0 archive.addfile(member, io.BytesIO(b'x')) with pytest.raises(module.VolumeTransitionError, match='entry_unsafe'): module._read_tar_tree(output.getvalue()) def test_config_writer_uses_archive_mode_to_preserve_forge_leaf_owners(monkeypatch): tree = ( { 'projects.json': (b'{}', 0o600), 'broker-token': (b'fixture-token', 0o600), 'gitea-validation.json': (b'{}', 0o600), 'gitea-validation-token': (b'fixture-validation-token', 0o600), } | _forge_secret_entries(), {'': 0o755, module.FORGE_SECRET_DIRECTORY: 0o700}, ) writer_id = 'f' * 64 commands = [] copy_payloads = [] def run(arguments, *, data=None, **_kwargs): commands.append(arguments) if arguments == ['docker', 'create']: return writer_id.encode() if arguments[:2] == ['docker', 'wait']: return b'0' if arguments[:2] == ['docker', 'cp']: copy_payloads.append(data) return b'' monkeypatch.setattr(module, '_read_partial_config_leaf', lambda: None) monkeypatch.setattr(module, 'ensure_volume', lambda **_kwargs: None) monkeypatch.setattr(module, '_release_tar_bytes', lambda *_args, **_kwargs: b'archive') monkeypatch.setattr(module, '_remove_stale_writer', lambda *_args, **_kwargs: None) monkeypatch.setattr(module, '_writer_spec', lambda *_args, **_kwargs: ['docker', 'create']) monkeypatch.setattr(module, '_inspect_writer', lambda *_args, **_kwargs: {}) monkeypatch.setattr(module, '_read_config_leaf', lambda: tree) monkeypatch.setattr(module, '_run', run) module._install_config_tree(tree, IMAGE_ID) assert [command for command in commands if command[:2] == ['docker', 'cp']] == [[ 'docker', 'cp', '-a', '-', f'{writer_id}:{module.TARGET}/{module.CONFIG_DIR}', ]] assert copy_payloads == [b'archive'] def test_partial_forge_bundle_fails_during_promotion_preparation_before_stop( monkeypatch, tmp_path ): valid_config_tree = module._config_tree events = _prepare_promotion_doubles(monkeypatch, tmp_path) monkeypatch.setattr(module, '_config_tree', valid_config_tree) partial = { 'projects.json': (b'{}', 0o600), 'broker-token': (b'fixture-token', 0o600), 'gitea-validation.json': (b'{}', 0o600), 'gitea-validation-token': (b'fixture-validation-token', 0o600), min(module.FORGE_SECRET_OWNERS): (b'fixture', module.FORGE_SECRET_MODE), } directories = {'': 0o755, module.FORGE_SECRET_DIRECTORY: 0o700} identity = { 'sourceRevision': 'b' * 40, 'sourceTree': 'c' * 40, 'controllerVersion': '2.1.0', } monkeypatch.setattr( module, '_snapshot_attested_controller', lambda *_args, **_kwargs: (partial, directories, identity), ) with pytest.raises(module.VolumeTransitionError, match='forge_secret_bundle_incomplete'): module.prepare_promotion( tmp_path / 'stage', source_revision='d' * 40, source_tree='e' * 40, controller_version='2.2.0', image_id=IMAGE_ID, ) assert 'backup_volume_prepared' not in events @pytest.mark.skipif(os.name == 'nt' or not hasattr(os, 'geteuid') or os.geteuid() != 0, reason='fixed Forge ownership roundtrip requires Linux root') def test_forge_secret_filesystem_capture_and_private_stage_keep_exact_owner(tmp_path): config_root = tmp_path / 'config' config_root.mkdir(mode=0o700) secret_root = config_root / module.FORGE_SECRET_DIRECTORY secret_root.mkdir(mode=0o700) for index, (relative, owner) in enumerate(sorted(module.FORGE_SECRET_OWNERS.items())): path = config_root / relative path.write_bytes(f'fixture:{index}'.encode()) os.chown(path, *owner) path.chmod(module.FORGE_SECRET_MODE) descriptor = os.open(config_root, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW) try: entries, directories = module._read_tree_fd(descriptor, allow_forge_secrets=True) finally: os.close(descriptor) config, config_dirs = module._config_tree(entries, directories, allow_partial=True) payload = module._release_tar_bytes(REVISION, (config, config_dirs)) roundtrip, roundtrip_dirs = module._read_tar_tree(payload) assert roundtrip == config assert roundtrip_dirs == config_dirs staged = tmp_path / 'private-stage' module._write_private_tree(staged, config, config_dirs) for relative, owner in module.FORGE_SECRET_OWNERS.items(): info = (staged / relative).stat(follow_symlinks=False) assert (info.st_uid, info.st_gid) == owner assert stat.S_IMODE(info.st_mode) == module.FORGE_SECRET_MODE def test_unknown_config_file_is_rejected(): entries = { "projects.json": (b"{}", 0o600), "broker-token": (b"secret", 0o600), "gitea-validation.json": (b"{}", 0o600), "gitea-validation-token": (b"secret", 0o600), "shell.py": (b"unexpected", 0o600), } with pytest.raises(module.VolumeTransitionError, match="config_snapshot_unknown"): module._config_tree(entries, {"": 0o755}) def test_partial_config_tree_is_accepted_only_for_crash_resume(): partial = {"projects.json": (b"{}", 0o600), "broker-token": (b"secret", 0o600)} assert module._config_tree(partial, {"": 0o755}, allow_partial=True) == ( partial, {"": 0o755} ) with pytest.raises(module.VolumeTransitionError, match="snapshot_incomplete"): module._config_tree(partial, {"": 0o755}) def test_config_snapshot_requires_private_mode_for_every_file(): entries = { "projects.json": (b"{}", 0o644), "broker-token": (b"secret", 0o600), "gitea-validation.json": (b"{}", 0o600), "gitea-validation-token": (b"secret", 0o600), } with pytest.raises(module.VolumeTransitionError, match="config_file_unsafe"): module._config_tree(entries, {"": 0o755}) def test_previous_private_tls_is_validated_from_the_attested_config_snapshot( tmp_path, monkeypatch ): previous = tmp_path / "previous-controller" previous.mkdir(mode=0o700) (previous / "status_server.py").write_text("# captured validator\n", encoding="utf-8") (previous / "private-tls.json").write_bytes(b"captured private config") calls = [] monkeypatch.setattr(module, "_run", lambda arguments, **kwargs: calls.append((arguments, kwargs)) or b"") module._validate_previous_private_tls(tmp_path) assert calls == [( ["python3", str(previous / "status_server.py"), "--check-private-tls-config"], {"max_output": 4096}, )] (previous / "private-tls.json").unlink() calls.clear() module._validate_previous_private_tls(tmp_path) assert calls == [] def test_staged_config_checks_run_new_artifact_with_copied_config_and_bounded_output( tmp_path, monkeypatch ): stage_root = tmp_path / "source" stage_root.mkdir(mode=0o700) release_tree = ( { "status_server.py": (b"new status server", 0o644), "controller-release.json": (b"new release identity", 0o644), }, {"": 0o755}, ) config_tree = ( { "projects.json": (b"{\"repo\":{}}", 0o600), "broker-token": (b"private broker token", 0o600), "gitea-validation.json": (b"{}", 0o600), "gitea-validation-token": (b"private validation token", 0o600), "private-tls.json": (b"private TLS config", 0o600), "webhook-secrets/repo": (b"private webhook secret", 0o600), }, {"": 0o755, "webhook-secrets": 0o700}, ) calls = [] validation_root = stage_root / "validation" def run(arguments, *, data=None, max_output=module.MAX_BYTES): calls.append((arguments, max_output)) assert (validation_root / "status_server.py").read_bytes() == b"new status server" assert (validation_root / "projects.json").read_bytes() == b"{\"repo\":{}}" assert (validation_root / "broker-token").read_bytes() == b"private broker token" assert (validation_root / "webhook-secrets" / "repo").read_bytes() == b"private webhook secret" return b"validated" monkeypatch.setattr(module, "_write_private_tree", _write_test_tree) monkeypatch.setattr(module, "_run", run) validator = getattr(module, "_validate_staged_configuration", None) assert callable(validator), "staged configuration validation helper is required" validator(stage_root, release_tree, config_tree) assert calls == [ (["python3", str(validation_root / "status_server.py"), "--check-artifact"], 4096), (["python3", str(validation_root / "status_server.py"), "--check-config"], 4096), (["python3", str(validation_root / "status_server.py"), "--check-validation-config"], 4096), (["python3", str(validation_root / "status_server.py"), "--check-validation-api"], 4096), (["python3", str(validation_root / "status_server.py"), "--check-private-tls-config"], 4096), ] assert not validation_root.exists() def test_staged_config_check_failure_is_sanitized_and_cleans_private_validation_tree( tmp_path, monkeypatch ): stage_root = tmp_path / "source" stage_root.mkdir(mode=0o700) release_tree = ( { "status_server.py": (b"new status server", 0o644), "controller-release.json": (b"new release identity", 0o644), }, {"": 0o755}, ) config_tree = ( { "projects.json": (b"{}", 0o600), "broker-token": (b"top-secret-broker", 0o600), "gitea-validation.json": (b"{}", 0o600), "gitea-validation-token": (b"top-secret-validation", 0o600), }, {"": 0o755}, ) class FailedProcess: returncode = 1 stdout = b"token=top-secret-validation" stderr = b"secret=top-secret-broker" monkeypatch.setattr(module, "_write_private_tree", _write_test_tree) monkeypatch.setattr(module.subprocess, "run", lambda *args, **kwargs: FailedProcess()) validator = getattr(module, "_validate_staged_configuration", None) assert callable(validator), "staged configuration validation helper is required" with pytest.raises(module.VolumeTransitionError, match="command_failed") as error: validator(stage_root, release_tree, config_tree) assert "top-secret" not in str(error.value) assert not (stage_root / "validation").exists() def _write_test_tree(path, entries, directories): path.mkdir(mode=0o700) for relative, _mode in sorted(directories.items(), key=lambda item: item[0].count("/")): if relative: (path / relative).mkdir(parents=True, exist_ok=True) for relative, (contents, _mode) in entries.items(): destination = path / relative destination.parent.mkdir(parents=True, exist_ok=True) destination.write_bytes(contents) @pytest.mark.skipif(os.name == "nt", reason="private TLS snapshot guard requires POSIX symlink semantics") def test_previous_private_tls_rejects_symlinked_snapshot(tmp_path): previous = tmp_path / "previous-controller" previous.mkdir(mode=0o700) (previous / "status_server.py").write_text("# captured validator\n", encoding="utf-8") target = tmp_path / "tls.json" target.write_bytes(b"unsafe") (previous / "private-tls.json").symlink_to(target) with pytest.raises(module.VolumeTransitionError, match="private_tls_unsafe"): module._validate_previous_private_tls(tmp_path) @pytest.mark.parametrize("kind", ["artifacts", "config"]) def test_writer_is_exact_image_only_bounded_and_has_no_host_paths(kind): command = module._writer_spec(IMAGE_ID, REVISION, kind=kind) assert command[0:3] == ["docker", "create", "--name"] assert module.WRITER_NAME in command assert IMAGE_ID in command assert "--network" in command and command[command.index("--network") + 1] == "none" assert "--read-only" in command and "--cap-drop" in command assert "--cpu-quota" in command and "--memory" in command and "--pids-limit" in command assert "--mount" in command mount = command[command.index("--mount") + 1] assert mount == f"type=volume,source={module.VOLUME},target={module.TARGET},volume-nocopy" assert not any(item.startswith(("/mnt/", "/var/lib/")) for item in command) def test_promoted_compose_actions_use_only_stdin_and_fixed_service(compose): baseline, calls = compose rendered = module.rendered_model(ORIGINAL_COMPOSE, REVISION, IMAGE_ID) module.compose_action(rendered, "stop") module.compose_action(rendered, "up") assert calls[-2][0][-2:] == ["stop", module.SERVICE] up_index = calls[-1][0].index("up") assert calls[-1][0][up_index:] == [ "up", "-d", "--force-recreate", "--pull", "never", "--no-deps", module.SERVICE ] assert calls[-2][1] == rendered assert calls[-1][1] == rendered assert baseline["name"] == "gitea-deploy-runners" def test_rollback_uses_no_dependencies_when_recreating_prior_controller(compose, tmp_path, monkeypatch): _baseline, calls = compose previous = REVISION target = "c" * 40 previous_compose = module.rendered_model(ORIGINAL_COMPOSE, previous, IMAGE_ID) target_compose = module.rendered_model(ORIGINAL_COMPOSE, target, IMAGE_ID) receipt = { "phase": "activated", "imageId": IMAGE_ID, "previousRevision": previous, "sourceRevision": target, } values = { "controller-before.tar": module._release_tar_bytes( previous, ({"controller-release.json": (b"previous release", 0o644)}, {"": 0o755}), ), "controller-after.tar": b"unused by isolated rollback test", "mirror-before.tar": b"unused by isolated rollback test", "compose-rollback.json": previous_compose, "compose-after.json": target_compose, } current = { "Mounts": [{ "Destination": module.EXECUTABLE_TARGET, "Source": module._expected_revision_path(previous).as_posix(), }] } monkeypatch.setattr(module, "_load_transaction", lambda _backup: (1, receipt, values)) monkeypatch.setattr(module.os, "close", lambda _descriptor: None) monkeypatch.setattr(module, "_controller_image_inspect", lambda *args, **kwargs: current) monkeypatch.setattr(module, "_verify_release_leaf", lambda *args, **kwargs: True) monkeypatch.setattr(module, "_stop_current_controller", lambda *args, **kwargs: None) monkeypatch.setattr(module, "_sync_controller_mirror", lambda *args, **kwargs: None) monkeypatch.setattr(module, "_ensure_root_compose", lambda *args, **kwargs: None) monkeypatch.setattr(module, "_verify_compose_image", lambda *args, **kwargs: None) monkeypatch.setattr(module, "_verify_active_mount", lambda *args, **kwargs: None) monkeypatch.setattr(module, "_set_promotion_phase", lambda *args, **kwargs: None) module.rollback_promotion(tmp_path / "backup") up_calls = [ arguments for arguments, _data in calls if arguments[:2] == ["docker", "compose"] and "up" in arguments ] assert len(up_calls) == 1 up_index = up_calls[0].index("up") assert up_calls[0][up_index:] == [ "up", "-d", "--force-recreate", "--pull", "never", "--no-deps", module.SERVICE ] def test_installer_has_no_shared_executable_stage_or_compose_path(): source = (ROOT / "scripts/upgrade_deployment_controller.sh").read_text(encoding="utf-8") assert "mktemp -d -p /tmp \"itworx-controller-upgrade-" in source assert 'docker compose --project-directory "$ROOT" -f "$COMPOSE_FILE"' not in source assert 'python3 "${work}/${PREPARER_NAME}"' in source assert 'mv -- "$CONTROLLER_DIR"' not in source assert 'cp -a -- "$CONTROLLER_DIR"' not in source assert "ops/gitea-deploy-broker/mcp_hub_release_store.py" in source def test_installer_release_order_matches_renderer_contract(): renderer_spec = importlib.util.spec_from_file_location( "controller_renderer", ROOT / "scripts/prepare_controller_artifact.py" ) renderer = importlib.util.module_from_spec(renderer_spec) renderer_spec.loader.exec_module(renderer) assert tuple(module.RELEASE_FILES) == tuple(renderer._ARTIFACT_FILES) assert module.RELEASE_FILES[:3] == ( "controller_paths.py", "mcp_hub_release_store.py", "hub_permission_preparation.py" ) assert module.RELEASE_FILES[module.RELEASE_FILES.index("forge_first_install.py") + 1] == ( "forge_database_credentials.py" ) helper_index = module.RELEASE_FILES.index("forge_database_credentials.py") + 1 assert module.RELEASE_FILES[helper_index : helper_index + 2] == ( "forge_backup_paths.py", "forge_secret_bundle.py" ) assert module.LEGACY_RELEASE_FILES_WITH_ROLE_HELPER == tuple( name for name in module.RELEASE_FILES if name not in {"forge_backup_paths.py", "forge_secret_bundle.py"} ) assert module.LEGACY_RELEASE_FILES_WITHOUT_ROLE_HELPER == tuple( name for name in module.LEGACY_RELEASE_FILES_WITH_ROLE_HELPER if name != "forge_database_credentials.py" ) installer = (ROOT / "scripts/upgrade_deployment_controller.sh").read_text(encoding="utf-8") module_list = installer.split("module_files=(", 1)[1].split(")", 1)[0].split() module_helper_index = module_list.index("forge_database_credentials.py") + 1 assert module_list[module_helper_index : module_helper_index + 2] == [ "forge_backup_paths.py", "forge_secret_bundle.py" ] source_list = installer.split("source_paths=(", 1)[1].split(")", 1)[0].split() source_helper_index = source_list.index("ops/gitea-deploy-broker/forge_database_credentials.py") + 1 assert source_list[source_helper_index : source_helper_index + 2] == [ "ops/gitea-deploy-broker/forge_backup_paths.py", "ops/gitea-deploy-broker/forge_secret_bundle.py", ] def test_current_release_digest_binds_the_role_password_helper(): entries = {name: (f"source:{name}".encode(), 0o644) for name in module.RELEASE_FILES} expected = module._artifact_digest(entries) assert module._matching_release_files(entries, expected) == module.RELEASE_FILES entries["forge_database_credentials.py"] = (b"tampered helper", 0o644) with pytest.raises(module.VolumeTransitionError, match="release_identity_invalid"): module._matching_release_files(entries, expected) def test_previous_role_helper_release_digest_remains_recognized_and_bound(): files = module.LEGACY_RELEASE_FILES_WITH_ROLE_HELPER entries = {name: (f"source:{name}".encode(), 0o644) for name in files} expected = module._artifact_digest(entries, files) assert module._matching_release_files(entries, expected) == files entries["forge_database_credentials.py"] = (b"tampered role helper", 0o644) with pytest.raises(module.VolumeTransitionError, match="release_identity_invalid"): module._matching_release_files(entries, expected) def test_historical_config_root_manifest_order_and_digest_remain_exact(): files = HISTORICAL_CONFIG_ROOT_RELEASE_FILES assert module.LEGACY_RELEASE_FILES_WITH_CONFIG_ROOT == files entries = {name: (f"source:{name}".encode(), 0o644) for name in files} expected = module._artifact_digest(entries, files) assert module._matching_release_files(entries, expected) == files entries["status_server.py"] = (b"tampered historical file", 0o644) with pytest.raises(module.VolumeTransitionError, match="release_identity_invalid"): module._matching_release_files(entries, expected) def test_activated_controller_must_have_the_exact_backup_mount(monkeypatch): mounts = [ { "Type": "bind", "Source": module._expected_revision_path(REVISION).as_posix(), "Destination": module.EXECUTABLE_TARGET, "RW": False, }, { "Type": "volume", "Name": module.VOLUME, "Source": module.MOUNTPOINT.as_posix(), "Destination": module.TARGET, "RW": True, }, { "Type": "bind", "Source": module._expected_config_path().as_posix(), "Destination": module.CONFIG_TARGET, "RW": False, }, { "Type": "bind", "Source": module.DOCKER_ROOT.as_posix(), "Destination": module.DOCKER_ROOT.as_posix(), "RW": False, }, { "Type": "volume", "Name": "itworx-forge-master-key-backup", "Source": "/var/lib/docker/volumes/itworx-forge-master-key-backup/_data", "Destination": "/forge-master-key-backup", "RW": True, }, ] item = {"Mounts": mounts} monkeypatch.setattr(module, "_controller_image_inspect", lambda _image_id: item) monkeypatch.setattr(module, "_read_release_leaf", lambda _revision: ({}, {"": 0o755})) monkeypatch.setattr(module, "_release_identity", lambda _entries: {"sourceRevision": REVISION}) monkeypatch.setattr(module, "_read_config_leaf", lambda: ({}, {"": 0o755})) module._verify_active_mount(REVISION, IMAGE_ID) mounts.pop() with pytest.raises(module.VolumeTransitionError, match="backup_mount"): module._verify_active_mount(REVISION, IMAGE_ID) @pytest.mark.parametrize( "damage", ["backup_readonly", "backup_name", "backup_source", "backup_alias", "docker_root_rw"], ) def test_activated_controller_rejects_backup_or_docker_root_mount_drift( monkeypatch, damage ): mounts = [ { "Type": "bind", "Source": module._expected_revision_path(REVISION).as_posix(), "Destination": module.EXECUTABLE_TARGET, "RW": False, }, { "Type": "volume", "Name": module.VOLUME, "Source": module.MOUNTPOINT.as_posix(), "Destination": module.TARGET, "RW": True, }, { "Type": "bind", "Source": module._expected_config_path().as_posix(), "Destination": module.CONFIG_TARGET, "RW": False, }, { "Type": "bind", "Source": module.DOCKER_ROOT.as_posix(), "Destination": module.DOCKER_ROOT.as_posix(), "RW": False, }, { "Type": "volume", "Name": module.BACKUP_VOLUME, "Source": module.BACKUP_MOUNTPOINT.as_posix(), "Destination": module.BACKUP_TARGET, "RW": True, }, ] if damage == "backup_readonly": mounts[-1]["RW"] = False elif damage == "backup_name": mounts[-1]["Name"] = "foreign-volume" elif damage == "backup_source": mounts[-1]["Source"] = "/var/lib/docker/volumes/foreign/_data" elif damage == "backup_alias": mounts.append({ "Type": "bind", "Source": "/foreign", "Destination": f"{module.BACKUP_TARGET}/child", "RW": True, }) else: mounts[-2]["RW"] = True monkeypatch.setattr(module, "_controller_image_inspect", lambda _image_id: {"Mounts": mounts}) monkeypatch.setattr(module, "_read_release_leaf", lambda _revision: ({}, {"": 0o755})) monkeypatch.setattr(module, "_release_identity", lambda _entries: {"sourceRevision": REVISION}) monkeypatch.setattr(module, "_read_config_leaf", lambda: ({}, {"": 0o755})) with pytest.raises(module.VolumeTransitionError): module._verify_active_mount(REVISION, IMAGE_ID) @pytest.mark.skipif(os.name == "nt", reason="dirfd swap test requires POSIX openat") def test_mirror_updates_stay_on_the_open_directory_after_path_swap(tmp_path, monkeypatch): original = tmp_path / "controller" moved = tmp_path / "controller-pinned" original.mkdir() before_entries = { "controller_paths.py": (b"prior paths", 0o644), "mcp_hub_release_store.py": (b"prior Hub helper", 0o644), } for name, (value, _mode) in before_entries.items(): (original / name).write_bytes(value) prior = { name: (f"prior:{name}".encode(), 0o644) for name in module.LEGACY_RELEASE_FILES_2_1_13 } prior["controller-release.json"] = (b"prior manifest", 0o644) updated = { name: (f"updated:{name}".encode(), 0o644) for name in module.RELEASE_FILES } updated["controller-release.json"] = (b"updated manifest", 0o644) prior_tar = module._release_tar_bytes("b" * 40, (prior, {"": 0o755})) updated_tar = module._release_tar_bytes("c" * 40, (updated, {"": 0o755})) mirror_tar = module._release_tar_bytes("a" * 40, (before_entries, {"": 0o755})) current_path = original monkeypatch.setattr( module, "_directory", lambda _path, _anchor: os.open(current_path, os.O_RDONLY | os.O_DIRECTORY), ) read_item = module._file_info_at_optional swapped = False def swap_after_first_read(descriptor, name): nonlocal swapped value = read_item(descriptor, name) if not swapped: original.rename(moved) original.mkdir() swapped = True return value monkeypatch.setattr(module, "_file_info_at_optional", swap_after_first_read) module._sync_controller_mirror( prior_tar, updated_tar, mirror_tar, direction="forward" ) assert swapped and list(original.iterdir()) == [] assert (moved / "controller_paths.py").read_bytes() == b"updated:controller_paths.py" assert (moved / "mcp_hub_release_store.py").read_bytes() == b"updated:mcp_hub_release_store.py" current_path = moved monkeypatch.setattr(module, "_file_info_at_optional", read_item) module._sync_controller_mirror( updated_tar, prior_tar, mirror_tar, direction="rollback" ) assert {path.name: path.read_bytes() for path in moved.iterdir()} == { name: value for name, (value, _mode) in before_entries.items() } @pytest.mark.skipif(os.name == "nt" or not hasattr(os, "geteuid") or os.geteuid() != 0, reason="private /tmp ownership validation requires root on POSIX") @pytest.mark.parametrize("tmp_mode", [0o700, 0o1777]) def test_private_stage_accepts_root_owned_tmp_with_only_approved_modes(monkeypatch, tmp_mode): import shutil import uuid work = Path("/tmp") / f"itworx-controller-upgrade-{uuid.uuid4().hex[:12]}.Ab3xY7" source = work / "source" source.mkdir(parents=True, mode=0o700) source.chmod(0o700) work.chmod(0o700) try: tmp_info = os.stat("/tmp", follow_symlinks=False) original_fstat = os.fstat def fstat_with_tmp_mode(descriptor): info = original_fstat(descriptor) if (info.st_dev, info.st_ino) == (tmp_info.st_dev, tmp_info.st_ino): return types.SimpleNamespace( st_uid=info.st_uid, st_gid=info.st_gid, st_mode=stat.S_IFDIR | tmp_mode, st_dev=info.st_dev, ) return info monkeypatch.setattr(os, "fstat", fstat_with_tmp_mode) module._validate_private_stage(source) finally: monkeypatch.undo() shutil.rmtree(work) @pytest.mark.skipif(os.name == "nt" or not hasattr(os, "geteuid") or os.geteuid() != 0, reason="private /tmp ownership validation requires root on POSIX") @pytest.mark.parametrize( ("target_name", "changes"), [ ("tmp", {"st_mode": stat.S_IFDIR | 0o777}), ("tmp", {"st_uid": 1000}), ("tmp", {"st_gid": 1000}), ("work", {"st_mode": stat.S_IFDIR | 0o755}), ("source", {"st_mode": stat.S_IFDIR | 0o755}), ("work", {"st_uid": 1000}), ("work", {"st_gid": 1000}), ("source", {"st_uid": 1000}), ("source", {"st_gid": 1000}), ("source", {"st_dev": 123456}), ], ) def test_private_stage_rejects_unsafe_tmp_and_child_metadata(monkeypatch, target_name, changes): import shutil import uuid work = Path("/tmp") / f"itworx-controller-upgrade-{uuid.uuid4().hex[:12]}.Ab3xY7" source = work / "source" source.mkdir(parents=True, mode=0o700) source.chmod(0o700) work.chmod(0o700) targets = {"tmp": Path("/tmp"), "work": work, "source": source} target_info = os.stat(targets[target_name], follow_symlinks=False) original_fstat = os.fstat def fstat_with_unsafe_metadata(descriptor): info = original_fstat(descriptor) if (info.st_dev, info.st_ino) == (target_info.st_dev, target_info.st_ino): values = { "st_uid": info.st_uid, "st_gid": info.st_gid, "st_mode": info.st_mode, "st_dev": info.st_dev, } values.update(changes) return types.SimpleNamespace(**values) return info try: monkeypatch.setattr(os, "fstat", fstat_with_unsafe_metadata) with pytest.raises(module.VolumeTransitionError, match="private_stage_invalid"): module._validate_private_stage(source) finally: monkeypatch.undo() shutil.rmtree(work) @pytest.mark.skipif(os.name == "nt" or not hasattr(os, "geteuid") or os.geteuid() != 0, reason="private /tmp ownership validation requires root on POSIX") def test_private_stage_rejects_acl_and_symlinked_source_directory(monkeypatch): import shutil import uuid work = Path("/tmp") / f"itworx-controller-upgrade-{uuid.uuid4().hex[:12]}.Ab3xY7" source = work / "source" source.mkdir(parents=True, mode=0o700) source.chmod(0o700) work.chmod(0o700) source_info = os.stat(source, follow_symlinks=False) original_listxattr = os.listxattr def listxattr_with_acl(descriptor): info = os.fstat(descriptor) values = original_listxattr(descriptor) if (info.st_dev, info.st_ino) == (source_info.st_dev, source_info.st_ino): return [*values, "system.posix_acl_access"] return values try: monkeypatch.setattr(os, "listxattr", listxattr_with_acl) with pytest.raises(module.VolumeTransitionError, match="acl_unsafe"): module._validate_private_stage(source) monkeypatch.setattr(os, "listxattr", original_listxattr) module._validate_private_stage(source) source.rename(work / "source-real") source.symlink_to(work / "source-real", target_is_directory=True) with pytest.raises(module.VolumeTransitionError, match="private_stage_invalid"): module._validate_private_stage(source) finally: shutil.rmtree(work) @pytest.mark.parametrize("directive", ["env_file", "label_file"]) def test_source_compose_preflight_rejects_host_file_reads_before_compose(compose, directive): _baseline, calls = compose unsafe = ( b"name: gitea-deploy-runners\nservices:\n deploy-control:\n" + f" image: x\n {directive}: /tmp/secret\n".encode() ) with pytest.raises(module.VolumeTransitionError, match="external_source_invalid"): module.rendered_model(unsafe, REVISION, IMAGE_ID) assert calls == [] @pytest.mark.parametrize( "source", [ b"services:\n app:\n 'env_file': /tmp/secret\n", b"services: {app: {\"include\": ./untrusted.yml}}\n", b"services:\n - \"build\": ./untrusted\n", b"services:\n app:\n \"\\u0065nv_file\": /tmp/secret\n", ], ) def test_source_compose_preflight_rejects_quoted_or_escaped_file_keys(source): with pytest.raises(module.VolumeTransitionError): module._preflight_source_compose(source) def test_preparer_uses_the_pinned_bundled_yaml_parser(): assert module.yaml.__version__ == "6.0.2" assert module.yaml.__name__.startswith("_appops_controller_yaml_") assert module.yaml.__with_libyaml__ is False assert Path(module.yaml.__file__).resolve().parent == ROOT / "scripts/vendor/yaml" def test_preparer_does_not_replace_a_host_cached_yaml_module(monkeypatch): host_yaml = types.ModuleType("yaml") host_yaml.__file__ = "/host/site-packages/yaml/__init__.py" monkeypatch.setitem(sys.modules, "yaml", host_yaml) original_sys_path = sys.path.copy() module_name = "_controller_volume_import_isolation_test" spec = importlib.util.spec_from_file_location( module_name, ROOT / "scripts/prepare_controller_hub_release_volume.py" ) assert spec is not None and spec.loader is not None isolated_module = importlib.util.module_from_spec(spec) monkeypatch.setitem(sys.modules, module_name, isolated_module) spec.loader.exec_module(isolated_module) assert sys.modules["yaml"] is host_yaml assert isolated_module.yaml.__name__.startswith("_appops_controller_yaml_") assert isolated_module.yaml.safe_load(b"services: {}") == {"services": {}} assert sys.path == original_sys_path def test_source_compose_preflight_ignores_comments_strings_and_block_scalars(): source = ( b"name: gitea-deploy-runners\n" b"# include: ./disabled.yml\n" b"services:\n app:\n" b" command: |\n" b" echo 'env_file: is command text'\n" b" echo include: is command text\n" b" labels: {note: \"build: is a label value\"}\n" ) module._preflight_source_compose(source) def test_source_compose_preflight_rejects_unresolvable_alias_keys(): source = b"services:\n app:\n *unsafe-key: value\n" with pytest.raises(module.VolumeTransitionError, match="compose_source_invalid"): module._preflight_source_compose(source) def test_source_compose_preflight_bounds_recursive_list_aliases(): source = b"services: {app: {image: fixed}}\nx-loop: &a [*a]\n" module._preflight_source_compose(source) @pytest.mark.parametrize( "source", [ b"services: {}\nconfigs:\n tls-material:\n file: /etc/ssl/private/key.pem\n", b"services: {}\nsecrets: {gitea-token: {environment: GITEA_TOKEN}}\n", ], ) def test_source_compose_preflight_rejects_local_config_and_secret_sources(source): with pytest.raises(module.VolumeTransitionError, match="external_source_invalid"): module._preflight_source_compose(source) def test_source_compose_preflight_allows_external_config_and_service_environment_fields(): source = ( b"configs:\n tls-material:\n external: true\n" b"services:\n app:\n environment:\n file: no-local-file-read\n" ) module._preflight_source_compose(source) def test_capture_program_is_fixed_descriptor_based_and_compiles(): legacy = module._controller_capture_source(config_root_in_code=True) protected = module._controller_capture_source(config_root_in_code=False) compile(legacy, "legacy-controller-capture", "exec") compile(protected, "protected-controller-capture", "exec") assert "config_root='/opt/gitea-deploy'" in legacy assert "config_root='/controller-config'" in protected assert "os.O_NOFOLLOW" in legacy and "os.fstat(fd)" in legacy assert "open(path,'rb')" not in legacy @pytest.mark.parametrize("legacy_config", [True, False]) def test_capture_archive_roundtrips_through_a_nonseekable_pipe(legacy_config): source = module._controller_capture_source(config_root_in_code=legacy_config) assert '__FORGE_' not in source assert 'runtime_database_url.secret' in source assert '/forge-master-key-backup' not in source compile(source, 'controller-capture', 'exec') statements = ast.parse(source).body start = next(i for i, node in enumerate(statements) if isinstance(node, ast.Assign) and any(isinstance(target, ast.Name) and target.id == "stream" for target in node.targets)) end = next(i for i, node in enumerate(statements[start:], start) if isinstance(node, ast.Expr) and isinstance(node.value, ast.Call) and isinstance(node.value.func, ast.Attribute) and isinstance(node.value.func.value, ast.Name) and node.value.func.value.id == "stream" and node.value.func.attr == "close") archive_program = ast.unparse(ast.Module(body=statements[start:end + 1], type_ignores=[])) fixture = ( "import io,sys,tarfile\n" "records={'deploy.py':(b'fixture source',0o644),'secrets/key':(b'fixture secret',0o600)}\n" "dirs={'secrets':0o700}\n" "forge_secret_owners={}\n" ) result = subprocess.run([sys.executable, "-c", fixture + archive_program], capture_output=True, timeout=10, check=False) assert result.returncode == 0, result.stderr.decode("utf-8", "replace") with tarfile.open(fileobj=io.BytesIO(result.stdout), mode="r:") as archive: assert archive.getnames() == ["secrets", "deploy.py", "secrets/key"] assert archive.getmember("secrets").mode == 0o700 assert archive.getmember("secrets/key").mode == 0o600 assert archive.extractfile("deploy.py").read() == b"fixture source" assert archive.extractfile("secrets/key").read() == b"fixture secret" assert all(member.uid == member.gid == 0 for member in archive.getmembers()) @pytest.mark.parametrize("files", [ module.LEGACY_RELEASE_FILES_WITHOUT_ROLE_HELPER, module.LEGACY_RELEASE_FILES_2_1_5, module.LEGACY_RELEASE_FILES_WITH_CONFIG_ROOT, module.LEGACY_RELEASE_FILES_BEFORE_FORGE, ]) def test_prior_release_capture_must_match_protected_git_tree(monkeypatch, files): entries = {name: (f"source:{name}".encode(), 0o644) for name in files} source_revision = "32a6eece0e3e1276946abdc04196c819daf02e2a" source_tree = "e0d7e80b79504b964d968e51e0d21c288cac8f0d" manifest = { "apiVersion": "itworx.controller-artifact/v1", "controllerVersion": "2.1.5", "sourceRepository": "jens/unraid-appops-gateway", "sourceRevision": source_revision, "sourceTree": source_tree, "artifactSha256": module._artifact_digest(entries, files), "builtAt": "2026-10-01T00:00:00.000Z", } entries["controller-release.json"] = ( json.dumps(manifest, separators=(",", ":")).encode(), 0o644 ) identity = module._release_identity(entries) outputs = {"rev-parse": (source_tree + "\n").encode()} outputs.update({ f"ops/gitea-deploy-broker/{name}": entries[name][0] for name in files if name != "run_repopulse_adoption.py" }) outputs["scripts/run_repopulse_adoption.py"] = entries["run_repopulse_adoption.py"][0] controller_id = "c" * 64 def run(arguments, **_kwargs): assert arguments[:5] == ["docker", "exec", controller_id, "git", f"--git-dir={module.SOURCE_GIT_DIR}"] return outputs[arguments[-1].split(":", 1)[-1]] if arguments[-2] == "show" else outputs["rev-parse"] monkeypatch.setattr(module, "_run", run) module._verify_git_source_identity(entries, identity, controller_id=controller_id) outputs["ops/gitea-deploy-broker/deploy.py"] = b"replaced source" with pytest.raises(module.VolumeTransitionError, match="source_mismatch"): module._verify_git_source_identity(entries, identity, controller_id=controller_id) @pytest.mark.parametrize("controller_id", ["gitea-deploy-control", "", "a" * 63, "a" * 65]) def test_prior_release_source_verification_requires_pinned_container_id(controller_id, monkeypatch): monkeypatch.setattr(module, "_run", lambda *_args, **_kwargs: pytest.fail("Docker must not run")) with pytest.raises(module.VolumeTransitionError, match="container_invalid"): module._verify_git_source_identity({}, {}, controller_id=controller_id) @pytest.mark.parametrize( ("name", "revision", "version", "captured", "source", "accepted"), [ ("run_repopulse_adoption.py", "034c9f844d2455850ba37b31379b7edaa786583e", "2.1.12", b"one\r\ntwo\r\n", b"one\ntwo\n", True), ("deploy.py", "034c9f844d2455850ba37b31379b7edaa786583e", "2.1.12", b"one\r\ntwo\r\n", b"one\ntwo\n", False), ("run_repopulse_adoption.py", "a" * 40, "2.1.12", b"one\r\ntwo\r\n", b"one\ntwo\n", False), ("run_repopulse_adoption.py", "034c9f844d2455850ba37b31379b7edaa786583e", "2.1.13", b"one\r\ntwo\r\n", b"one\ntwo\n", False), ("run_repopulse_adoption.py", "034c9f844d2455850ba37b31379b7edaa786583e", "2.1.12", b"one\r\ntwo\n", b"one\ntwo\n", False), ("run_repopulse_adoption.py", "034c9f844d2455850ba37b31379b7edaa786583e", "2.1.12", b"one\r\nchanged\r\n", b"one\ntwo\n", False), ("run_repopulse_adoption.py", "034c9f844d2455850ba37b31379b7edaa786583e", "2.1.12", b"one\r\r\n", b"one\r\n", False), ], ) def test_legacy_crlf_compatibility_is_limited_to_one_attested_handoff( name, revision, version, captured, source, accepted, ): identity = {"sourceRevision": revision, "controllerVersion": version} assert module._matches_legacy_crlf_source(name, captured, source, identity) is accepted