Add signed runtime distribution pipeline
Dependency Audit / dependency-audit (push) Successful in 1m39s
Deployment Installer / deployment-installer (push) Successful in 5s
Security Audit / security-audit (push) Successful in 10m3s

This commit is contained in:
2026-08-03 00:54:06 +02:00
parent 29acb55b7c
commit 43380eb068
24 changed files with 3371 additions and 54 deletions
+134 -34
View File
@@ -22,6 +22,15 @@ from .bundle import (
render_compose,
service_names,
)
from .distribution import (
MAX_KEYRING_BYTES,
MAX_MANIFEST_BYTES,
DistributionError,
file_sha256,
load_bounded_json,
verify_manifest,
verify_manifest_binding,
)
from .model import (
InstallationSpec,
image_is_digest_pinned,
@@ -212,40 +221,7 @@ def static_checks(spec: InstallationSpec, paths: BundlePaths) -> tuple[Check, ..
)
)
if spec.release.manifest_url and spec.release.manifest_sha256:
checks.append(
Check(
"release.manifest",
"ok",
"A distribution manifest URL and expected digest are recorded.",
)
)
else:
checks.append(
Check(
"release.manifest",
"error" if spec.profile == "self-hosted" else "warning",
"No verified distribution manifest is recorded.",
"Use a published signed distribution manifest for self-hosted apply.",
)
)
if spec.profile == "self-hosted":
checks.append(
Check(
"release.signature_verification",
"error",
"Signed distribution-manifest verification is not implemented in the deployer yet.",
"Use the published verifier/bootstrap slice before a production apply.",
)
)
checks.append(
Check(
"modules.image_composition",
"error" if spec.enabled_modules else "warning",
"The selected module set is not yet verified against image package contents.",
"Use the signed distribution composition evidence before production apply.",
)
)
checks.extend(_distribution_checks(spec, paths))
values = read_env(paths.env)
required = {"MASTER_KEY_B64", "DATABASE_URL"}
@@ -364,6 +340,130 @@ def static_checks(spec: InstallationSpec, paths: BundlePaths) -> tuple[Check, ..
return tuple(checks)
def _distribution_checks(
spec: InstallationSpec,
paths: BundlePaths,
) -> tuple[Check, ...]:
blocking_level = "error" if spec.profile == "self-hosted" else "warning"
if not (
spec.release.manifest_sha256
and spec.release.manifest_keyring_sha256
and spec.release.manifest_signature_key_id
and spec.release.composition_sha256
and paths.manifest.exists()
and paths.keyring.exists()
):
return (
Check(
"release.manifest",
blocking_level,
"No locally verified runtime distribution is recorded.",
"Run govoplan-deploy verify-release --adopt with an independently trusted keyring.",
),
Check(
"release.signature_verification",
blocking_level,
"Runtime distribution signature evidence is unavailable.",
"Install and verify the signed distribution before apply.",
),
Check(
"modules.image_composition",
blocking_level,
"Enabled modules are not bound to image composition evidence.",
"Adopt a distribution whose composition contains every enabled module.",
),
)
try:
manifest_digest = file_sha256(
paths.manifest,
maximum_bytes=MAX_MANIFEST_BYTES,
)
if manifest_digest != spec.release.manifest_sha256:
raise DistributionError("stored manifest digest does not match installation")
keyring_digest = file_sha256(
paths.keyring,
maximum_bytes=MAX_KEYRING_BYTES,
)
if keyring_digest != spec.release.manifest_keyring_sha256:
raise DistributionError("stored keyring digest does not match installation")
manifest = load_bounded_json(
paths.manifest,
maximum_bytes=MAX_MANIFEST_BYTES,
)
keyring = load_bounded_json(
paths.keyring,
maximum_bytes=MAX_KEYRING_BYTES,
)
key_id = verify_manifest(
manifest,
keyring,
expected_channel=spec.release.channel,
)
if key_id != spec.release.manifest_signature_key_id:
raise DistributionError("verified signature key does not match installation")
verify_manifest_binding(
manifest,
channel=spec.release.channel,
version=spec.release.version,
api_image=spec.release.api_image,
web_image=spec.release.web_image,
enabled_modules=spec.enabled_modules,
composition_sha256=spec.release.composition_sha256,
dependencies=_selected_dependency_images(spec),
)
except (DistributionError, OSError) as exc:
return (
Check(
"release.manifest",
blocking_level,
f"Runtime distribution verification failed: {exc}",
"Re-adopt an unexpired, non-revoked manifest from a trusted release key.",
),
Check(
"release.signature_verification",
blocking_level,
"Runtime distribution signature is not trusted.",
"Correct the manifest/keyring binding before apply.",
),
Check(
"modules.image_composition",
blocking_level,
"Runtime image composition is not trusted.",
"Correct the signed composition binding before apply.",
),
)
return (
Check(
"release.manifest",
"ok",
"Stored runtime distribution matches its independently pinned digest.",
),
Check(
"release.signature_verification",
"ok",
f"Runtime distribution is signed by trusted key {key_id}.",
),
Check(
"modules.image_composition",
"ok",
"Every enabled module is present in signed image composition evidence.",
),
)
def _selected_dependency_images(spec: InstallationSpec) -> dict[str, str]:
values = {"load_balancer": spec.components.load_balancer.image}
if spec.components.postgres.mode == "managed":
values["postgres"] = spec.components.postgres.image
if spec.components.redis.mode == "managed":
values["redis"] = spec.components.redis.image
if spec.components.mail.mode == "test-mail":
values["test_mail"] = spec.components.mail.image
if spec.components.storage.mode == "garage":
values["garage"] = spec.components.storage.image
return values
def host_checks(
spec: InstallationSpec,
paths: BundlePaths,