Allow RCs while Windows signing is pending

This commit is contained in:
rcourtman
2026-07-10 23:12:12 +01:00
parent d5c4f9adf3
commit d5437a9353
9 changed files with 113 additions and 31 deletions
+38 -19
View File
@@ -7,8 +7,13 @@ on:
description: 'Version number without the leading v'
required: true
type: string
require_platform_signing:
description: 'Require Developer ID/notarized macOS and Authenticode Windows agent binaries'
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
@@ -26,7 +31,7 @@ permissions:
jobs:
signing-configuration:
name: Verify Native Signing Configuration
if: ${{ inputs.require_platform_signing }}
if: ${{ inputs.require_macos_signing || inputs.require_windows_signing }}
runs-on: ubuntu-24.04
timeout-minutes: 2
steps:
@@ -40,18 +45,31 @@ jobs:
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 }}
REQUIRE_MACOS_SIGNING: ${{ inputs.require_macos_signing }}
REQUIRE_WINDOWS_SIGNING: ${{ inputs.require_windows_signing }}
run: |
set -euo pipefail
missing=0
for name in \
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 \
WINDOWS_CODE_SIGNING_CERTIFICATE_PFX_BASE64 \
WINDOWS_CODE_SIGNING_CERTIFICATE_PASSWORD; do
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
required+=(
WINDOWS_CODE_SIGNING_CERTIFICATE_PFX_BASE64
WINDOWS_CODE_SIGNING_CERTIFICATE_PASSWORD
)
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
@@ -62,7 +80,7 @@ jobs:
sign-macos-agent:
name: Sign and Notarize macOS Agent
needs: signing-configuration
if: ${{ inputs.require_platform_signing && needs.signing-configuration.result == 'success' }}
if: ${{ inputs.require_macos_signing && needs.signing-configuration.result == 'success' }}
runs-on: macos-15
timeout-minutes: 30
steps:
@@ -160,7 +178,7 @@ jobs:
sign-windows-agent:
name: Authenticode Sign Windows Agent
needs: signing-configuration
if: ${{ inputs.require_platform_signing && needs.signing-configuration.result == 'success' }}
if: ${{ inputs.require_windows_signing && needs.signing-configuration.result == 'success' }}
runs-on: windows-2025
timeout-minutes: 25
steps:
@@ -225,7 +243,7 @@ jobs:
build:
name: Build and Validate Signed Candidate
needs: [sign-macos-agent, sign-windows-agent]
if: ${{ always() && (!inputs.require_platform_signing || (needs.sign-macos-agent.result == 'success' && needs.sign-windows-agent.result == 'success')) }}
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:
@@ -288,14 +306,14 @@ jobs:
install -m 0755 "${TMP_DIR}/syft" /usr/local/bin/syft
- name: Download signed macOS binaries
if: ${{ inputs.require_platform_signing }}
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_platform_signing }}
if: ${{ inputs.require_windows_signing }}
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: signed-windows-agent-${{ github.sha }}-${{ inputs.version }}
@@ -307,8 +325,9 @@ jobs:
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_PLATFORM_SIGNING: ${{ inputs.require_platform_signing }}
PULSE_AGENT_NATIVE_BINARIES_DIR: ${{ inputs.require_platform_signing && format('{0}/native-agent-binaries', github.workspace) || '' }}
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:
+2 -1
View File
@@ -218,7 +218,8 @@ jobs:
secrets: inherit
with:
version: ${{ needs.prepare.outputs.version }}
require_platform_signing: true
require_macos_signing: true
require_windows_signing: ${{ needs.prepare.outputs.is_prerelease != 'true' }}
# Frontend checks run in parallel with backend tests
frontend_checks:
+2 -1
View File
@@ -68,7 +68,8 @@ jobs:
secrets: inherit
with:
version: ${{ inputs.version }}
require_platform_signing: true
require_macos_signing: true
require_windows_signing: true
dry-run:
name: Preflight Release Checks (No Publish)
@@ -131,3 +131,30 @@ demo runtime unchanged.
Blocked only on the absent native platform signing credentials. Repository-side
orchestration, diagnostics, timeout hardening, archive validation performance,
and late backend-flake containment are implemented and covered by contracts.
## Prerelease Windows Signing Boundary (2026-07-10)
The first `v6.0.6-rc.1` publication attempt proved Apple signing and
notarization credentials are configured, but Windows Authenticode credentials
are not. Pulse Monitoring Ltd submitted the public community project to the
SignPath Foundation open-source programme on 2026-07-10; approval and CI
integration remain externally owned and asynchronous.
RC publication may proceed while that application is pending, provided all of
the following remain true:
- macOS agent binaries are Developer ID signed and notarized;
- Windows agent binaries retain the exact-SHA candidate, checksums, detached
release signatures, and post-publication digest verification;
- the RC release notes state explicitly that Windows binaries are not
Authenticode-signed and may show an unknown-publisher warning; and
- stable publication continues to require successful Windows Authenticode
signing and verification.
The reusable candidate workflow therefore separates the macOS platform-signing
requirement from the Windows Authenticode requirement. `create-release.yml`
requires Windows signing for stable versions and relaxes only that requirement
for recognized prerelease versions. `release-dry-run.yml` continues to require
both platforms so the full stable-promotion path remains rehearsed and the
`single-build-release-promotion-path` release gate remains blocked for stable
release readiness until the external signing path is proven.
@@ -2301,6 +2301,17 @@ release-packet SBOM is absent so published RC/stable downloads can keep the
updater and installer trust chain fail-closed instead of downgrading to
checksum-only trust and can publish a shareable non-image software inventory
alongside the signed binaries.
The immutable candidate builder must model macOS Developer ID/notarization and
Windows Authenticode as independent native-signing requirements rather than one
all-or-nothing platform switch. Governed RC publication may require signed and
notarized macOS agent binaries while Windows Authenticode approval is still an
externally owned bounded residual, but only when the RC packet explicitly
discloses the unsigned Windows publisher state and the Windows binaries retain
the exact-SHA candidate, checksum, detached-signature, and post-publication
digest controls. Stable publication and the stable-path dry-run must continue
to require both native signing lanes. `scripts/build-release.sh` must replace
only the native targets required by those independent inputs and must fail
closed when a required native-binary directory or target is absent.
Historical published-release repair must flow through
`scripts/backfill-release-assets.sh` and
`.github/workflows/backfill-release-assets.yml` or the canonical
@@ -44,6 +44,8 @@ behavior, and broad security and reliability hardening.
fail fast when signing configuration is incomplete.
- Docker updates now recreate the container instead of attempting a restart
that cannot apply a new image.
- Docker containers retain their grouped-by-host view and open configured web
links consistently after REST resource snapshot hydration.
- Docker and Kubernetes agents tolerate realistic clock skew when evaluating
liveness, and posture alerts no longer ignore the intended guest-suppression
rules.
@@ -71,5 +73,10 @@ Pulse Mobile candidate builds with runtime version 1 receive the matching
typed-action approval client through the candidate OTA channel; no public store
rollout is part of this RC.
Windows Unified Agent binaries in this release candidate retain the same
checksum and detached-signature verification used by `v6.0.5`, but they are
not yet Authenticode-signed and Windows may show an unknown-publisher warning.
Public Windows Authenticode signing remains required before stable promotion.
Paid Pulse Pro, Relay, and eligible legacy customers should continue to use the
private download page and private runtime image for paid runtime features.
+10 -4
View File
@@ -152,7 +152,13 @@ done
# so the immutable candidate manifest covers the exact signed bytes users get.
if [[ -n "${PULSE_AGENT_NATIVE_BINARIES_DIR:-}" ]]; then
native_dir="${PULSE_AGENT_NATIVE_BINARIES_DIR}"
native_targets=(darwin-amd64 darwin-arm64 windows-amd64 windows-arm64 windows-386)
native_targets=()
if [[ "${PULSE_REQUIRE_MACOS_SIGNING:-false}" == "true" ]]; then
native_targets+=(darwin-amd64 darwin-arm64)
fi
if [[ "${PULSE_REQUIRE_WINDOWS_SIGNING:-false}" == "true" ]]; then
native_targets+=(windows-amd64 windows-arm64 windows-386)
fi
for target in "${native_targets[@]}"; do
filename="pulse-agent-${target}"
if [[ "$target" == windows-* ]]; then
@@ -164,9 +170,9 @@ if [[ -n "${PULSE_AGENT_NATIVE_BINARIES_DIR:-}" ]]; then
fi
cp "${native_dir}/${filename}" "${BUILD_DIR}/${filename}"
done
echo "Applied platform-native signed Unified Agent binaries."
elif [[ "${PULSE_REQUIRE_PLATFORM_SIGNING:-false}" == "true" ]]; then
echo "Error: platform signing is required but PULSE_AGENT_NATIVE_BINARIES_DIR is empty." >&2
echo "Applied required platform-native signed Unified Agent binaries."
elif [[ "${PULSE_REQUIRE_MACOS_SIGNING:-false}" == "true" || "${PULSE_REQUIRE_WINDOWS_SIGNING:-false}" == "true" ]]; then
echo "Error: required native signing is enabled but PULSE_AGENT_NATIVE_BINARIES_DIR is empty." >&2
exit 1
fi
@@ -709,7 +709,8 @@ func TestAgentRuntimeImagePersistsAgentIdentityByDefault(t *testing.T) {
func TestReleaseCandidateRequiresPlatformNativeAgentSigning(t *testing.T) {
candidateWorkflowPath := repoFile(".github", "workflows", "build-release-candidate.yml")
assertFileContainsAll(t, candidateWorkflowPath,
`require_platform_signing:`,
`require_macos_signing:`,
`require_windows_signing:`,
`sign-macos-agent:`,
`codesign --force --timestamp --options runtime`,
`xcrun notarytool submit`,
@@ -729,13 +730,18 @@ func TestReleaseCandidateRequiresPlatformNativeAgentSigning(t *testing.T) {
t.Fatal("bare command-line Mach-O binaries must not use Gatekeeper app assessment after notarization")
}
assertFileContainsAll(t, repoFile(".github", "workflows", "create-release.yml"),
`require_platform_signing: true`,
`require_macos_signing: true`,
`require_windows_signing: ${{ needs.prepare.outputs.is_prerelease != 'true' }}`,
)
assertFileContainsAll(t, repoFile("scripts", "build-release.sh"),
`PULSE_AGENT_NATIVE_BINARIES_DIR`,
`native_targets=(darwin-amd64 darwin-arm64 windows-amd64 windows-arm64 windows-386)`,
`Applied platform-native signed Unified Agent binaries.`,
`platform signing is required but PULSE_AGENT_NATIVE_BINARIES_DIR is empty.`,
`native_targets=()`,
`PULSE_REQUIRE_MACOS_SIGNING:-false`,
`native_targets+=(darwin-amd64 darwin-arm64)`,
`PULSE_REQUIRE_WINDOWS_SIGNING:-false`,
`native_targets+=(windows-amd64 windows-arm64 windows-386)`,
`Applied required platform-native signed Unified Agent binaries.`,
`required native signing is enabled but PULSE_AGENT_NATIVE_BINARIES_DIR is empty.`,
)
}
@@ -486,7 +486,8 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
self.assertIn("rc-to-ga-rehearsal-summary", workflow)
self.assertIn("build_release_candidate:", workflow)
self.assertIn("if: ${{ inputs.version != '' }}", workflow)
self.assertIn("require_platform_signing: true", workflow)
self.assertIn("require_macos_signing: true", workflow)
self.assertIn("require_windows_signing: true", workflow)
self.assertNotIn("if: ${{ github.event_name == 'workflow_dispatch' }}", workflow)
self.assertIn("record_rc_to_ga_rehearsal.py --run-id ${{ github.run_id }}", workflow)
self.assertIn("rc-to-ga-promotion-readiness-rehearsal-<record-date>.md", workflow)
@@ -623,6 +624,9 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
self.assertIn("timeout-minutes: 60", candidate_workflow)
self.assertIn("Verify Native Signing Configuration", candidate_workflow)
self.assertEqual(candidate_workflow.count("needs: signing-configuration"), 2)
self.assertIn("require_windows_signing: ${{ needs.prepare.outputs.is_prerelease != 'true' }}", content)
self.assertIn('if [[ "$REQUIRE_WINDOWS_SIGNING" == "true" ]]', candidate_workflow)
self.assertIn("inputs.require_windows_signing", candidate_workflow)
for signing_secret in (
"APPLE_DEVELOPER_ID_CERTIFICATE_P12_BASE64",
"APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD",