发布流水线出现“普通 PR 也能碰到证书”、工作区残留无法证明已清理、主机重启后签名失败等症状时,说明构建节点和签名节点已经混在了同一个信任域。

最快的架构判断是:生产签名默认独立,普通编译与 PR 验证进入共享构建池;只有单一应用、低频发布且能完成账号、Keychain、任务路由和重启验收的小团队,才适合同一台 Mac 做逻辑隔离。

这篇文章适合三类决策者:企业 IT 负责人,需要确定签名节点的隔离、远程恢复与审计标准;研发效能负责人,需要正确分流构建、归档、签名和上传任务;技术总监或采购负责人,需要比较共享 Mac、专用远程 Mac 与混合节点池的风险和 TCO。

先划分任务与敏感资产

iOS CI 里的“编译成功”不等于“发布环境安全”。编译、单元测试、归档、代码签名和上传虽然可能连续出现在同一个流水线中,但它们接触的资产和失败后的影响范围并不相同。

可以先按下面的路径分流:

源码 / PR
   │
   ├─ 普通编译、单元测试、静态检查
   │       └─ 共享构建池:不接触生产签名私钥
   │
   ├─ 归档、导出 IPA
   │       └─ 受控归档池:限制仓库、分支和触发主体
   │
   └─ 生产签名、上传 App Store Connect
           └─ 专用可信签名节点:独立账号、Keychain、网络出口和审计记录

需要单独盘点的资产至少包括:

  • 源码、依赖缓存、构建产物和临时工作区;
  • Apple Distribution 证书及对应私钥;
  • Provisioning Profile,以及其中关联的 App ID、能力和证书;
  • macOS Keychain 中的签名身份、密码和访问控制;
  • App Store Connect API Key、JWT 生成所需的私钥;
  • CI 平台令牌、仓库写入权限、内部网络访问权限;
  • 上传账号、发布审批记录和撤权日志。

Apple 将证书、账户凭证和相关材料视为能够确认开发者身份的敏感资产;Apple Distribution 证书用于分发或上传,而开发证书与分发证书的用途和归属并不相同。Apple 证书概览

因此,不能用“所有凭证都放在 CI 密钥变量里”作为隔离证明。变量隐藏只能减少明文暴露,不能自动阻止任务读取文件系统、访问 Keychain、调用网络服务,或从同一台长期运行的自托管 Runner 中读取残留信息。

iOS CI 签名节点的风险边界

共享 Runner 的残留风险

多个仓库共用一台自托管 Mac 时,工作目录、DerivedData、依赖缓存、日志和临时导出目录都可能留下项目相关信息。即使流水线最后执行了删除命令,也需要证明删除范围覆盖了失败任务、被中断任务和用户自定义脚本。

GitHub 官方文档明确提醒,自托管 Runner 不具备托管 Runner 那种干净、临时的隔离环境;不受信任的工作流代码可能持久影响 Runner,并接触主机上的密钥、令牌和其他敏感信息。GitHub 自托管 Runner 安全说明

多项目环境下,真正需要审计的不是“Runner 是否在线”,而是:

  • 哪些仓库可以把任务调度到该节点;
  • 哪些分支或事件可以触发发布任务;
  • 任务完成后是否清理工作区和临时凭证;
  • 一个项目的脚本是否能访问另一个项目的目录;
  • 普通构建任务是否能调用生产签名身份;
  • 节点被重启或任务中断后,旧会话是否仍然有效。

Keychain 不是普通环境变量

macOS Keychain 用于保存密码、密钥和证书等小型敏感数据,Keychain Services 还允许应用控制哪些程序可以访问其中的项目。Apple Keychain Services 文档

这意味着签名节点至少要区分“凭证存在”和“任务可用”两个问题。证书私钥即使没有出现在脚本变量中,也可能通过当前用户会话、钥匙串搜索路径、签名工具授权或遗留的登录状态被调用。

macOS 的 Keychain 访问控制列表会记录允许执行特定操作的应用;当应用尝试使用私钥进行签名时,系统会根据访问控制规则判断是否允许。Apple Keychain 访问控制列表文档

所以,多项目共享 Mac 时,单纯建立多个目录、多个环境变量或多个 CI 标签都不够。必须同时验证用户账号、Keychain、签名工具、任务路由和网络出口是否真的分开。

撤销后的影响不可忽略

Apple 说明,撤销证书后,包含该证书的 Provisioning Profile 会失效;对于 iOS App Store 分发证书,已上传但尚未提交审核的构建可能被标记为无效,后续上传也需要使用有效证书重新签名。Apple 撤销证书说明

这会带来两个管理后果:

  1. 生产证书不能像普通构建缓存一样随意复制到多个节点;
  2. 一旦发现私钥泄露,撤销、重新生成、重新导入和重新验证必须有明确责任人。

签名节点的独立价值,不只是防止一次任务误用凭证,更是为了缩小证书撤销、人员离职、主机故障或错误发布时的影响范围。

按团队治理成熟度选择架构

单一应用小团队:受控逻辑隔离

单一应用、代码来源稳定、发布频率较低、研发人员权限边界清晰时,同一台 Mac 可以采用逻辑隔离,但准入条件必须同时满足:

  • 普通 CI 账号不能读取生产 Keychain;
  • 发布账号不能被 PR、外部脚本或任意分支直接调用;
  • 生产签名使用独立 Keychain,而不是默认登录钥匙串;
  • 归档任务与生产签名任务拥有不同的路由标签;
  • App Store Connect 上传凭证只在发布步骤短时加载;
  • 发布任务需要人工审批或受保护分支触发;
  • 主机重启后,必须重新验证登录状态、Keychain 锁定和签名权限;
  • 旧账号、旧令牌和旧 Keychain 在撤权后不能继续完成上传。

这里的关键不是“某次 codesign 命令成功”,而是一次完整的发布闭环:干净源码检出、Xcode 归档、签名、导出、上传、主机重启、重新登录和再次发布都能完成,并且每个步骤都有日志。

如果无法证明普通任务绝不会进入签名上下文,应直接回退到专用签名节点。

多仓库团队:分离可信发布域

当产品团队、平台团队和外包协作者共同使用 CI 时,共享 Mac 的风险会从“单项目凭证管理”扩大到“跨项目任务路由”。

推荐至少划分三类节点:

  • 普通构建池:执行 PR 验证、单元测试、静态检查和不带生产私钥的编译任务;
  • 受控归档池:只允许指定仓库、指定分支和指定维护者触发;
  • 生产签名池:只执行经过审批的归档、签名和上传任务,不承接普通 PR。

GitHub Actions 支持通过 Runner Group 限制组织和仓库的使用范围,但这类路由边界仍需要结合企业内部权限、工作流配置和节点本身的账号权限进行验证。GitHub 添加与管理自托管 Runner

多仓库环境下,应把“谁能触发”拆成几个独立问题:

  • 产品团队能否提交普通构建;
  • 平台团队能否修改 Runner 标签和路由规则;
  • 发布负责人能否批准生产签名;
  • 外包协作者能否触发归档;
  • CI 管理员能否读取或替换签名凭证;
  • IT 管理员能否登录 macOS 本地管理员账号。

任何一个角色同时拥有“修改工作流”和“调用生产签名”的权限,都应被视为高风险组合。

受监管团队:把签名节点当作独立发布域

金融、医疗、政企或需要严格审计的团队,不应把专用签名节点理解成“单独租一台 Mac”这么简单。独立节点还需要独立的管理员、网络出口、变更审批、凭证轮换和事件响应责任。

Apple 的 Account Holder、Admin、App Manager 和 Developer 角色具有不同权限;证书管理、用户管理、App Store Connect 操作和 API Key 生成也并非天然由同一个角色承担。Apple Developer Program 角色与权限

建议建立一张责任矩阵,至少记录以下动作:

动作 Apple 角色 CI 平台角色 macOS 主机角色 审计证据
申请或创建证书 Account Holder / Admin 不应直接代替申请 不负责审批 申请记录、变更单
导入证书私钥 授权的发布管理员 受控服务账号 节点管理员执行 Keychain 导入日志
调用签名任务 受限发布主体 发布流水线 不能由普通账号调用 审批记录、任务日志
上传构建 受限 App Store Connect 权限 发布任务 节点仅提供执行环境 上传记录
撤销凭证 Account Holder / Admin 不应拥有默认权限 不负责 Apple 侧撤销 撤销记录、回退方案
人员离职撤权 IT 与研发负责人 禁用任务触发权限 删除本地账号和会话 离职工单、复测结果

App Store Connect API Key 也需要单独治理。Apple 说明,Team API Key 可以覆盖组织内的所有 App,不能限制到单个 App;个人 API Key 则继承关联用户的权限。私钥只提供一次下载,丢失或疑似泄露后应立即撤销。App Store Connect API 说明

因此,上传凭证不能被当成“只是另一个 CI Secret”。它与证书私钥、Provisioning Profile、macOS 本地账号和 CI 平台令牌的生命周期不同,责任人也不应默认相同。

用条件分支确定共享、专用还是混合

可以按以下条件做架构决策,不要只根据当前并发量或某一次构建耗时决定:

  • 若只有单一应用、发布频率较低、人员权限稳定,且能使用独立账号与临时 Keychain完成重启复测,则可选择同一台 Mac 的逻辑隔离;否则回退到专用签名节点。
  • 若多个仓库、多个产品团队或外包协作者共用 Runner,且无法证明任务之间完全清理,则普通构建与生产签名必须分离。
  • 若生产签名凭证需要访问内部网络、企业服务或受审计的发布系统,则签名节点应拥有独立网络出口和独立变更流程。
  • 若业务要求人员离职后立即撤权、证书轮换后快速恢复,专用节点比共享节点更容易形成可验证的责任边界。
  • 若生产发布负载稳定、持续运行且对本地接口或固定网络有要求,可考虑自购并长期维护专用 Mac;若需求是阶段性发布、PoC、临时扩容或跨地域协作,则远程 Mac 更适合先验证。
  • 若普通构建负载存在明显峰值,而生产签名负载相对稳定,应采用共享构建池加专用签名节点的混合架构。

三种方案的差异可以这样理解:

  • 共享 Mac:固定容量和设备数量较少,但工作区残留、权限串用和故障影响面更大;
  • 专用 Mac:隔离、审计和恢复边界更清晰,但空闲容量、节点维护和凭证轮换成本需要长期承担;
  • 混合节点池:把普通构建的弹性需求与生产签名的稳定边界分开,架构复杂度更高,但通常更适合多项目企业。

这里不应凭未经核实的价格或节省比例下结论。采购表至少要同时记录固定容量、空闲时间、节点交付方式、系统升级责任、恢复路径、人员投入和退出成本。

远程 Mac 签名节点验收清单

把远程 Mac 作为签名节点时,验收重点不是能否通过 VNC 登录,而是能否在权限变化和主机故障后恢复完整发布流程。

账号与路由

  • [ ] 普通构建账号无法读取生产 Keychain;
  • [ ] 生产签名任务只能由受保护分支或审批后的工作流触发;
  • [ ] Runner 标签不会被普通仓库自行修改;
  • [ ] 外包或低信任协作者不能调度到生产签名节点;
  • [ ] 归档、签名和上传步骤在日志中可区分;
  • [ ] 任务失败后不会自动把生产凭证写入普通日志。

Keychain 与凭证

  • [ ] 生产证书私钥不放在默认登录钥匙串;
  • [ ] 临时 Keychain 的创建、解锁和销毁都有记录;
  • [ ] securitycodesignxcodebuild 的调用账号已固定;
  • [ ] Provisioning Profile 与 App ID、证书的对应关系可追溯;
  • [ ] App Store Connect API Key 的角色和适用范围已登记;
  • [ ] 证书、API Key、CI 令牌和本地账号分别设置撤销责任人。

故障与恢复

  • [ ] 主机重启后,自动任务不会在未解锁的生产 Keychain 上静默失败;
  • [ ] 用户会话丢失后,可以按运行手册恢复归档、签名和上传;
  • [ ] 旧节点被隔离后,新节点能够从干净环境重新导入非生产凭证;
  • [ ] 证书撤销并重新生成后,流水线能完成重新签名;
  • [ ] 人员离职后,旧账号、旧令牌和旧会话均无法继续发布;
  • [ ] 主机故障时,团队知道回退到哪个构建池,以及哪些任务必须暂停。

Apple 的 Keychain 项目可以根据设备状态设置可访问条件,默认行为与设备解锁状态有关;这也是为什么“机器重启后签名仍然成功”与“机器重启后签名仍然符合安全策略”不是同一件事。Apple Keychain 项目可访问性

验收记录应至少包含触发人、仓库、分支、节点、账号、Keychain 名称、证书标识、上传结果和失败后的处置方式。没有这些证据,专用节点只是物理上分开,并不能证明信任边界已经建立。

当前方案与 Mac 方案的取舍

如果当前做法是让所有 PR、归档和生产发布共用一台长期运行的 Mac,真实缺点通常不是“速度不够”,而是普通任务与生产凭证难以彻底分离、人员离职后的撤权依赖人工清理,以及主机故障后缺少可复现的恢复路径。

如果改成完全自购的多台 Mac,又会承担设备闲置、硬件维护、系统升级、远程接入和备用机建设等长期责任。对于稳定重负载、必须连接物理设备或需要长期固定网络的团队,自购专用 Mac 仍然可能更合适;但对于签名 PoC、阶段性发布、临时扩容和跨地域团队,先租赁一台 RUVCLOUD 远程 Mac 验证账号隔离、临时 Keychain、任务路由和重启恢复,通常比直接建设完整机房方案更容易控制试错范围。可先查看 RUVCLOUD 的远程 Mac 服务入口,再根据节点周期和容量需求核对 Mac 租赁方案与价格

最终决策不应从“这台 Mac 能不能签名”开始,而应从“哪些人、哪些任务、哪些凭证可以在什么条件下签名”开始。完成非生产凭证试点并保留完整验收记录后,再决定长期专用签名节点,或采用共享构建池加专用远程 Mac 的混合节点池。需要进入实施阶段时,可通过 RUVCLOUD 远程 Mac 订购页面申请隔离节点进行验证。