Skip to content

v0.1.5: notarize via App Store Connect API key + rotate-key.sh#18

Merged
mariomeyer merged 3 commits into
mainfrom
phase-4-followups-4
May 23, 2026
Merged

v0.1.5: notarize via App Store Connect API key + rotate-key.sh#18
mariomeyer merged 3 commits into
mainfrom
phase-4-followups-4

Conversation

@mariomeyer
Copy link
Copy Markdown
Member

Summary

Migrates notarization off Apple app-specific passwords (which expire annually and can only be regenerated via the appleid.apple.com web UI) onto App Store Connect API keys. After v0.1.5 ships, every release-pipeline credential is rotatable via a single CLI command with no annual ritual.

Three commits:

  1. fix(notarize): switch to App Store Connect API key auth — `notarize.sh` now uses `xcrun notarytool --key/--key-id/--issuer`. release-artifacts.yml stops passing APPLE_ID/PASSWORD/TEAM_ID; the Tauri build no longer needs APPLE_TEAM_ID either (derives team from APPLE_SIGNING_IDENTITY).
  2. `fix(release): swap Apple ID secrets for App Store Connect API key secrets` — bootstrap-secrets.sh drops the three Apple ID prompts and adds three App Store Connect prompts (with format validation: 10-char alphanumeric Key ID, UUID Issuer ID, .p8 path with PEM sanity check + secure-delete prompt).
  3. `chore(release): rotate-key.sh — guided credential rotation` — new `scripts/release/rotate-key.sh` with three subcommands (`app-store-connect-key`, `apple-cert`, `tauri-key`). Walks manual prep with checkpoints, delegates upload to bootstrap-secrets.sh, verifies updatedAt changed, offers smoke release.

Spec §4 + runbook `docs/runbooks/release-key-rotation.md` rewritten to reflect the new model.

Pre-merge prep

✅ App Store Connect API key created, all three secrets present:

  • `APP_STORE_CONNECT_KEY_ID`
  • `APP_STORE_CONNECT_ISSUER_ID`
  • `APP_STORE_CONNECT_PRIVATE_KEY`

Test plan

  • shellcheck clean on notarize.sh, bootstrap-secrets.sh, rotate-key.sh
  • `rotate-key.sh help` runs and prints usage
  • release-artifacts.yml parses as valid YAML
  • All three APP_STORE_CONNECT_* secrets uploaded
  • CI green on this branch
  • On merge: v0.1.5 publishes via API key auth, notarization succeeds
  • After v0.1.5 ships: `gh secret delete APPLE_ID APPLE_PASSWORD APPLE_TEAM_ID`

Why this matters

Before: APPLE_PASSWORD rotation was an annual manual ritual — visit Apple website, generate password, paste into bootstrap-secrets.sh. Apple shows the password once. Easy to lose; easy to forget until a release fails at notarization.

After: zero recurring credential maintenance. The App Store Connect API key has no automatic expiry. When rotation is needed (hygiene, suspected compromise), one command:

```bash
scripts/release/rotate-key.sh app-store-connect-key
```

Walks the App Store Connect UI step-by-step (the only manual part: creating new keys IS web-UI-only — Apple exposes no API for key creation itself), then handles upload + verify + revoke-old-key.

Merge via "Create a merge commit" per standard.

Replaces APPLE_ID/APPLE_PASSWORD/APPLE_TEAM_ID in xcrun notarytool
invocations with --key/--key-id/--issuer. Eliminates dependency on
Apple app-specific passwords, which expire annually and can only be
regenerated via the appleid.apple.com web UI — Apple exposes no API
for them. App Store Connect API keys are created once via the App
Store Connect web UI; every subsequent rotation is scripted.

notarize.sh now materializes the .p8 from the base64-encoded
APP_STORE_CONNECT_PRIVATE_KEY env var to a chmod-600 temp file (with
EXIT trap cleanup) since notarytool only accepts a path. The retry
loop, JSON parsing, and stapling logic are unchanged.

release-artifacts.yml stops referencing APPLE_ID/PASSWORD/TEAM_ID
entirely — they can be deleted from the GitHub secret store after this
ships. The Tauri build no longer needs APPLE_TEAM_ID either; it derives
the team from APPLE_SIGNING_IDENTITY's (TEAMID) suffix.

Before merging:
  1. Create an App Store Connect API key at
     https://appstoreconnect.apple.com/access/api (role: Developer).
  2. Download the .p8.
  3. Run: scripts/release/bootstrap-secrets.sh \
       APP_STORE_CONNECT_KEY_ID \
       APP_STORE_CONNECT_ISSUER_ID \
       APP_STORE_CONNECT_PRIVATE_KEY
  4. After v0.1.5 ships successfully, delete the now-unused
     APPLE_ID / APPLE_PASSWORD / APPLE_TEAM_ID GitHub secrets.
…rets

Drops prompt_apple_id / prompt_apple_password / prompt_apple_team_id
and the SECRETS array entries for those names. Adds prompts for the
three replacement secrets:

  APP_STORE_CONNECT_KEY_ID       — 10-char alphanumeric, validated
  APP_STORE_CONNECT_ISSUER_ID    — UUID, validated
  APP_STORE_CONNECT_PRIVATE_KEY  — path to .p8, base64-encoded, with
                                   optional secure-delete prompt
                                   (matches APPLE_CERTIFICATE handling)

Same dispatch pattern as the existing prompts — \`bootstrap-secrets.sh
APP_STORE_CONNECT_KEY_ID\` rotates a single secret, no-args runs the
full setup. Validation on the Key ID / Issuer ID formats catches
common copy-paste errors at prompt time.
\`scripts/release/rotate-key.sh <subcommand>\` walks the human through
unavoidable manual steps (App Store Connect / Xcode / cargo tauri
signer) with explicit pause-and-confirm checkpoints, delegates GitHub
secret upload to bootstrap-secrets.sh, then verifies the secret's
updatedAt timestamp actually changed before signing off.

Three subcommands:

  app-store-connect-key   Rotates the three APP_STORE_CONNECT_* secrets
                          used for notarization auth. Apple gates key
                          creation behind the App Store Connect web UI
                          (no API); everything else is scripted,
                          including a final "revoke the old key" prompt
                          so leaked .p8s stop being usable.

  apple-cert              Rotates the three Apple cert secrets. Walks
                          through Xcode → Keychain Access → .p12 export,
                          then handles upload and verification. Cert
                          generation via App Store Connect API is
                          possible but the JWT + CSR + key-pair-stitching
                          machinery isn't worth writing for a 5-year
                          rotation cadence.

  tauri-key               Rotates the updater signing key. Requires a
                          literal "I understand auto-update will break"
                          confirmation. Reminds about committing the
                          tauri.conf.json pubkey change and refuses to
                          offer a smoke release (the pubkey change must
                          ship first).

Each subcommand prints a smoke-release prompt at the end (except
tauri-key, which intentionally doesn't — see above).

Runbook docs/runbooks/release-key-rotation.md rewritten to be a
why-and-when reference; the script is the how. Spec §4 updated to
reflect the new secret inventory and rotation model.
@mariomeyer mariomeyer merged commit 179590b into main May 23, 2026
2 checks passed
@mariomeyer
Copy link
Copy Markdown
Member Author

🎉 This PR is included in version 0.1.5 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant