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`,