release: add MacOS signing to CI - #49
Merged
Merged
Conversation
guygrigsby
commented
Sep 23, 2026
Contributor
- release: strip resource forks and xattrs from notarized zips
- release: sign and notarize macOS builds in CI
- release: add curl installer and document the macOS install paths
ditto -c -k stores the built binary's xattrs (notably com.apple.provenance, which modern macOS stamps on every built file) as AppleDouble ._* entries inside the zip. Archive Utility on current macOS extracts those as literal files, a pattern already observed breaking Gatekeeper assessment of Finder-extracted downloads elsewhere. The binaries are bare Mach-O: no resource forks or xattrs need preserving, so --norsrc --noextattr removes the pollution at the source.
The certificate lives on one laptop, so every release depends on that machine being around and awake. With the certificate and notarization credentials in GitHub secrets, a macOS job can run the same Makefile targets the local flow uses: setup-macos-signing.sh builds a temporary keychain (adapted from the unmerged APT-331 branch), then release-mac-notarized and upload-mac produce and publish the signed zips. One implementation of asset replacement keeps CI and local identical. Tradeoff: GoReleaser still publishes unsigned darwin tarballs first, and they sit on the release until upload-mac replaces them, the length of one notarization. Cutting darwin from the goreleaser config would close that window but fork the local and CI release definitions. Revisit if anyone ships a workaround that scrapes the tarballs in that gap. Not exercised end to end until the secrets exist and a tag is pushed; the Makefile path itself is the one that produced v0.0.13.
Browser downloads set the quarantine attribute, and managed Macs can fail Gatekeeper's online notarization lookup even for properly notarized binaries: reproduced on a corp Mac with terraform's own signed release. So the README needs two things the go install line never had: a quarantine-free install path and the Open Anyway instructions for people who do click the zip in a browser. install.sh resolves the latest tag, downloads the matching asset and verifies it against the release checksums.txt before installing. Verified on linux amd64 against v0.0.13: latest-tag resolution, checksum accept, checksum reject on a corrupted download, and -version on the installed binary. The darwin branch is untested here (no Mac in the sandbox); the zip layout it expects was confirmed against the published v0.0.13 archive.
The box recreated two commits after origin already held their signed versions. sand replayed the replacements over the published additions and hit a workflow conflict. Keep the published commits and record the intended changes as one follow-up. Its tree is identical to 9686877. Rebase would repeat the original additions; replacing origin would discard signed history. Revisit only if the published commits themselves must be removed.
The two-job design published unsigned darwin tarballs and replaced them afterwards, so for minutes every release carried binaries Gatekeeper would block, and a failed signing job left them there permanently. Move signing into the pipeline: one macOS job runs goreleaser with a build post-hook (scripts/sign-macos.sh) that codesigns and notarizes each darwin binary before archiving, failing the run on anything but Accepted. Linux cross-compiles with CGO off. GoReleaser's native notarization is Pro-only, which is why this is a hook script. PR dry runs get no keychain setup, so the hook passes binaries through unsigned. The keychain prep and the fake-Apple-tool hook test are adapted from the unmerged APT-331 branch, with the keychain created once per job instead of once per binary. upload-mac stays as the local fallback for a release the workflow could not sign.
CI now ships darwin as goreleaser tarballs like linux, so the installer looks for aperture-cli_darwin_<arch>.tar.gz first and falls back to the zips the local upload-mac flow still produces. The README releasing section describes the hook-based workflow and marks the Makefile flow as the fallback. Verified end to end on linux and against the v0.0.13 darwin zip fallback: download, checksum, extract, install, -version.
Missing signing setup let direct GoReleaser releases publish unsigned Darwin binaries. An exact JSON grep also rejected valid Accepted responses while discarding notarytool failures. Use the snapshot flag to permit unsigned builds only when GoReleaser cannot publish. Require a successful submission and one Accepted JSON object through jq. Widening the grep would still accept nested or malformed results. The exit trap removes submissions on failure too. Keep the hook gate until the release tool provides equivalent built-in signing and notarization checks. Regression tests failed before the fix; make check and the fresh security review now pass. Real GoReleaser snapshots package all four targets, while a non-snapshot run without signing setup stops before archiving. Apple signing still needs the credentialed macOS run.
Workflow edits started a full macOS snapshot build on PRs even though Linux CI already runs make check. Release execution belongs to tag pushes and explicit release dispatches. Removing the PR trigger avoids conditional secret handling and snapshot branches in the release workflow. Local snapshots remain available. Revisit separate packaging validation only if the existing PR gate misses a concrete packaging regression. The regression test failed before the change. make check, actionlint and an independent security review pass. Hosted Actions execution remains unverified from this sandbox.
Setup failures could leave certificate material without an exported cleanup path, and notarization credentials stayed in the login keychain outside teardown. Store both in the temporary keychain and remove the exported certificate before setup succeeds. The workflow teardown now reports deletion failure instead of hiding it. Setup failure cleanup still suppresses secondary deletion errors so the original setup failure remains visible. Revisit the local fallback separately; it intentionally uses the developer login keychain. Regression tests cover the hook requiring the temporary keychain and passing it to notarytool. shellcheck, make check and a fresh independent security review pass. Apple credential storage and hosted teardown remain unverified until the credentialed macOS run.
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.