From c8e24f06d7fef197cc3c2b03dfb2b622d4622c9c Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 1 May 2026 23:12:41 +0100 Subject: [PATCH] Fix clean VCS metadata for release builds --- .github/workflows/deploy-demo-server.yml | 1 + Dockerfile | 14 ++++++++++++++ .../subsystems/deployment-installability.md | 6 ++++++ scripts/build-release.sh | 5 +++-- scripts/installtests/build_release_assets_test.go | 12 ++++++++++++ 5 files changed, 36 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy-demo-server.yml b/.github/workflows/deploy-demo-server.yml index 21bd103d4..78f5c070a 100644 --- a/.github/workflows/deploy-demo-server.yml +++ b/.github/workflows/deploy-demo-server.yml @@ -130,6 +130,7 @@ jobs: SERVER_LDFLAGS="$(if [ -n "${PULSE_LICENSE_PUBLIC_KEY:-}" ]; then ./scripts/release_ldflags.sh server --version "${VERSION}" --build-time "${BUILD_TIME}" --git-commit "${GIT_COMMIT}" --license-public-key "${PULSE_LICENSE_PUBLIC_KEY}"; else ./scripts/release_ldflags.sh server --version "${VERSION}" --build-time "${BUILD_TIME}" --git-commit "${GIT_COMMIT}"; fi)" CGO_ENABLED=0 go build \ -ldflags="${SERVER_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse ./cmd/pulse/ diff --git a/Dockerfile b/Dockerfile index 99affc323..7bac52f61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -86,11 +86,13 @@ RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ -tags release \ -ldflags="${SERVER_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-linux-amd64 ./cmd/pulse && \ CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build \ -tags release \ -ldflags="${SERVER_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-linux-arm64 ./cmd/pulse @@ -111,50 +113,62 @@ RUN --mount=type=cache,id=pulse-go-mod,target=/go/pkg/mod \ AGENT_LDFLAGS="$(./scripts/release_ldflags.sh agent --version "${VERSION}" $(if [ -n "${UPDATE_PUBLIC_KEYS}" ]; then printf '%s %s' --update-public-keys "${UPDATE_PUBLIC_KEYS}"; fi))" && \ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \ -ldflags="${AGENT_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-agent-linux-amd64 ./cmd/pulse-agent && \ CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build \ -ldflags="${AGENT_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-agent-linux-arm64 ./cmd/pulse-agent && \ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build \ -ldflags="${AGENT_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-agent-linux-armv7 ./cmd/pulse-agent && \ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build \ -ldflags="${AGENT_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-agent-linux-armv6 ./cmd/pulse-agent && \ CGO_ENABLED=0 GOOS=linux GOARCH=386 go build \ -ldflags="${AGENT_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-agent-linux-386 ./cmd/pulse-agent && \ CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build \ -ldflags="${AGENT_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-agent-darwin-amd64 ./cmd/pulse-agent && \ CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build \ -ldflags="${AGENT_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-agent-darwin-arm64 ./cmd/pulse-agent && \ CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build \ -ldflags="${AGENT_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-agent-windows-amd64.exe ./cmd/pulse-agent && \ CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build \ -ldflags="${AGENT_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-agent-windows-arm64.exe ./cmd/pulse-agent && \ CGO_ENABLED=0 GOOS=windows GOARCH=386 go build \ -ldflags="${AGENT_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-agent-windows-386.exe ./cmd/pulse-agent && \ CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build \ -ldflags="${AGENT_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-agent-freebsd-amd64 ./cmd/pulse-agent && \ CGO_ENABLED=0 GOOS=freebsd GOARCH=arm64 go build \ -ldflags="${AGENT_LDFLAGS}" \ + -buildvcs=false \ -trimpath \ -o pulse-agent-freebsd-arm64 ./cmd/pulse-agent diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 5d18177e8..add304b2d 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -206,6 +206,12 @@ server-side update execution surfaces. External helper binaries fetched by governed release workflows are part of the same supply-chain boundary and must be checksum-verified before they are executed. + Release-grade Go builds must use `scripts/release_ldflags.sh` as the + canonical source for embedded version, commit, license, and update trust-root + identity, and must disable Go's automatic VCS stamping with + `-buildvcs=false` in `scripts/build-release.sh`, `Dockerfile`, and the demo + deployment build so generated frontend or release-packet files cannot leak a + misleading dirty-tree marker into published binary metadata. Release validation must prove that installer script download endpoints return signature headers, and unified-agent download endpoints must return checksum and signature headers whose checksum value matches the served binary. 8. Add or change the non-secret Pulse Cloud public signup route smoke through diff --git a/scripts/build-release.sh b/scripts/build-release.sh index e0ef01221..18c20fa0a 100755 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -32,6 +32,7 @@ fi # Force static binaries so release artifacts run on older glibc hosts export CGO_ENABLED=0 +release_go_build_args=(-buildvcs=false -trimpath) VERSION=${1:-$(cat VERSION)} BUILD_DIR="build" @@ -141,7 +142,7 @@ for i in "${!agent_build_order[@]}"; do env $build_env go build \ -ldflags="${agent_ldflags}" \ - -trimpath \ + "${release_go_build_args[@]}" \ -o "$output_path" \ ./cmd/pulse-agent done @@ -179,7 +180,7 @@ for i in "${!build_order[@]}"; do env $build_env go build \ -tags release \ -ldflags="${server_ldflags}" \ - -trimpath \ + "${release_go_build_args[@]}" \ -o "$BUILD_DIR/pulse-$build_name" \ ./cmd/pulse done diff --git a/scripts/installtests/build_release_assets_test.go b/scripts/installtests/build_release_assets_test.go index 6d4dda762..476b4ddc1 100644 --- a/scripts/installtests/build_release_assets_test.go +++ b/scripts/installtests/build_release_assets_test.go @@ -47,6 +47,8 @@ func TestBuildReleaseUsesV6InstallScripts(t *testing.T) { requiredScriptWiring := []string{ `agent_ldflags="$(./scripts/release_ldflags.sh agent --version "v${VERSION}" "${update_ldflags_args[@]}")"`, `server_ldflags="$(./scripts/release_ldflags.sh server --version "v${VERSION}" --build-time "${build_time}" --git-commit "${git_commit}" "${license_ldflags_args[@]}" "${update_ldflags_args[@]}")"`, + `release_go_build_args=(-buildvcs=false -trimpath)`, + `"${release_go_build_args[@]}"`, `RELEASE_PACKET_SBOM="pulse-v${VERSION}-release.sbom.spdx.json"`, `pulse_release_prepare_signing_state "pulse-installer" "pulse-install"`, `trap 'pulse_release_cleanup_signing_state' EXIT`, @@ -60,6 +62,9 @@ func TestBuildReleaseUsesV6InstallScripts(t *testing.T) { t.Fatalf("build-release.sh missing canonical ldflags wiring: %s", needle) } } + if builds, cleanBuilds := strings.Count(script, `env $build_env go build \`), strings.Count(script, `"${release_go_build_args[@]}"`); builds != cleanBuilds { + t.Fatalf("build-release.sh must disable automatic VCS stamping on every release go build: builds=%d clean_builds=%d", builds, cleanBuilds) + } helperBytes, err := os.ReadFile(repoFile("scripts", "release_asset_common.sh")) if err != nil { @@ -278,6 +283,8 @@ func TestReleaseValidationRequiresSignedSidecars(t *testing.T) { "`checksums.txt` is missing its `.sshsig` sidecar", "release-packet SBOM is absent", "download endpoints must return checksum and signature headers", + "must disable Go's automatic VCS stamping", + "`-buildvcs=false`", } for _, needle := range contractRequired { if !strings.Contains(contract, needle) { @@ -314,6 +321,7 @@ func TestDockerAndDemoBuildsUseCanonicalReleaseLdflags(t *testing.T) { `mounted update signing key does not match PULSE_UPDATE_SIGNING_PUBLIC_KEY.`, `./scripts/release_ldflags.sh server --version "${VERSION}" --build-time "${BUILD_TIME}" --git-commit "${GIT_COMMIT}"`, `./scripts/release_ldflags.sh agent --version "${VERSION}"`, + `-buildvcs=false`, `go run ./scripts/render_installers.go --source-dir ./scripts --output-dir /app/rendered-installers`, `--allow-empty-installer-ssh-public-key`, `ssh-keygen -q -Y sign -f "${OPENSSH_SIGNING_KEY}" -n pulse-install`, @@ -340,6 +348,9 @@ func TestDockerAndDemoBuildsUseCanonicalReleaseLdflags(t *testing.T) { strings.Contains(dockerfile, `FROM alpine:3.20 AS pulse-runtime-base`) { t.Fatal("Dockerfile base images must be pinned by immutable @sha256 digests") } + if builds, cleanBuilds := strings.Count(dockerfile, " go build \\"), strings.Count(dockerfile, "-buildvcs=false"); builds != cleanBuilds { + t.Fatalf("Dockerfile release go builds must all disable automatic VCS stamping: builds=%d clean_builds=%d", builds, cleanBuilds) + } workflowBytes, err := os.ReadFile(repoFile(".github", "workflows", "deploy-demo-server.yml")) if err != nil { @@ -348,6 +359,7 @@ func TestDockerAndDemoBuildsUseCanonicalReleaseLdflags(t *testing.T) { workflow := string(workflowBytes) workflowRequired := []string{ `./scripts/release_ldflags.sh server --version "${VERSION}" --build-time "${BUILD_TIME}" --git-commit "${GIT_COMMIT}"`, + `-buildvcs=false`, `demo-preview-v6`, `demo-stable`, `workflow_dispatch:`,