From f313882a7bacd444775035417e51179baf074e98 Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 09:49:01 +0100 Subject: [PATCH] Pin container provenance workflow revision Change-source: pulse-maintainer --- docs/CODE_SIGNING_POLICY.md | 6 +++ .../verify_release_container_images_test.py | 42 +++++++++++++------ scripts/verify-release-container-images.sh | 1 + 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/docs/CODE_SIGNING_POLICY.md b/docs/CODE_SIGNING_POLICY.md index 8b6967396..0db6561c0 100644 --- a/docs/CODE_SIGNING_POLICY.md +++ b/docs/CODE_SIGNING_POLICY.md @@ -90,6 +90,12 @@ Normal stable publication and stable dry runs select `signpath` directly. that verification, and Helm Pages refuses to advertise a chart whose OCI tag, signer workflow, source commit, or digest has drifted from the immutable activation packet. +- Exact-version server and control-plane container tags in Docker Hub and GHCR + must resolve to one digest per image. Each registry copy must carry SLSA v1 + provenance from the exact `publish-docker.yml` workflow, release source + commit, workflow commit, and a GitHub-hosted runner before either image + digest can enter `release-activation.json`. Activation recovery repeats the + same policy before customer-facing aliases can move. - Release activation requires GitHub CLI 2.97.0 or newer, which includes the literal signer-identity matcher fix. The shared `scripts/require-safe-gh-attestation.sh` guard enforces this floor. The diff --git a/scripts/release_control/verify_release_container_images_test.py b/scripts/release_control/verify_release_container_images_test.py index 7e3591227..bf11a1694 100644 --- a/scripts/release_control/verify_release_container_images_test.py +++ b/scripts/release_control/verify_release_container_images_test.py @@ -4,6 +4,7 @@ from __future__ import annotations import os from pathlib import Path +import shlex import subprocess import tempfile import textwrap @@ -105,18 +106,35 @@ class VerifyReleaseContainerImagesTests(unittest.TestCase): result.stdout.splitlines(), [f"server_digest={DIGEST}", f"control_plane_digest={DIGEST}"], ) - self.assertEqual(len(calls.splitlines()), 4) - self.assertIn(f"oci://docker.io/rcourtman/pulse@{DIGEST}", calls) - self.assertIn(f"oci://ghcr.io/rcourtman/pulse-control-plane@{DIGEST}", calls) - self.assertIn("--repo rcourtman/Pulse", calls) - self.assertIn("--bundle-from-oci", calls) - self.assertIn( - "--signer-workflow github.com/rcourtman/Pulse/.github/workflows/publish-docker.yml", - calls, - ) - self.assertIn(f"--source-digest {SOURCE_SHA}", calls) - self.assertIn("--deny-self-hosted-runners", calls) - self.assertIn("--predicate-type https://slsa.dev/provenance/v1", calls) + subjects = { + f"oci://docker.io/rcourtman/pulse@{DIGEST}", + f"oci://ghcr.io/rcourtman/pulse@{DIGEST}", + f"oci://docker.io/rcourtman/pulse-control-plane@{DIGEST}", + f"oci://ghcr.io/rcourtman/pulse-control-plane@{DIGEST}", + } + expected_policy = [ + "--repo", + "rcourtman/Pulse", + "--bundle-from-oci", + "--signer-workflow", + "github.com/rcourtman/Pulse/.github/workflows/publish-docker.yml", + "--signer-digest", + SOURCE_SHA, + "--source-digest", + SOURCE_SHA, + "--deny-self-hosted-runners", + "--predicate-type", + "https://slsa.dev/provenance/v1", + ] + invocations = [shlex.split(call) for call in calls.splitlines()] + self.assertEqual(len(invocations), len(subjects)) + self.assertEqual({invocation[2] for invocation in invocations}, subjects) + for invocation in invocations: + self.assertEqual( + invocation, + ["attestation", "verify", invocation[2], *expected_policy], + f"incomplete attestation policy for {invocation[2]}", + ) def test_rejects_a_moved_exact_version_tag_before_attestation(self) -> None: changed = "sha256:" + "c" * 64 diff --git a/scripts/verify-release-container-images.sh b/scripts/verify-release-container-images.sh index 4d88cb3dd..06d5be03a 100755 --- a/scripts/verify-release-container-images.sh +++ b/scripts/verify-release-container-images.sh @@ -77,6 +77,7 @@ verify_image() { --repo "$REPOSITORY" \ --bundle-from-oci \ --signer-workflow "$SIGNER_WORKFLOW" \ + --signer-digest "$SOURCE_SHA" \ --source-digest "$SOURCE_SHA" \ --deny-self-hosted-runners \ --predicate-type https://slsa.dev/provenance/v1 \