mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-22 19:56:01 +00:00
509 lines
24 KiB
YAML
509 lines
24 KiB
YAML
name: Build Release Candidate
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
version:
|
|
description: 'Version number without the leading v'
|
|
required: true
|
|
type: string
|
|
require_macos_signing:
|
|
description: 'Require Developer ID signed and notarized macOS agent binaries'
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
require_windows_signing:
|
|
description: 'Require Authenticode-signed Windows agent binaries'
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
windows_signing_backend:
|
|
description: 'Windows signing backend: signpath (canonical) or legacy-pfx (break-glass fallback)'
|
|
required: false
|
|
default: signpath
|
|
type: string
|
|
outputs:
|
|
artifact_name:
|
|
description: 'Immutable release candidate artifact name'
|
|
value: ${{ jobs.build.outputs.artifact_name }}
|
|
manifest_artifact_name:
|
|
description: 'Release candidate manifest artifact name'
|
|
value: ${{ jobs.build.outputs.manifest_artifact_name }}
|
|
windows_signing_backend:
|
|
description: 'Windows signing backend used for the candidate'
|
|
value: ${{ jobs.sign-windows-agent.outputs.signing_backend }}
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
signing-configuration:
|
|
name: Verify Native Signing Configuration
|
|
if: ${{ inputs.require_macos_signing || inputs.require_windows_signing }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 2
|
|
steps:
|
|
- name: Report missing signing secrets
|
|
env:
|
|
APPLE_DEVELOPER_ID_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_P12_BASE64 }}
|
|
APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
|
|
APPLE_DEVELOPER_ID_APPLICATION_IDENTITY: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_IDENTITY }}
|
|
APPLE_NOTARY_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_KEY_P8_BASE64 }}
|
|
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
|
|
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
|
|
WINDOWS_CODE_SIGNING_CERTIFICATE_PFX_BASE64: ${{ secrets.WINDOWS_CODE_SIGNING_CERTIFICATE_PFX_BASE64 }}
|
|
WINDOWS_CODE_SIGNING_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CODE_SIGNING_CERTIFICATE_PASSWORD }}
|
|
SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
|
|
SIGNPATH_ORGANIZATION_ID: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
|
|
SIGNPATH_PROJECT_SLUG: ${{ vars.SIGNPATH_PROJECT_SLUG }}
|
|
SIGNPATH_SIGNING_POLICY_SLUG: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }}
|
|
SIGNPATH_ARTIFACT_CONFIGURATION_SLUG: ${{ vars.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }}
|
|
SIGNPATH_EXPECTED_CERTIFICATE_SUBJECT: ${{ vars.SIGNPATH_EXPECTED_CERTIFICATE_SUBJECT }}
|
|
REQUIRE_MACOS_SIGNING: ${{ inputs.require_macos_signing }}
|
|
REQUIRE_WINDOWS_SIGNING: ${{ inputs.require_windows_signing }}
|
|
WINDOWS_SIGNING_BACKEND: ${{ inputs.windows_signing_backend }}
|
|
run: |
|
|
set -euo pipefail
|
|
missing=0
|
|
required=()
|
|
if [[ "$REQUIRE_MACOS_SIGNING" == "true" ]]; then
|
|
required+=(
|
|
APPLE_DEVELOPER_ID_CERTIFICATE_P12_BASE64
|
|
APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD
|
|
APPLE_DEVELOPER_ID_APPLICATION_IDENTITY
|
|
APPLE_NOTARY_KEY_P8_BASE64
|
|
APPLE_NOTARY_KEY_ID
|
|
APPLE_NOTARY_ISSUER_ID
|
|
)
|
|
fi
|
|
if [[ "$REQUIRE_WINDOWS_SIGNING" == "true" ]]; then
|
|
case "$WINDOWS_SIGNING_BACKEND" in
|
|
signpath)
|
|
required+=(
|
|
SIGNPATH_API_TOKEN
|
|
SIGNPATH_ORGANIZATION_ID
|
|
SIGNPATH_PROJECT_SLUG
|
|
SIGNPATH_SIGNING_POLICY_SLUG
|
|
SIGNPATH_ARTIFACT_CONFIGURATION_SLUG
|
|
SIGNPATH_EXPECTED_CERTIFICATE_SUBJECT
|
|
)
|
|
;;
|
|
legacy-pfx)
|
|
required+=(
|
|
WINDOWS_CODE_SIGNING_CERTIFICATE_PFX_BASE64
|
|
WINDOWS_CODE_SIGNING_CERTIFICATE_PASSWORD
|
|
)
|
|
echo "::warning::Using the break-glass legacy PFX Windows signing backend."
|
|
;;
|
|
*)
|
|
echo "::error::Unsupported Windows signing backend '${WINDOWS_SIGNING_BACKEND}'. Expected signpath or legacy-pfx."
|
|
missing=1
|
|
;;
|
|
esac
|
|
else
|
|
echo "::notice::Windows Authenticode is not required for this candidate."
|
|
fi
|
|
for name in "${required[@]}"; do
|
|
if [ -z "${!name:-}" ]; then
|
|
echo "::error::Missing required Actions secret ${name}."
|
|
missing=1
|
|
fi
|
|
done
|
|
exit "$missing"
|
|
|
|
sign-macos-agent:
|
|
name: Sign and Notarize macOS Agent
|
|
needs: signing-configuration
|
|
if: ${{ inputs.require_macos_signing && needs.signing-configuration.result == 'success' }}
|
|
runs-on: macos-15
|
|
timeout-minutes: 30
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: true
|
|
|
|
- name: Build, sign, and notarize agent binaries
|
|
shell: bash
|
|
env:
|
|
APPLE_CERTIFICATE_P12_BASE64: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_P12_BASE64 }}
|
|
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
|
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_DEVELOPER_ID_APPLICATION_IDENTITY }}
|
|
APPLE_NOTARY_KEY_P8_BASE64: ${{ secrets.APPLE_NOTARY_KEY_P8_BASE64 }}
|
|
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
|
|
APPLE_NOTARY_ISSUER_ID: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
|
|
PULSE_UPDATE_SIGNING_PUBLIC_KEY: ${{ vars.PULSE_UPDATE_SIGNING_PUBLIC_KEY }}
|
|
run: |
|
|
set -euo pipefail
|
|
for name in \
|
|
APPLE_CERTIFICATE_P12_BASE64 \
|
|
APPLE_CERTIFICATE_PASSWORD \
|
|
APPLE_SIGNING_IDENTITY \
|
|
APPLE_NOTARY_KEY_P8_BASE64 \
|
|
APPLE_NOTARY_KEY_ID \
|
|
APPLE_NOTARY_ISSUER_ID \
|
|
PULSE_UPDATE_SIGNING_PUBLIC_KEY; do
|
|
test -n "${!name:-}" || { echo "::error::Missing required ${name}."; exit 1; }
|
|
done
|
|
|
|
mkdir -p native-agent-binaries
|
|
python3 - <<'PY'
|
|
import base64, os
|
|
from pathlib import Path
|
|
Path('developer-id.p12').write_bytes(base64.b64decode(os.environ['APPLE_CERTIFICATE_P12_BASE64']))
|
|
Path('AuthKey.p8').write_bytes(base64.b64decode(os.environ['APPLE_NOTARY_KEY_P8_BASE64']))
|
|
PY
|
|
|
|
keychain="$RUNNER_TEMP/pulse-signing.keychain-db"
|
|
keychain_password="$(openssl rand -hex 24)"
|
|
security create-keychain -p "$keychain_password" "$keychain"
|
|
security set-keychain-settings -lut 21600 "$keychain"
|
|
security unlock-keychain -p "$keychain_password" "$keychain"
|
|
security import developer-id.p12 -k "$keychain" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
|
|
security set-key-partition-list -S apple-tool:,apple: -s -k "$keychain_password" "$keychain"
|
|
security list-keychains -d user -s "$keychain"
|
|
|
|
ldflags="$(./scripts/release_ldflags.sh agent \
|
|
--version "v${{ inputs.version }}" \
|
|
--update-public-keys "$PULSE_UPDATE_SIGNING_PUBLIC_KEY")"
|
|
for arch in amd64 arm64; do
|
|
output="native-agent-binaries/pulse-agent-darwin-${arch}"
|
|
GOOS=darwin GOARCH="$arch" CGO_ENABLED=0 \
|
|
go build -buildvcs=false -trimpath -ldflags="$ldflags" -o "$output" ./cmd/pulse-agent
|
|
codesign --force --timestamp --options runtime --sign "$APPLE_SIGNING_IDENTITY" "$output"
|
|
codesign --verify --deep --strict --verbose=2 "$output"
|
|
done
|
|
|
|
ditto -c -k --keepParent native-agent-binaries pulse-agent-macos-notarization.zip
|
|
xcrun notarytool submit pulse-agent-macos-notarization.zip \
|
|
--key AuthKey.p8 \
|
|
--key-id "$APPLE_NOTARY_KEY_ID" \
|
|
--issuer "$APPLE_NOTARY_ISSUER_ID" \
|
|
--wait \
|
|
--output-format json > notarization-result.json
|
|
python3 - <<'PY'
|
|
import json
|
|
from pathlib import Path
|
|
|
|
result = json.loads(Path('notarization-result.json').read_text())
|
|
if result.get('status') != 'Accepted':
|
|
raise SystemExit(f"Apple notarization was not accepted: {result.get('status', 'unknown')}")
|
|
PY
|
|
for binary in native-agent-binaries/pulse-agent-darwin-*; do
|
|
# Gatekeeper's spctl app assessment rejects bare command-line
|
|
# Mach-O binaries even when the notary service accepted them.
|
|
# Verify the signed bytes that will be packaged instead.
|
|
codesign --verify --deep --strict --verbose=2 "$binary"
|
|
done
|
|
|
|
- name: Upload signed macOS binaries
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: signed-macos-agent-${{ github.sha }}-${{ inputs.version }}
|
|
path: native-agent-binaries/
|
|
if-no-files-found: error
|
|
retention-days: 1
|
|
compression-level: 0
|
|
|
|
sign-windows-agent:
|
|
name: Authenticode Sign Windows Agent
|
|
needs: signing-configuration
|
|
if: ${{ inputs.require_windows_signing && needs.signing-configuration.result == 'success' }}
|
|
runs-on: windows-2025
|
|
timeout-minutes: 50
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
outputs:
|
|
signing_backend: ${{ steps.evidence.outputs.signing_backend }}
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: true
|
|
|
|
- name: Build unsigned agent binaries
|
|
shell: pwsh
|
|
env:
|
|
PULSE_UPDATE_SIGNING_PUBLIC_KEY: ${{ vars.PULSE_UPDATE_SIGNING_PUBLIC_KEY }}
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
foreach ($name in @('PULSE_UPDATE_SIGNING_PUBLIC_KEY')) {
|
|
if ([string]::IsNullOrWhiteSpace((Get-Item "Env:$name").Value)) {
|
|
throw "Missing required $name."
|
|
}
|
|
}
|
|
|
|
New-Item -ItemType Directory -Path unsigned-native-agent-binaries -Force | Out-Null
|
|
$ldflags = & bash ./scripts/release_ldflags.sh agent --version "v${{ inputs.version }}" --update-public-keys $env:PULSE_UPDATE_SIGNING_PUBLIC_KEY
|
|
foreach ($arch in @('amd64', 'arm64', '386')) {
|
|
$env:GOOS = 'windows'
|
|
$env:GOARCH = $arch
|
|
$env:CGO_ENABLED = '0'
|
|
$output = "unsigned-native-agent-binaries/pulse-agent-windows-$arch.exe"
|
|
go build -buildvcs=false -trimpath -ldflags="$ldflags" -o $output ./cmd/pulse-agent
|
|
if ($LASTEXITCODE -ne 0) { throw "Go build failed for Windows $arch." }
|
|
}
|
|
|
|
- name: Sign with legacy PFX fallback
|
|
if: ${{ inputs.windows_signing_backend == 'legacy-pfx' }}
|
|
shell: pwsh
|
|
env:
|
|
WINDOWS_CERTIFICATE_PFX_BASE64: ${{ secrets.WINDOWS_CODE_SIGNING_CERTIFICATE_PFX_BASE64 }}
|
|
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CODE_SIGNING_CERTIFICATE_PASSWORD }}
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
$pfxPath = Join-Path $env:RUNNER_TEMP 'pulse-code-signing.pfx'
|
|
[IO.File]::WriteAllBytes($pfxPath, [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_PFX_BASE64))
|
|
$password = ConvertTo-SecureString $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force
|
|
$certificate = Import-PfxCertificate -FilePath $pfxPath -CertStoreLocation Cert:\CurrentUser\My -Password $password -Exportable:$false
|
|
if ($null -eq $certificate) { throw 'Failed to import Windows code-signing certificate.' }
|
|
$signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Filter signtool.exe -Recurse | Sort-Object FullName -Descending | Select-Object -First 1 -ExpandProperty FullName
|
|
foreach ($output in (Get-ChildItem unsigned-native-agent-binaries\*.exe)) {
|
|
& $signtool sign /sha1 $certificate.Thumbprint /fd SHA256 /td SHA256 /tr http://timestamp.digicert.com $output.FullName
|
|
if ($LASTEXITCODE -ne 0) { throw "Authenticode signing failed for $($output.Name)." }
|
|
}
|
|
|
|
- name: Upload unsigned SignPath input
|
|
if: ${{ inputs.windows_signing_backend == 'signpath' }}
|
|
id: upload-unsigned-windows
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: unsigned-windows-agent-${{ github.sha }}-${{ inputs.version }}
|
|
path: unsigned-native-agent-binaries/
|
|
if-no-files-found: error
|
|
retention-days: 1
|
|
compression-level: 0
|
|
|
|
- name: Submit SignPath Authenticode request
|
|
if: ${{ inputs.windows_signing_backend == 'signpath' }}
|
|
id: signpath
|
|
uses: signpath/github-action-submit-signing-request@b9d91eadd323de506c0c81cf0c7fe7438f3360fd # v2
|
|
with:
|
|
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
|
organization-id: ${{ vars.SIGNPATH_ORGANIZATION_ID }}
|
|
project-slug: ${{ vars.SIGNPATH_PROJECT_SLUG }}
|
|
signing-policy-slug: ${{ vars.SIGNPATH_SIGNING_POLICY_SLUG }}
|
|
artifact-configuration-slug: ${{ vars.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }}
|
|
github-artifact-id: ${{ steps.upload-unsigned-windows.outputs.artifact-id }}
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
wait-for-completion: true
|
|
wait-for-completion-timeout-in-seconds: 2400
|
|
output-artifact-directory: signed-native-agent-binaries
|
|
parameters: |
|
|
version: ${{ toJSON(inputs.version) }}
|
|
|
|
- name: Verify Authenticode signatures and write evidence
|
|
id: evidence
|
|
shell: pwsh
|
|
env:
|
|
WINDOWS_SIGNING_BACKEND: ${{ inputs.windows_signing_backend }}
|
|
SIGNPATH_SIGNING_REQUEST_ID: ${{ steps.signpath.outputs.signing-request-id }}
|
|
SIGNPATH_SIGNING_REQUEST_URL: ${{ steps.signpath.outputs.signing-request-web-url }}
|
|
SIGNPATH_INPUT_ARTIFACT_ID: ${{ steps.upload-unsigned-windows.outputs.artifact-id }}
|
|
SIGNPATH_EXPECTED_CERTIFICATE_SUBJECT: ${{ vars.SIGNPATH_EXPECTED_CERTIFICATE_SUBJECT }}
|
|
run: |
|
|
$ErrorActionPreference = 'Stop'
|
|
$sourceDir = if ($env:WINDOWS_SIGNING_BACKEND -eq 'signpath') { 'signed-native-agent-binaries' } else { 'unsigned-native-agent-binaries' }
|
|
New-Item -ItemType Directory -Path native-agent-binaries -Force | Out-Null
|
|
$signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Filter signtool.exe -Recurse | Sort-Object FullName -Descending | Select-Object -First 1 -ExpandProperty FullName
|
|
$files = @()
|
|
foreach ($arch in @('amd64', 'arm64', '386')) {
|
|
$name = "pulse-agent-windows-$arch.exe"
|
|
$source = Join-Path $sourceDir $name
|
|
if (-not (Test-Path $source -PathType Leaf)) { throw "Signed output is missing $name." }
|
|
& $signtool verify /pa /v $source
|
|
if ($LASTEXITCODE -ne 0) { throw "Authenticode verification failed for $name." }
|
|
$signature = Get-AuthenticodeSignature $source
|
|
if ($signature.Status -ne 'Valid' -or $null -eq $signature.SignerCertificate) { throw "Invalid Authenticode status for $name: $($signature.Status)." }
|
|
if ($env:WINDOWS_SIGNING_BACKEND -eq 'signpath' -and $signature.SignerCertificate.Subject -ne $env:SIGNPATH_EXPECTED_CERTIFICATE_SUBJECT) { throw "Unexpected Authenticode signer subject for ${name}: $($signature.SignerCertificate.Subject)." }
|
|
Copy-Item $source (Join-Path native-agent-binaries $name)
|
|
$files += [ordered]@{
|
|
name = $name
|
|
sha256 = (Get-FileHash $source -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
signerSubject = $signature.SignerCertificate.Subject
|
|
signerThumbprint = $signature.SignerCertificate.Thumbprint
|
|
}
|
|
}
|
|
$evidence = [ordered]@{
|
|
schemaVersion = 1
|
|
backend = $env:WINDOWS_SIGNING_BACKEND
|
|
version = '${{ inputs.version }}'
|
|
sourceSha = $env:GITHUB_SHA
|
|
workflowRunUrl = "https://github.com/$env:GITHUB_REPOSITORY/actions/runs/$env:GITHUB_RUN_ID"
|
|
signPathSigningRequestId = $env:SIGNPATH_SIGNING_REQUEST_ID
|
|
signPathSigningRequestUrl = $env:SIGNPATH_SIGNING_REQUEST_URL
|
|
githubInputArtifactId = $env:SIGNPATH_INPUT_ARTIFACT_ID
|
|
files = $files
|
|
}
|
|
$evidence | ConvertTo-Json -Depth 6 | Set-Content windows-signing-evidence.json -Encoding utf8NoBOM
|
|
"signing_backend=$env:WINDOWS_SIGNING_BACKEND" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
|
|
|
|
- name: Upload signed Windows binaries
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: signed-windows-agent-${{ github.sha }}-${{ inputs.version }}
|
|
path: native-agent-binaries/
|
|
if-no-files-found: error
|
|
retention-days: 1
|
|
compression-level: 0
|
|
|
|
- name: Upload Windows signing evidence
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: windows-signing-evidence-${{ github.sha }}-${{ inputs.version }}
|
|
path: windows-signing-evidence.json
|
|
if-no-files-found: error
|
|
retention-days: 30
|
|
|
|
build:
|
|
name: Build and Validate Release Candidate
|
|
needs: [sign-macos-agent, sign-windows-agent]
|
|
if: ${{ always() && (!inputs.require_macos_signing || needs.sign-macos-agent.result == 'success') && (!inputs.require_windows_signing || needs.sign-windows-agent.result == 'success') }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 60
|
|
outputs:
|
|
artifact_name: ${{ steps.identity.outputs.artifact_name }}
|
|
manifest_artifact_name: ${{ steps.identity.outputs.manifest_artifact_name }}
|
|
steps:
|
|
- name: Resolve candidate identity
|
|
id: identity
|
|
run: |
|
|
set -euo pipefail
|
|
echo "artifact_name=release-candidate-${GITHUB_SHA}-${{ inputs.version }}" >> "$GITHUB_OUTPUT"
|
|
echo "manifest_artifact_name=release-candidate-manifest-${GITHUB_SHA}-${{ inputs.version }}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Checkout repository
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Validate candidate identity
|
|
run: |
|
|
set -euo pipefail
|
|
test "$(tr -d '\n' < VERSION)" = "${{ inputs.version }}"
|
|
test "$(git rev-parse HEAD)" = "${GITHUB_SHA}"
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: true
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: '20'
|
|
cache: 'npm'
|
|
cache-dependency-path: 'frontend-modern/package-lock.json'
|
|
|
|
- name: Install release prerequisites
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y zip
|
|
|
|
- name: Set up Helm
|
|
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
|
|
with:
|
|
version: 'v3.15.2'
|
|
|
|
- name: Install Syft
|
|
run: |
|
|
set -euo pipefail
|
|
SYFT_VERSION="1.42.4"
|
|
SYFT_ARCHIVE="syft_${SYFT_VERSION}_linux_amd64.tar.gz"
|
|
SYFT_SHA256="590650c2743b83f327d1bf9bec64f6f83b7fec504187bb84f500c862bf8f2a0f"
|
|
TMP_DIR="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP_DIR"' EXIT
|
|
curl -fsSL "https://github.com/anchore/syft/releases/download/v${SYFT_VERSION}/${SYFT_ARCHIVE}" \
|
|
-o "${TMP_DIR}/${SYFT_ARCHIVE}"
|
|
printf '%s %s\n' "${SYFT_SHA256}" "${TMP_DIR}/${SYFT_ARCHIVE}" | sha256sum --check --
|
|
tar -xzf "${TMP_DIR}/${SYFT_ARCHIVE}" -C "${TMP_DIR}" syft
|
|
install -m 0755 "${TMP_DIR}/syft" /usr/local/bin/syft
|
|
|
|
- name: Download signed macOS binaries
|
|
if: ${{ inputs.require_macos_signing }}
|
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
with:
|
|
name: signed-macos-agent-${{ github.sha }}-${{ inputs.version }}
|
|
path: native-agent-binaries
|
|
|
|
- name: Download signed Windows binaries
|
|
if: ${{ inputs.require_windows_signing }}
|
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
with:
|
|
name: signed-windows-agent-${{ github.sha }}-${{ inputs.version }}
|
|
path: native-agent-binaries
|
|
|
|
- name: Download Windows signing evidence
|
|
if: ${{ inputs.require_windows_signing }}
|
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
with:
|
|
name: windows-signing-evidence-${{ github.sha }}-${{ inputs.version }}
|
|
path: release-candidate-manifest
|
|
|
|
- name: Build release candidate
|
|
run: ./scripts/build-release.sh "${{ inputs.version }}"
|
|
env:
|
|
PULSE_LICENSE_PUBLIC_KEY: ${{ secrets.PULSE_LICENSE_PUBLIC_KEY }}
|
|
PULSE_UPDATE_SIGNING_KEY: ${{ secrets.PULSE_UPDATE_SIGNING_KEY }}
|
|
PULSE_UPDATE_SIGNING_PUBLIC_KEY: ${{ vars.PULSE_UPDATE_SIGNING_PUBLIC_KEY }}
|
|
PULSE_REQUIRE_MACOS_SIGNING: ${{ inputs.require_macos_signing }}
|
|
PULSE_REQUIRE_WINDOWS_SIGNING: ${{ inputs.require_windows_signing }}
|
|
PULSE_AGENT_NATIVE_BINARIES_DIR: ${{ (inputs.require_macos_signing || inputs.require_windows_signing) && format('{0}/native-agent-binaries', github.workspace) || '' }}
|
|
|
|
- name: Validate installer signing key pins
|
|
env:
|
|
PULSE_UPDATE_SIGNING_PUBLIC_KEY: ${{ vars.PULSE_UPDATE_SIGNING_PUBLIC_KEY }}
|
|
run: |
|
|
set -euo pipefail
|
|
TRUSTED_SSH_PUBLIC_KEY="$(
|
|
go run ./scripts/release_update_key.go public-key-ssh \
|
|
--public-key "${PULSE_UPDATE_SIGNING_PUBLIC_KEY}" \
|
|
--comment pulse-installer
|
|
)"
|
|
for installer in install.sh scripts/pulse-auto-update.sh release/pulse-auto-update.sh; do
|
|
grep -F "PINNED_RELEASE_SSH_PUBLIC_KEY=\"${TRUSTED_SSH_PUBLIC_KEY}\"" "${installer}" >/dev/null || {
|
|
echo "::error::${installer} does not trust the configured release signing key."
|
|
exit 1
|
|
}
|
|
done
|
|
|
|
- name: Validate complete candidate locally
|
|
run: ./scripts/validate-release.sh "${{ inputs.version }}" --skip-docker
|
|
|
|
- name: Create immutable candidate manifest
|
|
run: |
|
|
python3 scripts/release_candidate_manifest.py create \
|
|
--release-dir release \
|
|
--version "${{ inputs.version }}" \
|
|
--source-sha "${GITHUB_SHA}" \
|
|
--output release-candidate-manifest/release-candidate.json
|
|
|
|
- name: Upload immutable release candidate
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: ${{ steps.identity.outputs.artifact_name }}
|
|
path: release/
|
|
if-no-files-found: error
|
|
retention-days: 1
|
|
compression-level: 0
|
|
overwrite: true
|
|
|
|
- name: Upload candidate manifest
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: ${{ steps.identity.outputs.manifest_artifact_name }}
|
|
path: release-candidate-manifest/
|
|
if-no-files-found: error
|
|
retention-days: 1
|
|
overwrite: true
|