云端 Mac 团队签名证书管理实战:fastlane match 与远程 Keychain 免密解锁

Security ·约 7 分钟阅读

云端 Mac 团队签名证书管理实战:fastlane match 与远程 Keychain 免密解锁

凌晨两点,团队里第三个人在同一台云端 Mac 上跑了一次 fastlane build,结果 CI 直接报错:证书失效。三个人各自在本地跑过 match,谁也没注意到自己那一次操作,已经把别人刚导入的签名证书从系统默认 Keychain 里挤了出去。这种事故在多人共享一台云端 Mac 出包时几乎是必经的坑——本文记录我们如何用 fastlane match 把证书统一管起来,并解决无头远程会话里 Keychain 反复要密码的麻烦。

问题:共享一台云端 Mac,证书为什么总出岔子

本地开发时,一台 Mac 通常只服务一个人,证书装进默认 Keychain 谁都不会打架。但一台按天租来的云端 Mac 常常是团队共用:今天你连上去打包,明天同事远程登录改个配置又跑一次。如果每个人都各自执行 fastlane match development 或手动导入 p12,系统默认 Keychain 里的证书条目就会被反复覆盖、删除又重建,最终出现「证书存在但签名时找不到私钥」这种典型报错。

根本原因是两件事被混在一起了:证书的存储与分发,和证书在本机 Keychain 里的临时授权。前者应该集中管理,后者才需要按会话隔离。

fastlane match 的核心思路

match 把开发证书、发行证书和对应的描述文件加密后统一存进一个独立的 Git 仓库,团队所有人(以及云端 Mac 上的自动化用户)都从这个仓库拉取同一份证书,而不是各自去 Apple Developer 后台重新生成。这样能保证同一个 Bundle ID 在任何机器上签名结果一致,也避免了「证书数量超过 Apple 限额」的老问题。

match 的价值不是「自动生成证书」,而是「不再需要每个人自己生成证书」——这一句话决定了它该怎么被使用:只应该有一个人(或一次 CI 初始化任务)执行写入操作,其余所有场景都用只读模式拉取。

在云端 Mac 上初始化证书仓库

在你的云端 Mac 项目目录下,先确认 Matchfile 指向私有仓库而不是公开地址:

git_url("git@github.com:your-org/certs-private.git")
storage_mode("git")
type("appstore")

首次初始化只在一台受信任的机器上执行一次:

fastlane match appstore --readonly false

之后所有云端 Mac 上的拉取操作都加 --readonly true,确保它们只读取已有证书、不会尝试重新生成:

fastlane match appstore --readonly true

给部署用户单独发一个只读的 Git 部署密钥(deploy key),而不是把团队成员的个人 SSH key 都塞进云端 Mac,权限边界更清楚,人员变动时也不用逐个撤销。

无头远程会话里的 Keychain 免密解锁

云端 Mac 大多是通过 SSH 或 VNC 远程登录,不是本人物理坐在屏幕前敲密码,系统默认的登录 Keychain 在会话之间经常处于锁定状态,codesign 一执行就弹出授权对话框——这在无人值守的 CI 触发场景里会直接卡死。

做法是新建一个专用的签名 Keychain,和登录 Keychain 分开:

security create-keychain -p "$KEYCHAIN_PWD" signing.keychain
security set-keychain-settings -lut 21600 signing.keychain
security unlock-keychain -p "$KEYCHAIN_PWD" signing.keychain
security import cert.p12 -k signing.keychain -P "$P12_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PWD" signing.keychain
list=$(security list-keychains -d user | tr -d '"')
security list-keychains -d user -s signing.keychain $list

其中 set-key-partition-list 这一步很容易漏掉,漏了的话 codesign 依然会弹窗要求授权,即便 Keychain 已经解锁。这台专用 Keychain 建议只保留签名需要的证书,不要和日常密码混放,退租或转交项目时删掉它比清理默认 Keychain 干净得多。

CI 触发后的证书拉取与清理

自动化流程建议按下面的顺序执行,每一步的失败都应该让流水线直接停下,而不是继续往后跑:

步骤 命令/动作 失败时的处理
解锁专用 Keychain security unlock-keychain 立即中止,报工单而非静默重试
只读拉取证书 fastlane match … --readonly true 中止,提示先在主控机执行写入
执行构建 xcodebuild archive 保留构建日志供排查
清理临时 Keychain 引用 security delete-keychain signing.keychain(按需) 记录清理是否成功

如果云端 Mac 是按天租用、用完就会转手给其他任务或到期回收,最后一步的清理尤其重要:专用签名 Keychain、临时下载的 p12、~/Library/MobileDevice/Provisioning Profiles 里的描述文件都建议在任务结束时一并清空,不要指望下一个租期自动继承干净环境。具体机型与节点是否满足你团队并发出包的需求,建议在控制台确认当前可选配置后再排期,不同芯片型号的编译速度差异会直接影响多人排队等待时长。

团队协作与权限边界

把职责拆清楚能省掉后续很多故障排查时间:

上线前检查清单

把证书管理和 Keychain 解锁这两件事分开处理之后,团队在同一台云端 Mac 上交替打包基本不会再互相踩坑,新人加入项目时也只需要拉一次仓库、跑一遍只读命令,不用重新申请证书。

常见问题

match 的证书仓库能直接用公开的 Git 仓库吗?

不建议。match 会把 p12 证书和描述文件加密后存进仓库,虽然加了密码保护,但公开仓库仍会暴露仓库结构与元数据,应使用私有仓库,并给云端 Mac 上的部署用户单独发只读 token。

云端 Mac 重启后 Keychain 密码还需要每次手动输入吗?

不需要。用 security create-keychain 配合固定密码创建一个专用签名 Keychain,再用 security set-keychain-settings 关闭自动锁定,配合 -A 授权 codesign 免确认访问,重启后重新登录会话执行一次解锁脚本即可。

多人同时用同一台云端 Mac 打包,证书会不会互相覆盖?

会,如果大家各自跑 match,本地默认 Keychain 里的证书会被反复导入导出导致冲突。做法是给每个人分配独立的构建目录和专用 Keychain 文件,match 的 readonly 模式只读取仓库证书而不重新生成,避免互相覆盖。

HireVPS

今天就试试独享云 Mac mini

按天起租,2 分钟拿到 SSH/VNC 凭据,配置随时可升级。

立即租用 Mac mini