from __future__ import annotations import base64 from datetime import UTC, datetime, timedelta import hashlib from pathlib import Path import sys import tempfile import unittest from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey META_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(META_ROOT / "tools" / "deployment")) from govoplan_deploy.bundle import bundle_paths # noqa: E402 from govoplan_deploy.cli import main # noqa: E402 from govoplan_deploy.distribution import ( # noqa: E402 canonical_json, canonical_signed_payload, ) from govoplan_deploy.model import load_spec # noqa: E402 from govoplan_deploy.planning import static_checks # noqa: E402 class DeploymentReleaseAdoptionTests(unittest.TestCase): def test_adopts_verified_manifest_and_makes_release_checks_pass(self) -> None: with tempfile.TemporaryDirectory(prefix="govoplan-release-adopt-") as value: root = Path(value) self.assertEqual( 0, main( [ "init", "--directory", str(root), "--non-interactive", "--module-set", "core", ] ), ) manifest, keyring = self._signed_distribution() manifest_path = root / "source-manifest.json" keyring_path = root / "source-keyring.json" encoded_manifest = canonical_json(manifest) manifest_path.write_bytes(encoded_manifest) keyring_path.write_bytes(canonical_json(keyring)) result = main( [ "verify-release", "--directory", str(root), "--manifest", str(manifest_path), "--manifest-sha256", hashlib.sha256(encoded_manifest).hexdigest(), "--trusted-keyring", str(keyring_path), "--adopt", ] ) self.assertEqual(0, result) paths = bundle_paths(root) spec = load_spec(paths.spec) self.assertEqual("1.2.3", spec.release.version) self.assertEqual("release-1", spec.release.manifest_signature_key_id) self.assertTrue(spec.release.api_image.endswith("a" * 64)) release_checks = { item.id: item for item in static_checks(spec, paths) if item.id.startswith("release.") or item.id == "modules.image_composition" } self.assertEqual("ok", release_checks["release.manifest"].level) self.assertEqual( "ok", release_checks["release.signature_verification"].level ) self.assertEqual("ok", release_checks["modules.image_composition"].level) def test_rejects_manifest_whose_independent_digest_does_not_match(self) -> None: with tempfile.TemporaryDirectory(prefix="govoplan-release-adopt-") as value: root = Path(value) main( [ "init", "--directory", str(root), "--non-interactive", "--module-set", "core", ] ) manifest, keyring = self._signed_distribution() manifest_path = root / "source-manifest.json" keyring_path = root / "source-keyring.json" manifest_path.write_bytes(canonical_json(manifest)) keyring_path.write_bytes(canonical_json(keyring)) self.assertEqual( 1, main( [ "verify-release", "--directory", str(root), "--manifest", str(manifest_path), "--manifest-sha256", "0" * 64, "--trusted-keyring", str(keyring_path), ] ), ) @staticmethod def _signed_distribution() -> tuple[dict[str, object], dict[str, object]]: now = datetime.now(UTC) private = Ed25519PrivateKey.generate() public = private.public_key().public_bytes( serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo, ).decode("ascii") artifact = { "url": "https://downloads.example.test/artifact.json", "sha256": "f" * 64, } payload: dict[str, object] = { "schema_version": "1", "channel": "stable", "sequence": 1, "version": "1.2.3", "issued_at": (now - timedelta(minutes=1)).isoformat(), "expires_at": (now + timedelta(days=30)).isoformat(), "revoked": False, "deployer": { "url": "https://downloads.example.test/govoplan-deploy.pyz", "sha256": "e" * 64, }, "images": { "api": { "index": "registry.example/govoplan/api@sha256:" + "a" * 64, "platforms": { "linux/amd64": "registry.example/govoplan/api@sha256:" + "1" * 64, "linux/arm64": "registry.example/govoplan/api@sha256:" + "2" * 64, }, "sbom": dict(artifact), "provenance": dict(artifact), }, "web": { "index": "registry.example/govoplan/web@sha256:" + "b" * 64, "platforms": { "linux/amd64": "registry.example/govoplan/web@sha256:" + "3" * 64, "linux/arm64": "registry.example/govoplan/web@sha256:" + "4" * 64, }, "sbom": dict(artifact), "provenance": dict(artifact), }, }, "dependencies": { "postgres": "docker.io/library/postgres@sha256:" + "5" * 64, "redis": "docker.io/library/redis@sha256:" + "6" * 64, "load_balancer": "docker.io/library/haproxy@sha256:" + "7" * 64, }, "composition": { "sha256": "c" * 64, "module_ids": [], "packages": [ { "name": "govoplan-core", "version": "1.2.3", "wheel_sha256": "8" * 64, } ], }, } payload["signatures"] = [ { "key_id": "release-1", "algorithm": "ed25519", "value": base64.b64encode( private.sign(canonical_signed_payload(payload)) ).decode("ascii"), } ] keyring = { "schema_version": "1", "purpose": "govoplan-runtime-distribution", "keys": [ { "key_id": "release-1", "algorithm": "ed25519", "status": "active", "public_key_pem": public, "not_before": (now - timedelta(days=1)).isoformat(), "expires_at": (now + timedelta(days=365)).isoformat(), } ], } return payload, keyring if __name__ == "__main__": unittest.main()