mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
be6179c2d2
Dependabot #2094, #2095 and #2096 bump actions/setup-go 6.4.0 -> 7.0.0, signpath/github-action-submit-signing-request 2 -> 3.0 and actions/github-script 8.0.0 -> 9.0.0. Each proposal is blocked only by the reviewed pin constants, the workflow-trust allowlist, the release-consumer action manifest and the installer governance assertions that hard-code the previous revisions. Carry all three bumps with those artifacts in one commit so the proposals can be closed as superseded. All three actions keep the node24 runtime and their existing inputs and outputs; no consumer interface, installer behaviour or public contract changes. The deployment-installability and agent-lifecycle workflow references are pin-only. Contract-Neutral: Reviewed action pin refresh with identical node24 consumer interfaces and unchanged inputs/outputs; no public-contract or installer-behaviour delta. Change-source: pulse-maintainer
372 lines
19 KiB
YAML
372 lines
19 KiB
YAML
name: Qualify Secure Runtime Release Candidate
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
tag:
|
|
description: 'Immutable vX.Y.Z-rc.N release tag to qualify'
|
|
required: true
|
|
type: string
|
|
|
|
permissions:
|
|
attestations: read
|
|
contents: read
|
|
|
|
jobs:
|
|
qualify-secure-runtime-release:
|
|
name: Qualify Immutable RC on Disposable systemd
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 45
|
|
steps:
|
|
- name: Checkout release qualification control
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Resolve immutable RC identity
|
|
id: release
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
INPUT_TAG: ${{ inputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
tag="${INPUT_TAG:-}"
|
|
[[ "${tag}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[1-9][0-9]*$ ]] || {
|
|
echo "::error::Secure-runtime release qualification requires an exact vX.Y.Z-rc.N tag."
|
|
exit 1
|
|
}
|
|
release_json="$RUNNER_TEMP/secure-runtime-release.json"
|
|
for attempt in $(seq 1 30); do
|
|
gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${tag}" > "${release_json}"
|
|
if jq -e --arg tag "${tag}" '.tag_name == $tag and .draft == false and .prerelease == true and .immutable == true' "${release_json}" >/dev/null; then
|
|
break
|
|
fi
|
|
if [[ "${attempt}" == 30 ]]; then
|
|
jq -c '{tag_name, draft, prerelease, immutable}' "${release_json}" >&2
|
|
echo "::error::Release did not reach immutable prerelease state."
|
|
exit 1
|
|
fi
|
|
sleep 10
|
|
done
|
|
release_id="$(jq -er '.id | select(type == "number" and . > 0)' "${release_json}")"
|
|
python3 scripts/write_github_output.py tag "${tag}"
|
|
echo "release_id=${release_id}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Checkout exact release tag
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
fetch-depth: 0
|
|
ref: ${{ steps.release.outputs.tag }}
|
|
|
|
- name: Verify detached release source
|
|
id: source
|
|
env:
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
# The release train cuts patch candidates from release/vX.Y, so the
|
|
# candidate is reachable from the branch the control plane maps its
|
|
# version to, not necessarily from main (v6.4.3-rc.1, 2026-09-02).
|
|
required_branch="$(python3 scripts/release_control/control_plane.py \
|
|
--branch-for-version "${TAG#v}")"
|
|
[[ "${required_branch}" =~ ^(main|release/v[0-9]+\.[0-9]+)$ ]] || {
|
|
echo "::error::Control plane mapped ${TAG} to an unexpected branch: ${required_branch}"
|
|
exit 1
|
|
}
|
|
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}"
|
|
test "$(git branch --show-current)" = ""
|
|
test -z "$(git status --short --untracked-files=all)"
|
|
git merge-base --is-ancestor "${commit}" "origin/${required_branch}"
|
|
python3 scripts/write_github_output.py commit "${commit}"
|
|
python3 scripts/write_github_output.py line_ref "origin/${required_branch}"
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: true
|
|
|
|
- name: Download exact immutable qualification packet
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p \
|
|
"$RUNNER_TEMP/secure-runtime-downloads" \
|
|
"$RUNNER_TEMP/secure-runtime-evidence" \
|
|
"$RUNNER_TEMP/secure-runtime-harness" \
|
|
"$RUNNER_TEMP/secure-runtime-preexecution"
|
|
for asset in \
|
|
checksums.txt \
|
|
release-build-provenance.sigstore.json \
|
|
secure-runtime-compiler-provenance.sigstore.json \
|
|
secure-runtime-build-contract-v1.json \
|
|
pulse-secure-runtime-collector-v1-linux-amd64 \
|
|
pulse-secure-runtime-collector-v1-linux-amd64.sig \
|
|
pulse-secure-runtime-collector-v2-linux-amd64 \
|
|
pulse-secure-runtime-collector-v2-linux-amd64.sig \
|
|
pulse-secure-runtime-collector-v3-linux-amd64 \
|
|
pulse-secure-runtime-collector-v3-linux-amd64.sig \
|
|
pulse-agent-linux-amd64 \
|
|
pulse-agent-linux-amd64.sig \
|
|
pulse-agent-helper-linux-amd64 \
|
|
pulse-agent-runner-linux-amd64; do
|
|
gh release download "${TAG}" \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--dir "$RUNNER_TEMP/secure-runtime-downloads" \
|
|
--pattern "${asset}"
|
|
done
|
|
|
|
- name: Pre-authenticate exact qualification packet
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
PULSE_UPDATE_SIGNING_PUBLIC_KEY: ${{ vars.PULSE_UPDATE_SIGNING_PUBLIC_KEY }}
|
|
QUALIFIED_COMMIT: ${{ steps.source.outputs.commit }}
|
|
LINE_REF: ${{ steps.source.outputs.line_ref }}
|
|
RELEASE_ID: ${{ steps.release.outputs.release_id }}
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
download_dir="$RUNNER_TEMP/secure-runtime-downloads"
|
|
verification_dir="$RUNNER_TEMP/secure-runtime-preexecution"
|
|
test -n "${PULSE_UPDATE_SIGNING_PUBLIC_KEY}"
|
|
update_fingerprint="$(
|
|
go run ./scripts/release_update_key.go fingerprint \
|
|
--public-key "${PULSE_UPDATE_SIGNING_PUBLIC_KEY}"
|
|
)"
|
|
python3 scripts/release_control/secure_runtime_attestation_v7.py \
|
|
--verify-release-packet-only \
|
|
--checkout "${GITHUB_WORKSPACE}" \
|
|
--commit "${QUALIFIED_COMMIT}" \
|
|
--main-ref "${LINE_REF}" \
|
|
--collector-v1 "${download_dir}/pulse-secure-runtime-collector-v1-linux-amd64" \
|
|
--collector-v2 "${download_dir}/pulse-secure-runtime-collector-v2-linux-amd64" \
|
|
--collector-v3 "${download_dir}/pulse-secure-runtime-collector-v3-linux-amd64" \
|
|
--collector-v4 "${download_dir}/pulse-agent-linux-amd64" \
|
|
--helper "${download_dir}/pulse-agent-helper-linux-amd64" \
|
|
--runner "${download_dir}/pulse-agent-runner-linux-amd64" \
|
|
--collector-v1-signature "${download_dir}/pulse-secure-runtime-collector-v1-linux-amd64.sig" \
|
|
--collector-v2-signature "${download_dir}/pulse-secure-runtime-collector-v2-linux-amd64.sig" \
|
|
--collector-v3-signature "${download_dir}/pulse-secure-runtime-collector-v3-linux-amd64.sig" \
|
|
--collector-v4-signature "${download_dir}/pulse-agent-linux-amd64.sig" \
|
|
--release-candidate-tag "${TAG}" \
|
|
--release-repository "${GITHUB_REPOSITORY}" \
|
|
--release-id "${RELEASE_ID}" \
|
|
--release-checksums "${download_dir}/checksums.txt" \
|
|
--release-assembly-provenance "${download_dir}/release-build-provenance.sigstore.json" \
|
|
--release-compiler-provenance "${download_dir}/secure-runtime-compiler-provenance.sigstore.json" \
|
|
--release-build-contract "${download_dir}/secure-runtime-build-contract-v1.json" \
|
|
--expected-release-update-key-fingerprint "${update_fingerprint}" \
|
|
--expected-architecture amd64 \
|
|
--verified-packet-dir "$RUNNER_TEMP/secure-runtime-verified" \
|
|
--output "${verification_dir}/preexecution-verification.json"
|
|
|
|
- name: Build exact-source qualification harness
|
|
run: |
|
|
set -euo pipefail
|
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
|
|
go test -c \
|
|
-o "$RUNNER_TEMP/secure-runtime-harness/installtests-linux-amd64.test" \
|
|
./scripts/installtests
|
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
|
|
go build -trimpath \
|
|
-o "$RUNNER_TEMP/secure-runtime-harness/docker-fixture-linux-amd64" \
|
|
./scripts/installtests/testdata/secure_runtime_docker_fixture.go
|
|
|
|
- name: Build disposable systemd host image
|
|
run: |
|
|
set -euo pipefail
|
|
context="$RUNNER_TEMP/secure-runtime-systemd-image"
|
|
mkdir -p "${context}"
|
|
tee "${context}/pulse-qualification-docker.service" >/dev/null <<'UNIT'
|
|
[Unit]
|
|
Description=Pulse secure-runtime qualification Docker daemon
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=notify
|
|
ExecStart=/usr/local/bin/dockerd --host=unix:///var/run/docker.sock --group=root --data-root=/var/lib/pulse-qualification-docker --exec-root=/run/pulse-qualification-docker --pidfile=/run/pulse-qualification-docker.pid --storage-driver=vfs --bridge=none --iptables=false --ip6tables=false --ip-forward=false --ip-masq=false --userland-proxy=false
|
|
ExecStartPost=/bin/chown root:root /var/run/docker.sock
|
|
ExecStartPost=/bin/chmod 0660 /var/run/docker.sock
|
|
Restart=on-failure
|
|
RestartSec=1
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
UNIT
|
|
tee "${context}/Dockerfile" >/dev/null <<'DOCKERFILE'
|
|
FROM docker:27.5.1-dind@sha256:f649ef046008ca7f926a2571c32b0ac22e5c59eb61b959617f9acc2a4c638cf5 AS dind
|
|
FROM ubuntu:24.04@sha256:33ceb71981b602c1a7443a53469e4dba065f7503eab3078a2d7a57a2ab987517
|
|
ENV container=docker
|
|
COPY --from=dind /usr/local/bin/ /usr/local/bin/
|
|
COPY pulse-qualification-docker.service /etc/systemd/system/pulse-qualification-docker.service
|
|
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
ca-certificates curl dbus systemd systemd-sysv util-linux && \
|
|
apt-get clean && \
|
|
systemctl mask dev-hugepages.mount sys-fs-fuse-connections.mount systemd-remount-fs.service docker.service docker.socket && \
|
|
systemctl enable pulse-qualification-docker.service
|
|
STOPSIGNAL SIGRTMIN+3
|
|
CMD ["/sbin/init"]
|
|
DOCKERFILE
|
|
docker build \
|
|
--tag pulse-secure-runtime-systemd:ubuntu-24.04 \
|
|
--file "${context}/Dockerfile" \
|
|
"${context}"
|
|
|
|
- name: Run schema-v7 systemd and rootful Docker qualification
|
|
id: lab
|
|
env:
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
container="pulse-secure-runtime-qualification-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
|
echo "container=${container}" >> "$GITHUB_OUTPUT"
|
|
docker run \
|
|
--detach \
|
|
--name "${container}" \
|
|
--privileged \
|
|
--network none \
|
|
--cgroupns=host \
|
|
--tmpfs /run \
|
|
--tmpfs /run/lock \
|
|
--volume /sys/fs/cgroup:/sys/fs/cgroup:rw \
|
|
--volume "${GITHUB_WORKSPACE}:/workspace:ro" \
|
|
--volume "$RUNNER_TEMP/secure-runtime-verified:/release:ro" \
|
|
--volume "$RUNNER_TEMP/secure-runtime-harness:/harness:ro" \
|
|
--volume "$RUNNER_TEMP/secure-runtime-evidence:/evidence:rw" \
|
|
pulse-secure-runtime-systemd:ubuntu-24.04
|
|
for attempt in $(seq 1 30); do
|
|
if docker exec "${container}" systemctl show-environment >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
if [[ "${attempt}" == 30 ]]; then
|
|
docker logs "${container}"
|
|
exit 1
|
|
fi
|
|
sleep 2
|
|
done
|
|
docker exec "${container}" sh -c \
|
|
'for command in curl docker dockerd id nsenter runuser systemctl; do command -v "$command" >/dev/null || exit 1; done'
|
|
docker exec "${container}" systemctl start pulse-qualification-docker.service
|
|
for attempt in $(seq 1 60); do
|
|
if docker exec "${container}" docker info >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
if [[ "${attempt}" == 60 ]]; then
|
|
docker exec "${container}" systemctl status pulse-qualification-docker.service --no-pager || true
|
|
docker logs "${container}"
|
|
exit 1
|
|
fi
|
|
sleep 1
|
|
done
|
|
docker exec "${container}" sh -ceu '
|
|
test -S /var/run/docker.sock
|
|
test "$(stat -c %U:%G:%a /var/run/docker.sock)" = "root:root:660"
|
|
daemon_pid="$(systemctl show pulse-qualification-docker.service --property MainPID --value)"
|
|
test "${daemon_pid}" -gt 0
|
|
case "$(tr "\000" " " < "/proc/${daemon_pid}/cmdline")" in *tcp://*) exit 1;; esac
|
|
rootfs="$(mktemp -d)"
|
|
cp /harness/docker-fixture-linux-amd64 "${rootfs}/fixture"
|
|
chmod 0555 "${rootfs}/fixture"
|
|
tar -C "${rootfs}" -cf /tmp/pulse-secure-runtime-rootfs.tar .
|
|
docker import --change '\''ENTRYPOINT ["/fixture"]'\'' /tmp/pulse-secure-runtime-rootfs.tar pulse-secure-runtime-fixture:v7 >/dev/null
|
|
docker run --detach --name pulse-v7-running --network none pulse-secure-runtime-fixture:v7 >/dev/null
|
|
docker run --name pulse-v7-exited --network none pulse-secure-runtime-fixture:v7 --exit >/dev/null
|
|
test "$(docker inspect --format "{{.State.Status}}" pulse-v7-running)" = "running"
|
|
test "$(docker inspect --format "{{.State.Status}}" pulse-v7-exited)" = "exited"
|
|
'
|
|
docker exec "${container}" sh -c \
|
|
'printf "%s\n" "PULSE_SECURE_RUNTIME_SYSTEMD_LAB=disposable-v1" > /etc/pulse-secure-runtime-lab'
|
|
start_epoch="$(date +%s)"
|
|
docker exec \
|
|
--env PULSE_SECURE_RUNTIME_SYSTEMD_LAB=1 \
|
|
--env PULSE_SECURE_RUNTIME_COLLECTOR_V1=/release/pulse-secure-runtime-collector-v1-linux-amd64 \
|
|
--env PULSE_SECURE_RUNTIME_COLLECTOR_V1_SIGNATURE=/release/pulse-secure-runtime-collector-v1-linux-amd64.sig \
|
|
--env PULSE_SECURE_RUNTIME_COLLECTOR_V2=/release/pulse-secure-runtime-collector-v2-linux-amd64 \
|
|
--env PULSE_SECURE_RUNTIME_COLLECTOR_V2_SIGNATURE=/release/pulse-secure-runtime-collector-v2-linux-amd64.sig \
|
|
--env PULSE_SECURE_RUNTIME_COLLECTOR_V3=/release/pulse-secure-runtime-collector-v3-linux-amd64 \
|
|
--env PULSE_SECURE_RUNTIME_COLLECTOR_V3_SIGNATURE=/release/pulse-secure-runtime-collector-v3-linux-amd64.sig \
|
|
--env PULSE_SECURE_RUNTIME_COLLECTOR_V4=/release/pulse-agent-linux-amd64 \
|
|
--env PULSE_SECURE_RUNTIME_COLLECTOR_V4_SIGNATURE=/release/pulse-agent-linux-amd64.sig \
|
|
--env PULSE_SECURE_RUNTIME_HELPER=/release/pulse-agent-helper-linux-amd64 \
|
|
--env PULSE_SECURE_RUNTIME_RUNNER=/release/pulse-agent-runner-linux-amd64 \
|
|
--env PULSE_SECURE_RUNTIME_RECEIPT=/evidence/receipt.json \
|
|
--env "PULSE_SECURE_RUNTIME_RECEIPT_RECORD_PATH=docs/release-control/v6/internal/records/secure-agent-runtime-systemd-receipt-v7-${TAG}.json" \
|
|
--env PULSE_SECURE_RUNTIME_TRANSCRIPT=/evidence/transcript.jsonl \
|
|
--env "PULSE_SECURE_RUNTIME_TRANSCRIPT_RECORD_PATH=docs/release-control/v6/internal/records/secure-agent-runtime-systemd-transcript-v7-${TAG}.jsonl" \
|
|
"${container}" \
|
|
sh -c 'cd /workspace/scripts/installtests && exec /harness/installtests-linux-amd64.test -test.run "^TestSecureRuntimeSystemdDockerV7Lab$" -test.count=1 -test.v'
|
|
elapsed="$(( $(date +%s) - start_epoch ))"
|
|
if [[ "${elapsed}" -lt 1 ]]; then elapsed=1; fi
|
|
echo "elapsed_seconds=${elapsed}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Create release-candidate secure-runtime attestation
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
PULSE_UPDATE_SIGNING_PUBLIC_KEY: ${{ vars.PULSE_UPDATE_SIGNING_PUBLIC_KEY }}
|
|
QUALIFIED_COMMIT: ${{ steps.source.outputs.commit }}
|
|
LINE_REF: ${{ steps.source.outputs.line_ref }}
|
|
RELEASE_ID: ${{ steps.release.outputs.release_id }}
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
WORKFLOW_OUTPUT_1: ${{ steps.lab.outputs.elapsed_seconds }}
|
|
run: |
|
|
set -euo pipefail
|
|
release_dir="$RUNNER_TEMP/secure-runtime-verified"
|
|
evidence_dir="$RUNNER_TEMP/secure-runtime-evidence"
|
|
test -n "${PULSE_UPDATE_SIGNING_PUBLIC_KEY}"
|
|
update_fingerprint="$(
|
|
go run ./scripts/release_update_key.go fingerprint \
|
|
--public-key "${PULSE_UPDATE_SIGNING_PUBLIC_KEY}"
|
|
)"
|
|
python3 scripts/release_control/secure_runtime_attestation_v7.py \
|
|
--checkout "${GITHUB_WORKSPACE}" \
|
|
--commit "${QUALIFIED_COMMIT}" \
|
|
--main-ref "${LINE_REF}" \
|
|
--receipt "${evidence_dir}/receipt.json" \
|
|
--receipt-record-path "docs/release-control/v6/internal/records/secure-agent-runtime-systemd-receipt-v7-${TAG}.json" \
|
|
--transcript "${evidence_dir}/transcript.jsonl" \
|
|
--collector-v1 "${release_dir}/pulse-secure-runtime-collector-v1-linux-amd64" \
|
|
--collector-v2 "${release_dir}/pulse-secure-runtime-collector-v2-linux-amd64" \
|
|
--collector-v3 "${release_dir}/pulse-secure-runtime-collector-v3-linux-amd64" \
|
|
--collector-v4 "${release_dir}/pulse-agent-linux-amd64" \
|
|
--helper "${release_dir}/pulse-agent-helper-linux-amd64" \
|
|
--runner "${release_dir}/pulse-agent-runner-linux-amd64" \
|
|
--collector-v1-signature "${release_dir}/pulse-secure-runtime-collector-v1-linux-amd64.sig" \
|
|
--collector-v2-signature "${release_dir}/pulse-secure-runtime-collector-v2-linux-amd64.sig" \
|
|
--collector-v3-signature "${release_dir}/pulse-secure-runtime-collector-v3-linux-amd64.sig" \
|
|
--collector-v4-signature "${release_dir}/pulse-agent-linux-amd64.sig" \
|
|
--elapsed-seconds "${WORKFLOW_OUTPUT_1}" \
|
|
--release-candidate-tag "${TAG}" \
|
|
--release-repository "${GITHUB_REPOSITORY}" \
|
|
--release-id "${RELEASE_ID}" \
|
|
--release-checksums "${release_dir}/checksums.txt" \
|
|
--release-assembly-provenance "${release_dir}/release-build-provenance.sigstore.json" \
|
|
--release-compiler-provenance "${release_dir}/secure-runtime-compiler-provenance.sigstore.json" \
|
|
--release-build-contract "${release_dir}/secure-runtime-build-contract-v1.json" \
|
|
--expected-release-update-key-fingerprint "${update_fingerprint}" \
|
|
--output "${evidence_dir}/attestation.json"
|
|
|
|
- name: Stop disposable systemd host
|
|
if: ${{ always() && steps.lab.outputs.container != '' }}
|
|
env:
|
|
WORKFLOW_OUTPUT_1: ${{ steps.lab.outputs.container }}
|
|
run: docker rm --force "${WORKFLOW_OUTPUT_1}"
|
|
|
|
- name: Upload immutable RC qualification evidence
|
|
if: ${{ success() }}
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: secure-runtime-qualification-${{ steps.release.outputs.tag }}-${{ steps.source.outputs.commit }}
|
|
path: |
|
|
${{ runner.temp }}/secure-runtime-evidence/
|
|
${{ runner.temp }}/secure-runtime-preexecution/preexecution-verification.json
|
|
if-no-files-found: error
|
|
retention-days: 90
|
|
compression-level: 0
|