Harden runtime distribution acceptance
Dependency Audit / dependency-audit (push) Successful in 1m45s
Deployment Installer / deployment-installer (push) Successful in 6s
Security Audit / security-audit (push) Successful in 10m49s

This commit is contained in:
2026-08-03 17:32:52 +02:00
parent a5a0731d20
commit ff8ee991c3
8 changed files with 976 additions and 29 deletions
+104 -15
View File
@@ -27,13 +27,52 @@ from govoplan_deploy.model import default_spec # noqa: E402
DIGEST_IMAGE = re.compile(r"^[^@\s]+@sha256:[0-9a-f]{64}$")
def _run(argv: list[str], *, check: bool = True) -> subprocess.CompletedProcess[str]:
return subprocess.run(
argv,
check=check,
capture_output=True,
text=True,
timeout=60,
def _run(
argv: list[str],
*,
check: bool = True,
input_text: str | None = None,
) -> subprocess.CompletedProcess[str]:
try:
return subprocess.run(
argv,
check=check,
capture_output=True,
input=input_text,
text=True,
timeout=60,
)
except subprocess.CalledProcessError as exc:
stderr = exc.stderr.strip()
if stderr:
print(stderr, file=sys.stderr)
raise
def _write_volume_file(
*,
image: str,
volume: str,
filename: str,
content: str,
) -> None:
if not re.fullmatch(r"[A-Za-z0-9_.-]+", filename):
raise ValueError(f"invalid config filename: {filename!r}")
_run(
[
"docker",
"run",
"--rm",
"--interactive",
"--mount",
f"type=volume,src={volume},dst=/govoplan-config",
"--entrypoint",
"sh",
image,
"-c",
f"umask 022; cat > /govoplan-config/{filename}",
],
input_text=content,
)
@@ -85,15 +124,33 @@ def main() -> int:
backend = f"govoplan-ingress-backend-{suffix}"
data_volume = f"govoplan-ingress-data-{suffix}"
config_volume = f"govoplan-ingress-config-{suffix}"
backend_config_volume = f"govoplan-ingress-backend-config-{suffix}"
ingress_config_volume = f"govoplan-ingress-caddy-config-{suffix}"
load_balancer_config_volume = f"govoplan-ingress-haproxy-config-{suffix}"
cleanup = [
["docker", "rm", "--force", ingress, backend],
["docker", "network", "rm", network],
["docker", "volume", "rm", data_volume, config_volume],
[
"docker",
"volume",
"rm",
data_volume,
config_volume,
backend_config_volume,
ingress_config_volume,
load_balancer_config_volume,
],
]
try:
_run(["docker", "network", "create", network])
_run(["docker", "volume", "create", data_volume])
_run(["docker", "volume", "create", config_volume])
for volume in (
data_volume,
config_volume,
backend_config_volume,
ingress_config_volume,
load_balancer_config_volume,
):
_run(["docker", "volume", "create", volume])
with tempfile.TemporaryDirectory(prefix="govoplan-ingress-") as directory:
root = Path(directory)
backend_config = root / "backend.Caddyfile"
@@ -127,6 +184,24 @@ def main() -> int:
encoding="utf-8",
)
load_balancer_config.chmod(0o644)
_write_volume_file(
image=args.load_balancer_image,
volume=load_balancer_config_volume,
filename="haproxy.cfg",
content=load_balancer_config.read_text(encoding="utf-8"),
)
_write_volume_file(
image=args.caddy_image,
volume=backend_config_volume,
filename="Caddyfile",
content=backend_config.read_text(encoding="utf-8"),
)
_write_volume_file(
image=args.caddy_image,
volume=ingress_config_volume,
filename="Caddyfile",
content=ingress_config.read_text(encoding="utf-8"),
)
_run(
[
"docker",
@@ -136,7 +211,11 @@ def main() -> int:
"--cap-drop",
"ALL",
"--mount",
f"type=bind,src={load_balancer_config},dst=/usr/local/etc/haproxy/haproxy.cfg,readonly",
(
"type=volume,"
f"src={load_balancer_config_volume},"
"dst=/usr/local/etc/haproxy,readonly"
),
args.load_balancer_image,
"haproxy",
"-c",
@@ -154,16 +233,22 @@ def main() -> int:
backend,
"--network",
network,
"--network-alias",
"load-balancer",
"--read-only",
"--tmpfs",
"/tmp:rw,noexec,nosuid,size=16m",
"--mount",
f"type=bind,src={backend_config},dst=/etc/caddy/Caddyfile,readonly",
(
"type=volume,"
f"src={backend_config_volume},"
"dst=/govoplan-config,readonly"
),
args.caddy_image,
"caddy",
"run",
"--config",
"/etc/caddy/Caddyfile",
"/govoplan-config/Caddyfile",
]
)
ingress_command = [
@@ -186,7 +271,11 @@ def main() -> int:
"--publish",
"127.0.0.1::8443",
"--mount",
f"type=bind,src={ingress_config},dst=/etc/caddy/Caddyfile,readonly",
(
"type=volume,"
f"src={ingress_config_volume},"
"dst=/govoplan-config,readonly"
),
"--mount",
f"type=volume,src={data_volume},dst=/data",
"--mount",
@@ -195,7 +284,7 @@ def main() -> int:
"caddy",
"run",
"--config",
"/etc/caddy/Caddyfile",
"/govoplan-config/Caddyfile",
]
_run(ingress_command)
http_port = _published_port(ingress, 8080)