Files
ModelForge-Public/.gitea/tests/test_rc_acceptance_policy.py
T

434 lines
20 KiB
Python

"""Structural safety checks for public-candidate server acceptance."""
from __future__ import annotations
import importlib.util
import json
import os
import signal
import subprocess
import tempfile
import unittest
from pathlib import Path
from unittest import mock
ROOT = Path(__file__).resolve().parents[2]
WORKFLOW = ROOT / ".gitea" / "workflows" / "public-candidate-acceptance.yml"
MANAGED_WORKFLOW = ROOT / ".gitea" / "workflows" / "managed-validation.yml"
ACCEPTANCE = ROOT / "scripts" / "rc_server_acceptance.py"
TRIVY_VALIDATOR = ROOT / "scripts" / "validate_trivy_report.py"
RUNTIME_DOCKERFILE = ROOT / "runtime-worker" / "Dockerfile"
RUNTIME_PYPROJECT = ROOT / "runtime-worker" / "pyproject.toml"
class RcAcceptancePolicyTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.workflow = WORKFLOW.read_text(encoding="utf-8")
cls.managed = MANAGED_WORKFLOW.read_text(encoding="utf-8")
cls.acceptance = ACCEPTANCE.read_text(encoding="utf-8")
cls.trivy = TRIVY_VALIDATOR.read_text(encoding="utf-8")
cls.runtime_dockerfile = RUNTIME_DOCKERFILE.read_text(encoding="utf-8")
cls.runtime_pyproject = RUNTIME_PYPROJECT.read_text(encoding="utf-8")
def test_workflow_is_manual_and_never_calls_production_deployment(self) -> None:
self.assertIn("workflow_dispatch:", self.workflow)
self.assertNotIn("\n push:", self.workflow)
self.assertNotIn("pull_request:", self.workflow)
self.assertNotIn("deploy.py", self.workflow)
self.assertNotIn("DEPLOY_STABLE_TO_PRODUCTION", self.workflow)
def test_exact_source_and_docker_are_required(self) -> None:
self.assertIn(r"^[0-9a-f]{40}$", self.workflow)
self.assertIn("ref: ${{ inputs.source_commit }}", self.workflow)
self.assertIn("--canonical-source-commit", self.workflow)
self.assertIn('"--canonical-source-commit", canonical_commit', self.acceptance)
self.assertIn('"source_commit": canonical_commit', self.acceptance)
self.assertIn("docker version", self.workflow)
self.assertNotIn("SKIPPED: no docker", self.workflow)
def test_optional_candidate_freeze_retains_only_a_verified_transport_bundle(self) -> None:
self.assertIn("retain_image_bundle:", self.workflow)
self.assertIn("RETAIN_IMAGE_BUNDLE: ${{ inputs.retain_image_bundle }}", self.workflow)
self.assertIn("--retain-image-bundle", self.workflow)
self.assertIn("--image-bundle", self.acceptance)
self.assertIn("acceptance-evidence/release/*.tar", self.workflow)
self.assertIn("acceptance-evidence/release/*.tar.gz", self.workflow)
self.assertIn("atexit.register(cleanup_candidate_images", self.acceptance)
self.assertIn('canonical_commit="${{ inputs.source_commit }}"', self.workflow)
self.assertNotIn("candidate_commit=", self.workflow)
def test_security_tools_are_downloaded_with_exact_checksums(self) -> None:
self.assertIn("GITLEAKS_VERSION: 8.30.1", self.workflow)
self.assertIn(
"GITLEAKS_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb",
self.workflow,
)
self.assertIn('printf \'GITLEAKS=%s\\n\'', self.workflow)
self.assertIn('\"${GITLEAKS}\" dir \"${public_source}\"', self.workflow)
self.assertIn("sha256sum --check --strict", self.workflow)
self.assertNotIn("command -v gitleaks", self.workflow)
def test_unmerged_branch_can_use_the_existing_managed_dispatch_entry(self) -> None:
self.assertIn("workflow_call:", self.workflow)
self.assertIn("uses: ./.gitea/workflows/public-candidate-acceptance.yml", self.managed)
self.assertIn("inputs.profile == 'build'", self.managed)
self.assertIn("source_commit: ${{ gitea.sha }}", self.managed)
def test_all_four_images_are_built_and_scanned(self) -> None:
for image in (
"modelforge-api",
"modelforge-web",
"modelforge-node-agent",
"modelforge-runtime-worker",
):
self.assertIn(image, self.acceptance)
self.assertIn('"Metadata"', self.trivy)
self.assertIn('metadata.get("ImageID") != expected_image_id', self.trivy)
self.assertIn("reject_duplicate_keys", self.trivy)
self.assertIn("fixable_high_critical + unreviewed_unfixed_high_critical", self.trivy)
self.assertIn("public-candidate-unfixed-vulnerabilities.json", self.acceptance)
def test_runtime_image_applies_security_updates_and_exact_inference_pins(self) -> None:
self.assertIn("apt-get upgrade --yes", self.runtime_dockerfile)
self.assertIn("apt-get purge --yes linux-libc-dev", self.runtime_dockerfile)
self.assertNotIn("apt-get autoremove", self.runtime_dockerfile)
self.assertIn("pip check", self.runtime_dockerfile)
self.assertIn("python -m pip uninstall --yes pip setuptools wheel", self.runtime_dockerfile)
for dependency in (
"filelock==3.20.3",
"h2==4.4.1",
"idna==3.15",
"lief==0.17.2",
"Pillow==12.3.0",
"Pygments==2.20.0",
"protobuf==5.29.6",
"requests==2.33.0",
"sentencepiece==0.2.2",
"sentence-transformers==6.0.1",
"transformers==5.16.1",
"urllib3==2.7.0",
):
self.assertIn(dependency, self.runtime_pyproject)
self.assertNotIn("'filelock==3.20.3'", self.runtime_dockerfile)
def test_runtime_worker_has_only_group_read_access_to_the_agent_credential(self) -> None:
self.assertIn(
"groupadd --system --gid 101 modelforge-agent", self.runtime_dockerfile
)
self.assertIn(
"--groups modelforge-agent --home /app modelforge-runtime",
self.runtime_dockerfile,
)
def test_compose_project_is_isolated_and_always_removed(self) -> None:
self.assertIn('project = f"modelforge-validation-', self.acceptance)
self.assertIn('"MODELFORGE_SOURCE_REFERENCE": "validation-acceptance"', self.acceptance)
self.assertIn('project="modelforge-validation-${suffix}"', self.workflow)
self.assertNotIn('project = f"modelforge-rc-', self.acceptance)
self.assertIn('"MODELFORGE_API_BIND": "127.0.0.1"', self.acceptance)
self.assertIn('"MODELFORGE_API_PUBLISHED_PORT": "0"', self.acceptance)
self.assertIn('"production_changed": False', self.acceptance)
self.assertIn('"compute_identity_created": False', self.acceptance)
self.assertIn('"down", "--volumes", "--remove-orphans"', self.acceptance)
self.assertIn("atexit.register(cleanup_candidate_images", self.acceptance)
self.assertIn('"docker", "image", "rm", "--force", tag', self.acceptance)
self.assertNotIn("docker-compose.runtime-worker.yml", self.acceptance)
self.assertNotIn("docker-compose.node-agent.yml", self.acceptance)
def test_acceptance_override_disables_restarts_for_every_service(self) -> None:
spec = importlib.util.spec_from_file_location("rc_server_acceptance", ACCEPTANCE)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
write_override = getattr(module, "write_acceptance_compose_override", None)
self.assertIsNotNone(
write_override,
"acceptance must expose the same override writer used by the RC run",
)
with tempfile.TemporaryDirectory() as directory:
override = Path(directory) / "acceptance-compose.override.yml"
write_override(override)
env = os.environ.copy()
env.update(
{
"MODELFORGE_VERSION": "9.9.9-test",
"MODELFORGE_COMMIT": "a" * 40,
"MODELFORGE_BUILT_AT": "2099-01-01T00:00:00+00:00",
"MODELFORGE_POSTGRES_DB": "modelforge_validation_test",
"MODELFORGE_POSTGRES_ADMIN_PASSWORD": "admin-test-password",
"MODELFORGE_MIGRATION_DB_PASSWORD": "owner-test-password",
"MODELFORGE_RUNTIME_DB_PASSWORD": "runtime-test-password",
"MODELFORGE_MIGRATION_DATABASE_URL": (
"postgresql+psycopg://modelforge:owner-test-password@"
"postgres:5432/modelforge_validation_test"
),
"MODELFORGE_RUNTIME_DATABASE_URL": (
"postgresql+psycopg://modelforge_runtime:runtime-test-password@"
"postgres:5432/modelforge_validation_test"
),
"MODELFORGE_BACKUP_ENCRYPTION_KEY": "acceptance-test-key",
"MODELFORGE_CORS_ORIGINS": "https://modelforge.example.test",
"VITE_API_BASE_URL": "https://modelforge.example.test",
}
)
result = subprocess.run(
[
"docker",
"compose",
"-f",
str(ROOT / "docker-compose.yml"),
"-f",
str(ROOT / "docker-compose.production.yml"),
"-f",
str(override),
"config",
"--format",
"json",
],
cwd=ROOT,
env=env,
text=True,
capture_output=True,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
services = json.loads(result.stdout)["services"]
self.assertTrue(services)
self.assertEqual(
{name: service.get("restart") for name, service in services.items()},
{name: "no" for name in services},
)
def test_compose_cleanup_runs_when_acceptance_receives_sigterm(self) -> None:
spec = importlib.util.spec_from_file_location("rc_server_acceptance_cleanup", ACCEPTANCE)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
managed_cleanup = getattr(module, "managed_compose_cleanup", None)
self.assertIsNotNone(
managed_cleanup,
"acceptance must guard its RC project with interruption-safe cleanup",
)
compose = ("docker", "compose", "-p", "modelforge-validation-test")
env = {"COMPOSE_PROJECT_NAME": "modelforge-validation-test"}
commands: list[tuple[tuple[str, ...], dict[str, object]]] = []
registered: list[object] = []
unregistered: list[object] = []
handlers: dict[int, object] = {}
def fake_run(*args: str, **kwargs: object) -> str:
commands.append((args, kwargs))
return ""
def fake_signal(signum: int, handler: object) -> object:
previous = handlers.get(signum, signal.SIG_DFL)
handlers[signum] = handler
return previous
with (
mock.patch.object(module, "run", side_effect=fake_run),
mock.patch.object(module.atexit, "register", side_effect=registered.append),
mock.patch.object(module.atexit, "unregister", side_effect=unregistered.append),
mock.patch.object(module.signal, "signal", side_effect=fake_signal),
self.assertRaises(SystemExit) as raised,
managed_cleanup(compose, env),
):
handler = handlers[signal.SIGTERM]
assert callable(handler)
handler(signal.SIGTERM, None)
self.assertEqual(raised.exception.code, 128 + signal.SIGTERM)
self.assertEqual(
commands,
[
(
compose + ("down", "--volumes", "--remove-orphans"),
{"env": env, "check": False},
)
],
)
self.assertEqual(len(registered), 1)
self.assertEqual(unregistered, registered)
def test_clean_install_bootstraps_roles_across_a_remote_docker_daemon(self) -> None:
self.assertIn("def provision_database_roles(", self.acceptance)
self.assertIn('run("docker", "cp", str(bootstrap)', self.acceptance)
self.assertIn('"psql",', self.acceptance)
self.assertIn('"--file",', self.acceptance)
self.assertIn(
'"--wait", "postgres", "redis", env=env', self.acceptance
)
self.assertLess(
self.acceptance.index("provision_database_roles(compose, env, database)"),
self.acceptance.index(
'"--wait", "api", "web", env=env'
),
)
def test_clean_install_seeds_config_across_a_remote_docker_daemon(self) -> None:
self.assertIn("def provision_config_volume(project: str)", self.acceptance)
self.assertIn('f"{project}_acceptance-config"', self.acceptance)
self.assertIn('f"com.docker.compose.project={project}"', self.acceptance)
self.assertIn('run("docker", "cp", f"{ROOT / \'config\'}/."', self.acceptance)
self.assertIn(
'run("docker", "container", "rm", "--force", seed, check=False)',
self.acceptance,
)
self.assertIn('" read_only: true\\n"', self.acceptance)
self.assertLess(
self.acceptance.index("provision_config_volume(project)"),
self.acceptance.index('"--wait", "api", "web", env=env'),
)
def test_http_contract_runs_inside_the_isolated_compose_network(self) -> None:
self.assertIn("def container_http_status(", self.acceptance)
self.assertIn("def wait_for_container_status(", self.acceptance)
self.assertIn('headers["X-ModelForge-Admin-Token"]', self.acceptance)
self.assertIn('os.environ["MODELFORGE_OPERATOR_API_KEY"]', self.acceptance)
self.assertIn('wait_for_container_status(api_container, "http://web:3000/", 200)', self.acceptance)
self.assertNotIn("urllib.request.urlopen(request, timeout=timeout)", self.acceptance)
def test_workflow_has_an_always_run_exact_project_cleanup_fallback(self) -> None:
self.assertIn("- name: Always remove acceptance Docker resources", self.workflow)
cleanup = self.workflow.split(
" - name: Always remove acceptance Docker resources", 1
)[1].split(" - name: Upload acceptance evidence", 1)[0]
self.assertIn("if: always()", cleanup)
self.assertIn('label=com.docker.compose.project=${project}', cleanup)
self.assertIn("docker container rm --force", cleanup)
self.assertIn("docker volume rm --force", cleanup)
self.assertIn("docker network rm", cleanup)
self.assertIn("docker image rm --force", cleanup)
self.assertNotIn("docker system prune", cleanup)
def test_python_entrypoints_compile(self) -> None:
for path in (ACCEPTANCE, TRIVY_VALIDATOR, ROOT / "scripts" / "release_build.py"):
result = subprocess.run(
["python", "-m", "py_compile", str(path)],
capture_output=True,
check=False,
)
with self.subTest(path=path.name):
self.assertEqual(result.returncode, 0, result.stderr.decode("utf-8", "replace"))
def test_trivy_validator_binds_image_and_rejects_blockers(self) -> None:
spec = importlib.util.spec_from_file_location("trivy_report", TRIVY_VALIDATOR)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
image_id = "sha256:" + "a" * 64
report = {
"ArtifactName": image_id,
"ArtifactType": "container_image",
"Metadata": {"ImageID": image_id},
"Results": [
{
"Target": "debian",
"Class": "os-pkgs",
"Type": "debian",
"Vulnerabilities": [
{
"VulnerabilityID": "CVE-2099-9999",
"PkgName": "blocked-package",
"InstalledVersion": "1.0.0",
"FixedVersion": "1.0.1",
"Severity": "HIGH",
}
],
}
],
}
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "report.json"
path.write_text(json.dumps(report), encoding="utf-8")
result = module.validate(path, image_id)
self.assertEqual(result["release_blockers"], 1)
with self.assertRaisesRegex(ValueError, "does not match"):
module.validate(path, "sha256:" + "b" * 64)
def test_trivy_validator_rejects_duplicate_keys_and_empty_coverage(self) -> None:
spec = importlib.util.spec_from_file_location("trivy_report_negative", TRIVY_VALIDATOR)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
image_id = "sha256:" + "a" * 64
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "report.json"
path.write_text('{"ArtifactType":"container_image","ArtifactType":"container_image"}', encoding="utf-8")
with self.assertRaisesRegex(ValueError, "duplicate JSON key"):
module.validate(path, image_id)
path.write_text(
json.dumps(
{
"ArtifactType": "container_image",
"Metadata": {"ImageID": image_id},
"Results": [],
}
),
encoding="utf-8",
)
with self.assertRaisesRegex(ValueError, "no package result coverage"):
module.validate(path, image_id)
def test_trivy_validator_allows_only_exact_reviewed_upstream_unfixed_findings(self) -> None:
spec = importlib.util.spec_from_file_location("trivy_report_reviewed", TRIVY_VALIDATOR)
assert spec and spec.loader
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
image_id = "sha256:" + "a" * 64
finding = {
"VulnerabilityID": "CVE-2099-0001",
"PkgName": "example-package",
"InstalledVersion": "1.0.0",
"FixedVersion": "",
"Severity": "HIGH",
}
report = {
"ArtifactType": "container_image",
"Metadata": {"ImageID": image_id},
"Results": [
{
"Target": "debian",
"Class": "os-pkgs",
"Vulnerabilities": [finding],
}
],
}
baseline = {
"schema_version": 1,
"vulnerabilities": [
{
"vulnerability_id": "CVE-2099-0001",
"package": "example-package",
"installed_version": "1.0.0",
"severity": "HIGH",
}
],
}
with tempfile.TemporaryDirectory() as directory:
report_path = Path(directory) / "report.json"
baseline_path = Path(directory) / "baseline.json"
report_path.write_text(json.dumps(report), encoding="utf-8")
baseline_path.write_text(json.dumps(baseline), encoding="utf-8")
summary = module.validate(report_path, image_id, baseline_path)
self.assertEqual(summary["reviewed_unfixed_high_critical"], 1)
self.assertEqual(summary["release_blockers"], 0)
finding["InstalledVersion"] = "1.0.1"
report_path.write_text(json.dumps(report), encoding="utf-8")
self.assertEqual(module.validate(report_path, image_id, baseline_path)["release_blockers"], 1)
finding["InstalledVersion"] = "1.0.0"
finding["FixedVersion"] = "1.0.2"
report_path.write_text(json.dumps(report), encoding="utf-8")
summary = module.validate(report_path, image_id, baseline_path)
self.assertEqual(summary["fixable_high_critical"], 1)
self.assertEqual(summary["release_blockers"], 1)
if __name__ == "__main__":
unittest.main()