Repository navigation
feat: Add macOS code signing and notarization #1528
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1aa283d
4d6d579
726b5ea
0049640
ba7a8cb
b907418
7abae5f
cbc088a
ebc0629
14497a7
af04acf
949038f
2c1bd1a
98a04e5
62b8594
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| <plist version="1.0"> | ||
| <dict> | ||
| <key>com.apple.security.cs.disable-library-validation</key> | ||
| <true/> | ||
| <key>com.apple.security.network.client</key> | ||
| <true/> | ||
| <key>com.apple.security.network.server</key> | ||
| <true/> | ||
| <key>com.apple.security.files.user-selected.read-only</key> | ||
| <true/> | ||
| <key>com.apple.security.files.user-selected.read-write</key> | ||
| <true/> | ||
| <key>com.apple.security.files.downloads.read-write</key> | ||
| <true/> | ||
| </dict> | ||
| </plist> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| name: macOS signing | ||
|
|
||
| on: | ||
| pull_request: | ||
| branches: [main] | ||
| paths: | ||
| - scripts/sign-macos.sh | ||
| - .github/macos/entitlements.plist | ||
| - .github/workflows/macos-signing.yml | ||
| push: | ||
| branches: [main] | ||
| paths: | ||
| - scripts/sign-macos.sh | ||
| - .github/macos/entitlements.plist | ||
| - .github/workflows/macos-signing.yml | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| sign-macos: | ||
| runs-on: macos-latest | ||
| timeout-minutes: 45 | ||
| steps: | ||
| - uses: actions/checkout@v7 | ||
| - uses: ./.github/actions/install-frontend-dependencies | ||
| - name: Set up cargo cache | ||
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 | ||
| env: | ||
| NODE_OPTIONS: --no-deprecation | ||
| - name: Test the signing helper on pull requests | ||
| if: github.event_name == 'pull_request' | ||
| env: | ||
| APPLE_SIGNING_IDENTITY: "-" # Ad hoc signing requires no Apple credentials. | ||
| run: scripts/sign-macos.sh | ||
| - name: Run the signing helper with Apple credentials | ||
| if: github.event_name == 'push' | ||
| env: | ||
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | ||
| APPLE_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_NOTARIZATION_APPLE_ID }} | ||
| APPLE_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} | ||
| APPLE_NOTARIZATION_TEAM_ID: ${{ secrets.APPLE_NOTARIZATION_TEAM_ID }} | ||
| P12_BASE64: ${{ secrets.APPLE_SIGNING_CERTIFICATE_P12_BASE64 }} | ||
| P12_PASSWORD: ${{ secrets.APPLE_SIGNING_CERTIFICATE_PASSWORD }} | ||
| run: | | ||
| set -euo pipefail | ||
| : "${APPLE_SIGNING_IDENTITY:?Missing APPLE_SIGNING_IDENTITY secret}" | ||
| : "${APPLE_NOTARIZATION_APPLE_ID:?Missing APPLE_NOTARIZATION_APPLE_ID secret}" | ||
| : "${APPLE_NOTARIZATION_PASSWORD:?Missing APPLE_NOTARIZATION_PASSWORD secret}" | ||
| : "${APPLE_NOTARIZATION_TEAM_ID:?Missing APPLE_NOTARIZATION_TEAM_ID secret}" | ||
| : "${P12_BASE64:?Missing APPLE_SIGNING_CERTIFICATE_P12_BASE64 secret}" | ||
| : "${P12_PASSWORD:?Missing APPLE_SIGNING_CERTIFICATE_PASSWORD secret}" | ||
| CERTIFICATE_PATH="$RUNNER_TEMP/signing.p12" | ||
| KEYCHAIN_PATH="$RUNNER_TEMP/signing.keychain-db" | ||
| umask 077 | ||
| trap 'security delete-keychain "$KEYCHAIN_PATH"; rm -f "$CERTIFICATE_PATH" "$CERTIFICATE_PATH.pem"' EXIT | ||
| # Use PKCS12 algorithms supported by macOS Keychain. | ||
| printf '%s' "$P12_BASE64" | base64 -d > "$CERTIFICATE_PATH" | ||
| openssl pkcs12 -in "$CERTIFICATE_PATH" -passin env:P12_PASSWORD -nodes -out "$CERTIFICATE_PATH.pem" | ||
| openssl pkcs12 -export -in "$CERTIFICATE_PATH.pem" -passout env:P12_PASSWORD \ | ||
| -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -macalg sha1 -out "$CERTIFICATE_PATH" | ||
| rm -f "$CERTIFICATE_PATH.pem" | ||
| security create-keychain -p "" "$KEYCHAIN_PATH" | ||
| security set-keychain-settings -lut 3600 "$KEYCHAIN_PATH" | ||
| security unlock-keychain -p "" "$KEYCHAIN_PATH" | ||
| security import "$CERTIFICATE_PATH" -k "$KEYCHAIN_PATH" -P "$P12_PASSWORD" -T /usr/bin/codesign | ||
| security set-key-partition-list -S apple-tool:,apple: -s -k "" "$KEYCHAIN_PATH" > /dev/null | ||
| security list-keychains -d user -s "$KEYCHAIN_PATH" | ||
| scripts/sign-macos.sh | ||
| - name: Run the signed binary | ||
| run: target/aarch64-apple-darwin/superoptimized/sqlpage --version | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| #!/bin/bash | ||
| # Script for local macOS signing and notarization testing | ||
| # | ||
| # Prerequisites: | ||
| # 1. Build the frontend assets first: npm ci && npm run build | ||
| # 2. Install the Rust target: rustup target add aarch64-apple-darwin | ||
| # 3. Import your Developer ID certificate into the login keychain: | ||
| # security import sqlpage.p12 -k ~/Library/Keychains/login.keychain-db -P "<p12-password>" -T /usr/bin/codesign | ||
| # | ||
| # Required environment variables for notarization: | ||
| # APPLE_SIGNING_IDENTITY - e.g. "Developer ID Application: Your Name (TEAMID)", or "-" for ad hoc signing | ||
| # APPLE_NOTARIZATION_APPLE_ID - Your Apple ID email | ||
| # APPLE_NOTARIZATION_PASSWORD - App-specific password | ||
| # APPLE_NOTARIZATION_TEAM_ID - Your 10-character Team ID | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| # Check if we're on macOS | ||
| if [[ "$(uname)" != "Darwin" ]]; then | ||
| echo "This script must be run on macOS" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Check if required tools are available (use xcrun --find as tools may not be on PATH) | ||
| if ! xcrun --find codesign &> /dev/null; then | ||
| echo "codesign not found. Please install Xcode command line tools:" | ||
| echo " xcode-select --install" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Check that the signing identity is set | ||
| if [[ -z "${APPLE_SIGNING_IDENTITY:-}" ]]; then | ||
| echo "APPLE_SIGNING_IDENTITY is not set." | ||
| echo "Example: export APPLE_SIGNING_IDENTITY=\"Developer ID Application: Your Name (TEAMID)\"" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Check that frontend assets exist (build.rs requires them) | ||
| if [[ ! -f frontend/dist/tabler-sprite.svg ]]; then | ||
| echo "Frontend assets not found. Building them now..." | ||
| npm ci | ||
| npm run build | ||
| fi | ||
|
|
||
| # Install the ARM target if on Intel Mac | ||
| if [[ "$(uname -m)" == "x86_64" ]]; then | ||
| echo "Intel Mac detected, ensuring aarch64-apple-darwin target is installed..." | ||
| rustup target add aarch64-apple-darwin | ||
| fi | ||
|
|
||
| # Build the binary | ||
| echo "Building SQLPage for aarch64-apple-darwin..." | ||
| cargo build --profile superoptimized --locked --target aarch64-apple-darwin --features "odbc-static" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On a fresh checkout, this helper reaches AGENTS.md reference: AGENTS.md:L147-L147 Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On an Intel Mac with a fresh Rust installation, this hard-coded Useful? React with 👍 / 👎. |
||
|
|
||
| # Check if the binary exists | ||
| BINARY_PATH="target/aarch64-apple-darwin/superoptimized/sqlpage" | ||
| if [[ ! -f "$BINARY_PATH" ]]; then | ||
| echo "Binary not found at $BINARY_PATH" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Sign the binary | ||
| echo "Signing the binary..." | ||
| SIGNING_TIMESTAMP_OPTION=--timestamp | ||
| if [[ "$APPLE_SIGNING_IDENTITY" == "-" ]]; then | ||
| SIGNING_TIMESTAMP_OPTION=--timestamp=none | ||
| fi | ||
| codesign --force --options runtime --entitlements .github/macos/entitlements.plist --sign "$APPLE_SIGNING_IDENTITY" "$SIGNING_TIMESTAMP_OPTION" "$BINARY_PATH" | ||
|
|
||
| # Verify the signature | ||
| echo "Verifying the signature..." | ||
| codesign --verify --deep --strict --verbose=2 "$BINARY_PATH" | ||
|
|
||
| # Create a zip archive for notarization | ||
| echo "Creating zip archive for notarization..." | ||
| ditto -c -k --keepParent "$BINARY_PATH" sqlpage-macos.zip | ||
|
|
||
| # Notarize the binary (if credentials are provided) | ||
| # Note: stapler does not support bare Mach-O executables or zip archives. | ||
| # For a standalone binary, notarization alone is sufficient — Gatekeeper | ||
| # checks Apple's notarization servers online when the binary is first run. | ||
| if [[ -n "${APPLE_NOTARIZATION_APPLE_ID:-}" && -n "${APPLE_NOTARIZATION_PASSWORD:-}" && -n "${APPLE_NOTARIZATION_TEAM_ID:-}" ]]; then | ||
| if ! xcrun --find notarytool &> /dev/null; then | ||
| echo "notarytool not found. Please install Xcode command line tools:" | ||
| echo " xcode-select --install" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "Submitting for notarization..." | ||
| xcrun notarytool submit sqlpage-macos.zip \ | ||
| --apple-id "$APPLE_NOTARIZATION_APPLE_ID" \ | ||
| --password "$APPLE_NOTARIZATION_PASSWORD" \ | ||
| --team-id "$APPLE_NOTARIZATION_TEAM_ID" \ | ||
| --wait --output-format plist > notarization-result.plist | ||
| if [[ "$(plutil -extract status raw -o - notarization-result.plist)" != "Accepted" ]]; then | ||
| echo "Notarization was not accepted:" >&2 | ||
| cat notarization-result.plist >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Verify the signature and notarization ticket for the standalone binary. | ||
| echo "Final verification..." | ||
| codesign --verify --deep --strict --verbose=2 --check-notarization -R=notarized "$BINARY_PATH" | ||
| else | ||
| echo "Skipping notarization. Set APPLE_NOTARIZATION_APPLE_ID, APPLE_NOTARIZATION_PASSWORD, and APPLE_NOTARIZATION_TEAM_ID to enable notarization." | ||
| echo "Note: The binary is signed but not notarized. Gatekeeper will still show a warning." | ||
| fi | ||
|
|
||
| echo "macOS signing complete!" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For any same-repository pull request touching one of the filtered files, this job checks out the PR merge commit and executes its
scripts/sign-macos.shwhile the production P12, its password, and notarization credentials are in the environment. An unreviewed change to that script can therefore exfiltrate the Developer ID private key or use it to sign arbitrary code; GitHub confirms thatpull_requestworkflows from branches in the same repository receive repository secrets and run code from the merge branch (GitHub security guidance). Limit this credentialed job to trusted post-merge pushes, or protect the secrets with an environment requiring approval.Useful? React with 👍 / 👎.