From d980b25198d4a58f235a919077c5eb76b965816d Mon Sep 17 00:00:00 2001 From: fwazb Date: Mon, 20 Jul 2026 17:22:06 -0700 Subject: [PATCH] Assert packaged image runtime configuration --- docs/gcp-leoclean-nosend-service-package.md | 12 ++-- scripts/run_gcp_leoclean_nosend_oci_smoke.py | 38 +++++++++++ tests/test_gcp_leoclean_nosend_oci_smoke.py | 69 ++++++++++++++++++++ 3 files changed, 115 insertions(+), 4 deletions(-) diff --git a/docs/gcp-leoclean-nosend-service-package.md b/docs/gcp-leoclean-nosend-service-package.md index f64837f..c91fc40 100644 --- a/docs/gcp-leoclean-nosend-service-package.md +++ b/docs/gcp-leoclean-nosend-service-package.md @@ -170,10 +170,14 @@ environment file, and no production/VPS/Telegram target. CI also builds exactly one `linux/amd64` image, starts it with the reviewed restrictions, requires the built-in zero-adapter/no-`send_message` health response, checks that an unsupported `send-message` entrypoint command fails with the exact redacted -error, binds the running container to the dynamically resolved image ID, and -removes the candidate container, tag, and visible image ID. The receipt records -whether `linux/amd64` ran natively or through cross-platform emulation. Pinned -base images and build cache may remain. +error, and binds the running container to the dynamically resolved image ID. +Before startup, the smoke also requires the built image to declare the implicit +root bootstrap user, exact Python entrypoint, `service` command, healthcheck +timings, and `SIGTERM` stop signal. The successful health command then proves +PID 1 dropped to UID/GID `65532` with zero Linux capabilities. Finally, the +smoke removes the candidate container, tag, and visible image ID. The receipt +records whether `linux/amd64` ran natively or through cross-platform emulation. +Pinned base images and build cache may remain. The entrypoint command rejection is not a real Hermes tool invocation, and the bridge-network smoke does not prove general network-egress denial. These tests diff --git a/scripts/run_gcp_leoclean_nosend_oci_smoke.py b/scripts/run_gcp_leoclean_nosend_oci_smoke.py index 4b61a03..5630b1b 100644 --- a/scripts/run_gcp_leoclean_nosend_oci_smoke.py +++ b/scripts/run_gcp_leoclean_nosend_oci_smoke.py @@ -36,6 +36,16 @@ FAILURE_CLAIM_CEILING = ( ) ENTRYPOINT = "/opt/livingip/leoclean-nosend/entrypoint.py" RUNTIME_PYTHON = "/opt/livingip/leoclean-nosend/venv/bin/python" +EXPECTED_ENTRYPOINT = [RUNTIME_PYTHON, ENTRYPOINT] +EXPECTED_COMMAND = ["service"] +EXPECTED_HEALTHCHECK = { + "Test": ["CMD", RUNTIME_PYTHON, ENTRYPOINT, "health"], + "Interval": 15_000_000_000, + "Timeout": 10_000_000_000, + "StartPeriod": 30_000_000_000, + "Retries": 4, +} +EXPECTED_STOP_SIGNAL = "SIGTERM" class SmokeError(RuntimeError): @@ -76,6 +86,32 @@ def _docker_json(arguments: list[str], *, timeout: float = 120.0) -> Any: raise SmokeError("Docker did not return JSON") from exc +def _validate_image_runtime_configuration(image: dict[str, Any]) -> dict[str, Any]: + config = image.get("Config") + _require(isinstance(config, dict), "smoke image runtime configuration is missing") + user = config.get("User") + _require(user in (None, ""), "smoke image must begin as the root bootstrap user") + _require(config.get("Entrypoint") == EXPECTED_ENTRYPOINT, "smoke image entrypoint drifted") + _require(config.get("Cmd") == EXPECTED_COMMAND, "smoke image command drifted") + _require(config.get("StopSignal") == EXPECTED_STOP_SIGNAL, "smoke image stop signal drifted") + healthcheck = config.get("Healthcheck") + _require(isinstance(healthcheck, dict), "smoke image healthcheck is missing") + normalized_healthcheck = dict(healthcheck) + start_interval = normalized_healthcheck.pop("StartInterval", None) + _require( + start_interval is None or (type(start_interval) is int and start_interval == 0), + "smoke image healthcheck start interval drifted", + ) + _require(normalized_healthcheck == EXPECTED_HEALTHCHECK, "smoke image healthcheck drifted") + return { + "bootstrap_user": "root", + "entrypoint": EXPECTED_ENTRYPOINT, + "command": EXPECTED_COMMAND, + "healthcheck": {**EXPECTED_HEALTHCHECK, "StartInterval": 0}, + "stop_signal": EXPECTED_STOP_SIGNAL, + } + + def _write_receipt(path: Path, value: dict[str, Any]) -> None: path.parent.mkdir(parents=True, exist_ok=True) temporary = path.with_name(f".{path.name}.{uuid.uuid4().hex}.tmp") @@ -311,6 +347,7 @@ def run_smoke(*, repo_root: Path, artifact: Path, runtime_receipt: Path, output: for key, value in package.expected_image_labels(image_input).items(): _require(labels.get(key) == value, f"smoke image label drifted: {key}") _require(labels.get("livingip.leoclean-nosend-smoke") == smoke_id, "smoke image label is missing") + runtime_configuration = _validate_image_runtime_configuration(image) _run(_run_command(tag=tag, name=name, smoke_label=smoke_label), timeout=60) health_wait = _wait_healthy(name, expected_image_id=image_id) health = _verify_health(name) @@ -333,6 +370,7 @@ def run_smoke(*, repo_root: Path, artifact: Path, runtime_receipt: Path, output: "docker_server_architecture": docker_server_arch, "host_machine": host_platform.machine(), "target_execution_mode": execution_mode, + "image_runtime_configuration": runtime_configuration, "health_wait": health_wait, "health": health, "unsupported_send_message": unsupported, diff --git a/tests/test_gcp_leoclean_nosend_oci_smoke.py b/tests/test_gcp_leoclean_nosend_oci_smoke.py index 3bc1475..a53ed5c 100644 --- a/tests/test_gcp_leoclean_nosend_oci_smoke.py +++ b/tests/test_gcp_leoclean_nosend_oci_smoke.py @@ -1,5 +1,6 @@ from __future__ import annotations +import copy import json import subprocess from pathlib import Path @@ -9,6 +10,74 @@ import pytest from scripts import run_gcp_leoclean_nosend_oci_smoke as smoke +def _runtime_image(*, user: str | None = "", start_interval: int | None = 0) -> dict: + healthcheck = { + **smoke.EXPECTED_HEALTHCHECK, + "StartInterval": start_interval, + } + return { + "Config": { + "User": user, + "Entrypoint": list(smoke.EXPECTED_ENTRYPOINT), + "Cmd": list(smoke.EXPECTED_COMMAND), + "Healthcheck": healthcheck, + "StopSignal": smoke.EXPECTED_STOP_SIGNAL, + } + } + + +@pytest.mark.parametrize("user", [None, ""]) +@pytest.mark.parametrize("start_interval", [None, 0]) +def test_runtime_configuration_accepts_portable_unset_values(user: str | None, start_interval: int | None) -> None: + receipt = smoke._validate_image_runtime_configuration( + _runtime_image(user=user, start_interval=start_interval) + ) + + assert receipt["bootstrap_user"] == "root" + assert receipt["entrypoint"] == smoke.EXPECTED_ENTRYPOINT + assert receipt["command"] == ["service"] + assert receipt["stop_signal"] == "SIGTERM" + + +@pytest.mark.parametrize( + ("field", "value", "message"), + [ + ("User", "65532:65532", "root bootstrap user"), + ("Entrypoint", ["/bin/sh"], "entrypoint drifted"), + ("Cmd", ["probe"], "command drifted"), + ("StopSignal", "SIGKILL", "stop signal drifted"), + ], +) +def test_runtime_configuration_rejects_authoritative_field_drift(field: str, value: object, message: str) -> None: + image = _runtime_image() + image["Config"][field] = value + + with pytest.raises(smoke.SmokeError, match=message): + smoke._validate_image_runtime_configuration(image) + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("Test", ["CMD-SHELL", "true"]), + ("Interval", 1), + ("Timeout", 1), + ("StartPeriod", 1), + ("Retries", 1), + ("StartInterval", 1), + ("StartInterval", False), + ("Unexpected", 1), + ], +) +def test_runtime_configuration_rejects_healthcheck_drift(field: str, value: object) -> None: + image = _runtime_image() + image["Config"]["Healthcheck"] = copy.deepcopy(image["Config"]["Healthcheck"]) + image["Config"]["Healthcheck"][field] = value + + with pytest.raises(smoke.SmokeError, match="healthcheck"): + smoke._validate_image_runtime_configuration(image) + + def test_build_command_is_one_platform_pinned_local_build(tmp_path: Path) -> None: command = smoke._build_command( context=tmp_path,