Safari 云签名在 GitHub Actions 中的两个陷阱

本文最后更新于:2026年8月5日 下午

背景

使用 GitHub Actions 全自动发布 Safari 扩展 里,我写过一套在 macOS runner 上用 Xcode 云签名(-allowProvisioningUpdates)自动构建、签名、上传 Safari 扩展的流程。流程本身是能跑起来的,但在跑通之前,我踩过两个坑,而且巧的是,这两个坑都有同一个特点:报错信息含糊不清,搜索引擎上几乎搜不到答案。这篇文章把它们记录下来,包括现象、原因和修复方法。

两个坑追根溯源是同一件事:无状态的 CI runner,打破了苹果签名工具原本假设的前提

陷阱一:「maximum number of certificates」

场景是这样的:GitHub 提供的 macos-* runner,开着 -allowProvisioningUpdates 让云签名自动处理证书。一开始一切正常,版本一个接一个发出去。然后某一天,你什么都没改,构建却突然报错,说证书数量达到了上限。

真正发生的事情是这样的:GitHub 的每个 runner 启动时都是一个全新的、空的 keychain。云签名找不到可用的签名身份时,不会直接失败,而是很「贴心」地帮你的账号新建一张 Apple Distribution 证书,把私钥放进这次 runner 的 keychain 里。job 结束后 runner 被销毁,私钥跟着一起消失。证书本身还留在你的 Apple Developer 账号里,永久躺在那儿,再也没有对应的私钥能用它签名。

也就是说,每次发布都在悄悄烧掉一张证书。苹果对每个账号的 Distribution 证书数量设了上限,一旦顶到这个上限,云签名就没法再新建证书,构建从此开始失败。最阴险的地方在于:撞上限之前,每一次构建都是成功的,你完全看不出哪里不对,直到它毫无征兆地、永久性地坏掉。

Snipaste\_2026-07-28\_08-34-22.png

这是攒了一段时间之后的样子——十几张证书,全部叫「Created via API」,全部挤在大约一周之内。它们无一例外都是孤儿证书:创建它们的 runner 早就没了,私钥自然也不存在了。

解决办法就是不要再让 CI 自己新建证书:

  1. 自己手动创建一张 Apple Distribution 证书(Xcode → Settings → Accounts → Manage Certificates,或者去 开发者后台
  2. 导出为带密码的 .p12
  3. 把证书和密码都存进仓库的 secrets(.p12 需要先 base64)
  4. 在 CI job 一开始、xcodebuild 跑之前,把它导入 keychain
1
2
3
4
5
6
7
8
9
10
11
12
- name: Import signing certificate
run: |
echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > certificate.p12
security create-keychain -p actions build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p actions build.keychain
security import certificate.p12 -k build.keychain \
-P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k actions build.keychain
env:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}

keychain 里已经有一个可用的签名身份之后,云签名每次都会复用它,而不是每次都新建一个。

注:修好这个问题之后,记得去开发者后台的证书列表,把 CI 之前留下的那一堆孤儿证书清理掉——找不到对应私钥的那些就是。

陷阱二:「Cloud signing permission error」

传给 xcodebuild 的 App Store Connect API Key 是有角色权限的,创建的时候就定死了。坑就在这里:一个 DeveloperApp Manager 角色的 key,身份认证是能通过的,然后构建会在签名进行到一半时失败,只甩给你一句语焉不详的「Cloud signing permission error」。

正是这种失败方式让人摸不着头脑:key 本身没有被拒绝,上传之类的其他 App Store Connect API 操作用这个权限也都正常,唯独云签名这一步会死,报错里连「权限」两个字都没提。

1785936111555.jpg

按我的实测,云签名要求 key 必须是 Admin 角色。角色在创建时就固定了、后续改不了,唯一的办法是重新生成一个:App Store Connect → Users and Access → Integrations → App Store Connect API → 生成一个 Admin 角色的 key,下载 .p8(只有这一次下载机会)。

注:Admin 角色的 .p8 要当成整套流程里真正的机密来对待,它是这里唯一的真机密。issuer id、key id、team id 这些都不是机密,.p8.p12 才是。

总结

两个坑追根溯源都是同一件事:苹果的签名工具链假设你在用一台长期存在的开发机,而无状态的 runner 打破了这个假设,失败得又晚又含糊,而不是又早又清楚。

写上一篇文章之后,我把这整套流程——生成 Xcode 工程、用上面这两个修复方式签名、上传、提交审核——都收进了 extport,也就是我现在给所有扩展发版用的工具。如果你不想自己维护这堆 YAML,它就是这篇和上一篇里讲的这一整套东西的打包版本。


Safari 云签名在 GitHub Actions 中的两个陷阱
https://blog.rxliuli.com/p/dcf4d3d7168f4e31b7719030005bf10e/
作者
rxliuli
发布于
2026年8月5日
许可协议