Publish curated Unraid-AppOps-Gateway source from 451646fee806a47e3256731766980a05a262dcdd
This commit is contained in:
1 parent
532f3d9f26
commit
0a82153d95
43 files changed
+17722
-93
No files matched your search
@@ -23,6 +23,18 @@ 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():
|
||||
@@ -58,6 +70,214 @@ 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")
|
||||
@@ -230,18 +450,28 @@ def test_compose_uses_protected_code_and_two_config_views(compose):
|
||||
"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) == 4
|
||||
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}
|
||||
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"]
|
||||
@@ -257,6 +487,46 @@ def test_compose_uses_protected_code_and_two_config_views(compose):
|
||||
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"]:
|
||||
@@ -274,7 +544,14 @@ def test_source_relative_controller_bind_uses_original_compose_directory(monkeyp
|
||||
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"])
|
||||
@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":
|
||||
@@ -289,12 +566,65 @@ def test_compose_rejects_unsafe_or_unpinned_activation_inputs(compose, damage):
|
||||
baseline["services"]["deploy-control"]["volumes"][0]["read_only"] = False
|
||||
elif damage == "relative_bind":
|
||||
baseline["services"]["runner"]["volumes"][0]["source"] = "./unsafe"
|
||||
else:
|
||||
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
|
||||
@@ -385,6 +715,184 @@ def test_config_snapshot_has_exact_allowlist_and_broker_token():
|
||||
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),
|
||||
@@ -655,6 +1163,154 @@ def test_installer_release_order_matches_renderer_contract():
|
||||
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")
|
||||
@@ -943,6 +1599,10 @@ def test_capture_program_is_fixed_descriptor_based_and_compiles():
|
||||
@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)
|
||||
@@ -958,6 +1618,7 @@ def test_capture_archive_roundtrips_through_a_nonseekable_pipe(legacy_config):
|
||||
"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)
|
||||
@@ -971,8 +1632,13 @@ def test_capture_archive_roundtrips_through_a_nonseekable_pipe(legacy_config):
|
||||
assert all(member.uid == member.gid == 0 for member in archive.getmembers())
|
||||
|
||||
|
||||
def test_prior_release_capture_must_match_protected_git_tree(monkeypatch):
|
||||
files = module.LEGACY_RELEASE_FILES_2_1_5
|
||||
@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"
|
||||
|
||||
Reference in new issue
Block a user