From e9f057c18782222d3b95efcb1647d37899c322ed Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 21:34:44 +0100 Subject: [PATCH] Run secure-runtime RC qualification from reviewed control Dispatch the post-publication check from protected main while binding its packet to the immutable RC tag. Accept only canonical Pulse checkout origins before normalising the spelling required by the attester, and document the deployment contract and source guard together. Change-source: pulse-maintainer --- .github/workflows/create-release.yml | 2 +- .../qualify-secure-runtime-release.yml | 21 +++++++++++++++++-- .../subsystems/deployment-installability.md | 11 +++++++++- .../installtests/build_release_assets_test.go | 13 ++++++++---- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 1a315d4e9e..e3e227c4c0 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -2163,7 +2163,7 @@ jobs: dispatch="$( jq -n \ --arg tag "${TAG}" \ - '{ref: $tag, return_run_details: true, inputs: {tag: $tag}}' | \ + '{ref: "main", return_run_details: true, inputs: {tag: $tag}}' | \ gh api \ --method POST \ -H "Accept: application/vnd.github+json" \ diff --git a/.github/workflows/qualify-secure-runtime-release.yml b/.github/workflows/qualify-secure-runtime-release.yml index 92f9e3f5ad..d0fcc1a2dd 100644 --- a/.github/workflows/qualify-secure-runtime-release.yml +++ b/.github/workflows/qualify-secure-runtime-release.yml @@ -59,6 +59,20 @@ jobs: fetch-depth: 0 ref: ${{ steps.release.outputs.tag }} + - name: Bind canonical Pulse origin + run: | + set -euo pipefail + test "${GITHUB_REPOSITORY}" = "rcourtman/Pulse" + # checkout v7 records the canonical HTTPS URL without .git. The + # released attestation expects the equivalent .git spelling and + # independently checks the remote tag and branch identities. + origin_url="$(git remote get-url origin)" + case "${origin_url}" in + https://github.com/rcourtman/Pulse|https://github.com/rcourtman/Pulse.git) ;; + *) echo "::error::Release checkout origin is not canonical Pulse."; exit 1 ;; + esac + git remote set-url origin https://github.com/rcourtman/Pulse.git + - name: Verify detached release source id: source env: @@ -77,8 +91,11 @@ jobs: git fetch --no-tags origin \ "refs/heads/${required_branch}:refs/remotes/origin/${required_branch}" commit="$(git rev-parse "${TAG}^{commit}")" - test "${GITHUB_REF}" = "refs/tags/${TAG}" - test "${GITHUB_SHA}" = "${commit}" + # Run reviewed qualification control from protected main, while the + # checkout and the packet remain bound to the immutable release tag. + test "${GITHUB_REF}" = "refs/heads/main" + test "${GITHUB_WORKFLOW_REF}" = "${GITHUB_REPOSITORY}/.github/workflows/qualify-secure-runtime-release.yml@refs/heads/main" + test "${GITHUB_WORKFLOW_SHA}" = "${GITHUB_SHA}" test "$(git branch --show-current)" = "" test -z "$(git status --short --untracked-files=all)" git merge-base --is-ancestor "${commit}" "origin/${required_branch}" diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 86624b192d..71d1d4f4c9 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -1219,7 +1219,16 @@ artifact-selection behaviour. Private signing material and publication credentials must never enter the compilation job. Post-publication secure-runtime qualification must authenticate before it - executes. `.github/workflows/qualify-secure-runtime-release.yml` may download + executes. The publication workflow dispatches the reviewed qualification + control from protected `main`, with the immutable RC tag as its sole source + input. The qualification workflow must prove its own repository, workflow + ref and workflow SHA are that `main` control, then check out the detached + release tag and verify its commit is on the governed release line. Its + checkout may report either canonical HTTPS origin spelling, with or without + `.git`; it must reject every other origin before normalising to the exact + `.git` spelling required by the packet attester. Normalisation must not + relax the separate remote tag, branch, release and artifact identity checks. + `.github/workflows/qualify-secure-runtime-release.yml` may download caller-owned release assets only into a non-executable holding directory. `scripts/release_control/secure_runtime_attestation_v7.py` must copy the six binaries, four collector signatures, checksum manifest, assembly and diff --git a/scripts/installtests/build_release_assets_test.go b/scripts/installtests/build_release_assets_test.go index f262cb0e61..a64578e473 100644 --- a/scripts/installtests/build_release_assets_test.go +++ b/scripts/installtests/build_release_assets_test.go @@ -2792,7 +2792,7 @@ func TestSecureRuntimeQualificationPacketIsHostedAndReleaseBound(t *testing.T) { "release/pulse-secure-runtime-collector-v1-linux-amd64", "release/pulse-secure-runtime-collector-v3-linux-amd64", "qualify-secure-runtime-release.yml/dispatches", - `{ref: $tag, return_run_details: true, inputs: {tag: $tag}}`, + `{ref: "main", return_run_details: true, inputs: {tag: $tag}}`, "Secure-runtime qualification dispatch did not return an exact workflow run.", "Immutable RC publication did not retain an exact secure-runtime qualification run identity.", } { @@ -2805,9 +2805,11 @@ func TestSecureRuntimeQualificationPacketIsHostedAndReleaseBound(t *testing.T) { if strings.Contains(qualificationWorkflow, "release:\n types: [published]") { t.Fatal("secure-runtime qualification must be explicitly dispatched after immutable publication, not rely on suppressed release events") } + originIndex := strings.Index(qualificationWorkflow, "Bind canonical Pulse origin") + sourceIndex := strings.Index(qualificationWorkflow, "Verify detached release source") preauthenticationIndex := strings.Index(qualificationWorkflow, "Pre-authenticate exact qualification packet") privilegedExecutionIndex := strings.Index(qualificationWorkflow, "docker run") - if preauthenticationIndex < 0 || privilegedExecutionIndex < 0 || preauthenticationIndex > privilegedExecutionIndex { + if originIndex < 0 || sourceIndex < originIndex || preauthenticationIndex < sourceIndex || privilegedExecutionIndex < preauthenticationIndex { t.Fatal("secure-runtime release packet must be authenticated before any privileged Docker execution") } if strings.Contains(qualificationWorkflow, `$RUNNER_TEMP/secure-runtime-downloads:/release:ro`) { @@ -2815,8 +2817,11 @@ func TestSecureRuntimeQualificationPacketIsHostedAndReleaseBound(t *testing.T) { } for _, required := range []string{ ".immutable == true", - `test "${GITHUB_REF}" = "refs/tags/${TAG}"`, - `test "${GITHUB_SHA}" = "${commit}"`, + `test "${GITHUB_REF}" = "refs/heads/main"`, + `test "${GITHUB_WORKFLOW_SHA}" = "${GITHUB_SHA}"`, + `test "${GITHUB_REPOSITORY}" = "rcourtman/Pulse"`, + `https://github.com/rcourtman/Pulse|https://github.com/rcourtman/Pulse.git)`, + `git remote set-url origin https://github.com/rcourtman/Pulse.git`, "ca-certificates curl dbus systemd systemd-sysv util-linux", `docker:27.5.1-dind@sha256:f649ef046008ca7f926a2571c32b0ac22e5c59eb61b959617f9acc2a4c638cf5`, `for command in curl docker dockerd id nsenter runuser systemctl`,