diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 72bccc3..4c93c35 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -32,3 +32,10 @@ jobs: # Token with contents + pull-requests write on sumup/homebrew-cli. # GITHUB_TOKEN cannot push cask updates to a different repository. HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} + # Apple Developer ID + notary credentials. + # Required for Gatekeeper-clean Homebrew installs on macOS. + MACOS_SIGN_P12: ${{ secrets.MACOS_SIGN_P12 }} + MACOS_SIGN_PASSWORD: ${{ secrets.MACOS_SIGN_PASSWORD }} + 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 }} diff --git a/.goreleaser.yml b/.goreleaser.yml index 79ff8cd..62d7c95 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -57,16 +57,6 @@ builds: ldflags: - -s -w -X github.com/sumup/sumup-cli/internal/buildinfo.Version={{ .Version }} -X github.com/sumup/sumup-cli/internal/buildinfo.Commit={{ .Commit }} -X github.com/sumup/sumup-cli/internal/buildinfo.Date={{ .Date }} - # Enable when signing credentials are configured in CI. - # hooks: - # post: - # - cmd: >- - # {{ if eq .Os "darwin" }}./scripts/sign-macos.sh '{{ .Path }}'{{ else }}echo{{ end }} - # output: true - # - cmd: >- - # {{ if eq .Os "windows" }}pwsh ./scripts/sign-windows.ps1 '{{ .Path }}'{{ else }}echo{{ end }} - # output: true - archives: - id: linux-archive ids: [linux] @@ -105,6 +95,20 @@ archives: checksum: name_template: checksums.txt +# Sign and notarize macOS binaries when Apple credentials are present. +# Homebrew installs require notarized binaries (no quarantine bypass in the cask). +notarize: + macos: + - enabled: '{{ isEnvSet "MACOS_SIGN_P12" }}' + ids: [macos] + sign: + certificate: "{{ .Env.MACOS_SIGN_P12 }}" + password: "{{ .Env.MACOS_SIGN_PASSWORD }}" + notarize: + issuer_id: "{{ .Env.MACOS_NOTARY_ISSUER_ID }}" + key_id: "{{ .Env.MACOS_NOTARY_KEY_ID }}" + key: "{{ .Env.MACOS_NOTARY_KEY }}" + # Publishes Casks/sumup.rb to the public tap sumup/homebrew-cli. # Requires HOMEBREW_TAP_GITHUB_TOKEN with write access to that repository. homebrew_casks: @@ -122,14 +126,6 @@ homebrew_casks: homepage: https://developer.sumup.com description: Command line tool for interacting with SumUp APIs. license: Apache-2.0 - # Binaries are not yet notarized; clear quarantine so Gatekeeper does not - # block the unsigned CLI. Remove this hook once macOS signing is enabled. - hooks: - post: - install: | - if OS.mac? - system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/sumup"] - end skip_upload: auto changelog: diff --git a/scripts/sign-macos.sh b/scripts/sign-macos.sh index bb21267..e290500 100755 --- a/scripts/sign-macos.sh +++ b/scripts/sign-macos.sh @@ -1,4 +1,6 @@ #!/usr/bin/env bash +# Local helper for macOS signing. Release builds use GoReleaser's notarize +# section with MACOS_SIGN_P12 / MACOS_SIGN_PASSWORD / MACOS_NOTARY_* secrets. set -euo pipefail binary_path="${1:-}" @@ -7,18 +9,13 @@ if [[ -z "$binary_path" ]]; then exit 1 fi -# Placeholder script for future macOS signing and notarization. -# Expected env vars once enabled: -# - APPLE_CERT_BASE64 -# - APPLE_CERT_PASSWORD -# - APPLE_TEAM_ID -# - APPLE_ID -# - APPLE_APP_SPECIFIC_PASSWORD -# -# Example (to enable later): -# security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain -# echo "$APPLE_CERT_BASE64" | base64 --decode > cert.p12 -# security import cert.p12 -k build.keychain -P "$APPLE_CERT_PASSWORD" -T /usr/bin/codesign -# codesign --force --timestamp --options runtime --sign "Developer ID Application: ..." "$binary_path" +if [[ -z "${MACOS_SIGN_IDENTITY:-}" ]]; then + echo "MACOS_SIGN_IDENTITY is not set; skipping codesign for $binary_path" >&2 + exit 0 +fi + +codesign --force --timestamp --options runtime \ + --sign "$MACOS_SIGN_IDENTITY" \ + "$binary_path" -echo "macOS signing is currently disabled. Skipping: $binary_path" +echo "Signed $binary_path with $MACOS_SIGN_IDENTITY" diff --git a/scripts/sign-windows.ps1 b/scripts/sign-windows.ps1 index 9afa5a4..e03a17f 100644 --- a/scripts/sign-windows.ps1 +++ b/scripts/sign-windows.ps1 @@ -5,13 +5,56 @@ param( $ErrorActionPreference = "Stop" -# Placeholder script for future Windows Authenticode signing. -# Expected env vars once enabled: -# - WINDOWS_CERT_BASE64 -# - WINDOWS_CERT_PASSWORD -# -# Example (to enable later): -# [IO.File]::WriteAllBytes("cert.pfx", [Convert]::FromBase64String($env:WINDOWS_CERT_BASE64)) -# signtool sign /f cert.pfx /p $env:WINDOWS_CERT_PASSWORD /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 $BinaryPath +# Cross-compiled Windows binaries are signed only when this hook runs on Windows +# with Authenticode credentials available. +if (-not $IsWindows) { + Write-Output "Not running on Windows; skipping Authenticode signing for $BinaryPath" + exit 0 +} -Write-Output "Windows signing is currently disabled. Skipping: $BinaryPath" +if (-not $env:WINDOWS_CERT_BASE64 -or -not $env:WINDOWS_CERT_PASSWORD) { + Write-Output "WINDOWS_CERT_BASE64 / WINDOWS_CERT_PASSWORD not set; skipping Authenticode signing for $BinaryPath" + exit 0 +} + +$certPath = Join-Path ([System.IO.Path]::GetTempPath()) ("sumup-windows-" + [guid]::NewGuid().ToString() + ".pfx") +try { + [IO.File]::WriteAllBytes($certPath, [Convert]::FromBase64String($env:WINDOWS_CERT_BASE64)) + + $signtool = $null + $candidates = @( + "${env:ProgramFiles(x86)}\Windows Kits\10\bin\*\x64\signtool.exe", + "${env:ProgramFiles}\Windows Kits\10\bin\*\x64\signtool.exe" + ) + foreach ($pattern in $candidates) { + $match = Get-Item $pattern -ErrorAction SilentlyContinue | Sort-Object FullName -Descending | Select-Object -First 1 + if ($match) { + $signtool = $match.FullName + break + } + } + if (-not $signtool) { + $signtool = Get-Command signtool.exe -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source + } + if (-not $signtool) { + throw "signtool.exe not found; install Windows SDK signing tools" + } + + & $signtool sign ` + /f $certPath ` + /p $env:WINDOWS_CERT_PASSWORD ` + /fd SHA256 ` + /tr http://timestamp.digicert.com ` + /td SHA256 ` + $BinaryPath + if ($LASTEXITCODE -ne 0) { + throw "signtool failed with exit code $LASTEXITCODE" + } + + Write-Output "Signed $BinaryPath" +} +finally { + if (Test-Path $certPath) { + Remove-Item -Force $certPath + } +}