Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ jobs:
- uses: sigstore/cosign-installer@v3
- uses: anchore/sbom-action/download-syft@v0

- name: Install rcodesign (Apple code-sign + notarize CLI)
run: |
version=0.29.0
archive=apple-codesign-${version}-x86_64-unknown-linux-musl.tar.gz
curl -fsSL "https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F${version}/${archive}" \
| tar -xz -C /tmp
sudo install /tmp/apple-codesign-${version}-x86_64-unknown-linux-musl/rcodesign /usr/local/bin/rcodesign
rcodesign --version

- uses: goreleaser/goreleaser-action@v7
id: goreleaser
with:
Expand All @@ -41,6 +50,18 @@ jobs:
# `.goreleaser.yaml`'s `homebrew_casks.repository.token` template
# reads this env var.
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
# Read by scripts/sign-macos.sh, invoked from goreleaser's
# builds.hooks.post. Absent → script no-ops.
MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }}
MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }}

- name: Notarize darwin binaries
if: env.MACOS_NOTARY_KEY != ''
run: ./scripts/notarize-macos.sh dist
env:
MACOS_NOTARY_KEY: ${{ secrets.MACOS_NOTARY_KEY }}
MACOS_NOTARY_KEY_ID: ${{ secrets.MACOS_NOTARY_KEY_ID }}
MACOS_NOTARY_ISSUER_ID: ${{ secrets.MACOS_NOTARY_ISSUER_ID }}

- name: Attest build provenance
uses: actions/attest-build-provenance@v4
Expand Down
8 changes: 8 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ builds:
# 32-bit ARM windows is not a supported Go target.
- goos: windows
goarch: arm
hooks:
post:
# Apple Developer ID code-signing for darwin binaries via
# `rcodesign` (Rust). Goreleaser's native notarize uses Go's
# stdlib x509 which can't parse Apple's critical extension OIDs,
# so we shell out to rcodesign instead.
# Skips silently for non-darwin and when MACOS_SIGN_P12 is unset.
- cmd: scripts/sign-macos.sh "{{ .Path }}" "{{ .Os }}"

archives:
- formats: [tar.gz]
Expand Down
69 changes: 69 additions & 0 deletions scripts/notarize-macos.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# notarize-macos.sh — submit darwin binaries from a goreleaser dist/ dir
# to Apple's notary service via rcodesign.
#
# Called as a post-goreleaser step in release.yaml. For each
# refuse_darwin_*.tar.gz in dist/, extract the binary, zip it on its
# own (Apple's notary service accepts .zip, not .tar.gz), submit, and
# wait for approval. We don't staple the ticket — stapling only works
# on bundles/.pkg/.dmg, not plain Mach-O binaries — but the
# notarization is recorded server-side and Gatekeeper online-queries
# Apple at first launch.
#
# Requires (set as workflow env from secrets):
# MACOS_NOTARY_KEY — base64 of the App Store Connect .p8 key
# MACOS_NOTARY_KEY_ID — the 10-char Key ID
# MACOS_NOTARY_ISSUER_ID — the UUID Issuer ID
#
# Exits 0 (silently) when secrets aren't set. Fails loudly on Apple's
# rejection so the release workflow surfaces the problem.

set -euo pipefail

DIST="${1:-dist}"

if [ -z "${MACOS_NOTARY_KEY:-}" ]; then
echo "notarize-macos: MACOS_NOTARY_KEY unset — skipping" >&2
exit 0
fi

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

# Build an rcodesign-compatible API key JSON from the three secrets.
p8="$tmp/AuthKey.p8"
printf '%s' "$MACOS_NOTARY_KEY" | base64 -d > "$p8"

api_key_json="$tmp/api-key.json"
rcodesign encode-app-store-connect-api-key \
-o "$api_key_json" \
"$MACOS_NOTARY_ISSUER_ID" \
"$MACOS_NOTARY_KEY_ID" \
"$p8"

shopt -s nullglob
archives=( "$DIST"/refuse_darwin_*.tar.gz )
if [ ${#archives[@]} -eq 0 ]; then
echo "notarize-macos: no darwin archives in $DIST — nothing to do" >&2
exit 0
fi

for archive in "${archives[@]}"; do
name=$(basename "$archive" .tar.gz)
work="$tmp/$name"
mkdir -p "$work"

echo "notarize-macos: preparing $name"
tar -xzf "$archive" -C "$work"

zip="$tmp/$name.zip"
( cd "$work" && zip -q "$zip" refuse )

echo "notarize-macos: submitting $name.zip to Apple notary"
rcodesign notary-submit \
--api-key-file "$api_key_json" \
--wait \
"$zip"
done

echo "notarize-macos: all darwin archives notarized"
50 changes: 50 additions & 0 deletions scripts/sign-macos.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# sign-macos.sh — code-sign a single darwin Go binary with rcodesign.
#
# Called by goreleaser's `builds.hooks.post` once per built binary. We
# can't use goreleaser's native `notarize.macos` block because its Go
# x509 verifier chokes on Apple's critical extension OIDs (e.g.
# 1.2.840.113635.100.6.1.13 — Developer ID Application). rcodesign
# (Rust) handles them correctly.
#
# Usage: sign-macos.sh BINARY_PATH GOOS
#
# Requires (set as workflow env from secrets):
# MACOS_SIGN_P12 — base64 of the Developer ID .p12
# MACOS_SIGN_PASSWORD — password the .p12 was exported with
#
# Exits 0 on success, 0 (silently) when GOOS != darwin or when the
# signing secret isn't set (forks / local snapshot builds).

set -euo pipefail

BINARY="${1:?missing BINARY arg}"
OS="${2:?missing GOOS arg}"

if [ "$OS" != "darwin" ]; then
exit 0
fi
if [ -z "${MACOS_SIGN_P12:-}" ]; then
echo "sign-macos: MACOS_SIGN_P12 unset — leaving $BINARY unsigned" >&2
exit 0
fi

tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT

p12="$tmp/cert.p12"
printf '%s' "$MACOS_SIGN_P12" | base64 -d > "$p12"

echo "sign-macos: signing $BINARY"
rcodesign sign \
--p12-file "$p12" \
--p12-password "$MACOS_SIGN_PASSWORD" \
--code-signature-flags runtime \
"$BINARY"

# Best-effort sanity check.
if rcodesign verify "$BINARY" >/dev/null 2>&1; then
echo "sign-macos: $BINARY verified"
else
echo "sign-macos: WARNING — $BINARY verify failed (continuing)" >&2
fi