Automate releases via tag-triggered workflow - #332
Open
alex-vulncheck wants to merge 2 commits into
Open
Conversation
Releases were cut by hand from a single machine in ~10 steps. The workflow that was supposed to do this, deployment.yml, has never run and could not work: it is an unmodified fork of github/cli's pipeline, referencing six files that do not exist, globbing dist/gh_* where GoReleaser emits vulncheck_*, and pushing to github/cli.github.com and a third-party homebrew-core fork. Releases are now cut with `script/tag-release vX.Y.Z`, which validates and pushes a tag. The tag triggers release.yml, which builds all nine artifacts on a single macos-latest runner. codesign and notarytool are macOS-only and osslsigncode also runs there, so github/cli's three-runner matrix (needed because they sign Windows with Azure signtool) is unnecessary here. Publishing happens last, after verification, so a failed signature or notarization leaves nothing public. script/verify-release enforces what was previously a manual checklist: archive set, checksum manifest, Developer ID authority, secure timestamp, Gatekeeper assessment against a quarantined copy, embedded version, and Windows signature digest plus RFC3161 timestamp. Asserting on the authority matters because codesign --verify alone is satisfied by the ad-hoc signature the Go toolchain applies to arm64 binaries. script/sign is hardened: notarytool now runs with --wait and fails on any non-Accepted status rather than returning as soon as the upload completes, osslsigncode passes -ts so signatures survive certificate expiry, the certificate is checked for expiry before signing, and REQUIRE_SIGNING=1 turns a missing credential into an error so a misconfigured secret cannot silently ship unsigned binaries. Also publishes the checksum manifest, which was being built and discarded, and attests build provenance for it. .goreleaser.yml moves to schema v2, dropping deprecated archives.format and archives.builds, and the unused nfpms block goes with it.
A single hardcoded timestamp authority could block a release on its own, since signing now refuses to emit an untimestamped signature. Try each authority in turn until one returns a usable token. GlobalSign is first because it issued the signing certificate, so it adds no trust relationship we do not already depend on; the others are there for an outage. Timestamping cannot be self-hosted — Windows only honours a token whose TSA certificate carries the Time Stamping EKU and chains to a root in the Microsoft Trusted Root Program. Each attempt is accepted only once the timestamp is confirmed present in the output, so an authority that answers without returning a usable token falls through instead of yielding a signature that dies with the certificate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replaces the manual release process with a tag-triggered workflow.
Why
.github/workflows/deployment.ymlhas never run, and could not work if it did. It is an unmodified fork ofgithub/cli's release pipeline:script/sign.ps1,script/label-assets,script/createrepo.sh,script/rpmmacros,script/distributions,build/windows/gh.wixproj)dist/gh_*, but GoReleaser here emitsvulncheck_*; combined withshopt -s failglob, every asset step hard-failsgithub/cli.github.comand a Homebrew formula to a third-partyhomebrew-coreforkSo releases have in practice been built and uploaded by hand, one machine, about ten steps, with signature and notarization success confirmed manually each time.
What this does
Releases are now cut with:
That validates (semver, on
main, clean tree, in sync withorigin/main, tag unused), confirms, tags and pushes. The tag triggers.github/workflows/release.yml.One
macos-latestrunner builds all nine artifacts.codesignandnotarytoolare macOS-only, andosslsigncoderuns there too, sogithub/cli's three-runner matrix — which exists because they sign Windows with Azuresigntool— is unnecessary here. Cross-compilation is CGO-free, whichtest.ymlalready documents as the assumption.Publishing is the last step, after verification, so a failed signature or notarization leaves nothing public.
script/verify-releaseEnforces what was previously a manual checklist: archive set, checksum manifest, Developer ID authority, secure timestamp, Gatekeeper assessment against a quarantined copy, embedded version, and Windows signature digest plus RFC3161 timestamp.
Asserting on the authority matters:
codesign --verifyon its own is satisfied by the ad-hoc signature the Go toolchain applies to arm64 binaries, so an unsigned build would otherwise pass.script/signhardeningnotarytoolruns with--waitand fails on any non-Acceptedstatus, instead of returning as soon as the upload finishesosslsigncodepasses-ts, so signatures survive certificate rollover — Authenticode validates against current time, not signing time, without a countersignatureREQUIRE_SIGNING=1makes a missing credential an error, so a misconfigured secret cannot silently ship unsigned binariesOther
vulncheck_X.Y.Z_checksums.txt, which was being built and discarded, and attests build provenance for it.goreleaser.ymlmigrated to schema v2 (archives.format→formats,archives.builds→ids);goreleaser checkpreviously failed on deprecated propertiesnfpmsdeb/rpm blockdeployment.yml,script/release,script/sign-oldVerification
goreleaser checkpasses on the migrated configscript/verify-releaserejects an unsigned snapshot (13 failures, non-zero exit) and passes all five macOS assertions against the real signed v1.1.0 artifact — no false positives or negativesNot yet exercised: keychain import,
osslsigncodeinstallation, and notarization in CI can only be proven by a real run. Intended shakedown is a prerelease tag (v1.1.1-rc.1), which also covers the--prereleasebranch.Note on Windows signing
The workflow requires a currently-valid Windows code-signing certificate and fails fast if it is not, rather than emitting a signature that will not be trusted.