自托管 Runner · 机器身份 · TTL 与审计 · 决策矩阵
当 SSH 跳板、签名证书与依赖缓存都已跑通,团队仍会遭遇「某台美东 Runner 磁盘镜像被拿去取证」「新加坡节点上残留三年前的人工 PAT」「同一私钥被复制到 staging 与 prod」。根因是身份模型仍停留在人,而不是按流水线与环境发放的机器会话;它与任务链幂等键和共享池互斥强相关,缺字段时审计只能追到「某同事某晚登录过」,无法对齐到具体构建号。
长密钥落盘税:把组织级 PAT 或 kubeconfig 写进镜像层、plist 与 dotfiles,表现为任意 shell 都能读到同一把万能钥匙;即便文件权限 600,备份与取证仍会扩大读者集合。
跨区复制税:为降低首次编译耗时,把同一份私钥 rsync 到三地 Runner,任何一地的磁盘克隆都会把信任链末端放大三倍;与产物同步策略混用时更难判断泄露面。
离职与轮换滞后税:人工列表记录「谁持有哪把钥匙」,轮换窗口依赖日历提醒;一旦与发布窗口冲突就被推迟,最终形成「知道该换但不敢换」的僵尸凭据。
环境串线税:同一 Runner 既接主干门禁又接外包贡献,环境变量里堆叠多套令牌,Job 间隔离不足时会把 staging 的 audience 误带到 prod 发布。
观测盲区税:只记录构建结果却不记录 token_issuer、subject 与 ttl_remaining_sec,事后无法证明「当时是哪条信任链签发的会话」。
把以上五条写成上线前勾选清单,再进入下一节对比 OIDC 与 PAT,才能把「能跑」升级到「可验收的零长期密钥」。与SSH 与 VNC 安全基线对照阅读时,请区分交互会话与无人值守作业对凭据刷新节奏的不同假设。
三条路径没有绝对优劣,只有与组织规模、合规审计粒度与是否允许出站拉镜像是否匹配。OIDC 把会话绑定到流水线与环境,适合多地区 mesh;长期 PAT 上手快但审计弱;部署私钥适合少数强签名场景,却最难做细粒度撤销。多地区 Mac 场景下还要把区域亲和与失败域写进信任策略,否则美东签发的 audience 被新加坡 Job 误用会把排障拖成跨时区猜谜。
| 维度 | OIDC 工作负载身份 | 长期 PAT | 部署私钥 |
|---|---|---|---|
| 身份粒度 | 仓库/环境/分支级 subject;可绑定 claims | 通常组织或用户级;细拆需要多把令牌 | 多为单密钥对;细拆需多证书与轮换矩阵 |
| 撤销速度 | 停信任策略或缩短 TTL 即可全局失效 | 依赖平台撤销 UI;缓存层可能滞后 | 需 CRL 或指纹黑名单;客户端缓存复杂 |
| 跨区适用性 | 强;claims 可携带 region 与 runner 指纹 | 中;复制面大时等效于广播密钥 | 中;签名场景硬需求但分发面大 |
| 可观测性 | issuer、audience、jti 天然可进日志 | 仅能记录哈希前缀与使用账号 | 需额外埋点记录 key id 与签名目标 |
| 运维成本 | 前置配置高;上线后轮换便宜 | 低起步;后期审计与吊销贵 | 中;证书生命周期管理不可省 |
mesh CI 是否安全,取决于「会话能否被构建号解释」而不是「成功时能否偶尔跑完」。
若你已在实践共享构建池 Runner 编排,把本节选型结论贴进架构说明,可避免「池子有了,但身份仍靠口头交接」的半截工程化。
下面六步刻意保持厂商无关:无论你用 GitHub Actions、GitLab 还是自研调度,只要交付物一致,新同事可以在半天内验证链路。每一步都应对应一条可检查的变更描述;与任务链 handoff组合时,请把 job_id 与 environment 写回信封。
冻结受信颁发者列表:只允许组织内固定 issuer URL,拒绝通配符域名;把变更记录进基础设施变更单。
为每个环境配置 audience:staging、prod、合规分区各自独立 audience,禁止同一 audience 跨环境复用。
在 Runner 启动脚本拒绝明文密钥:检测常见文件名与环境变量模式,发现 PAT 或 kubeconfig 明文即 fail fast。
把 OIDC token 交换成云凭据:使用各云厂商推荐的短时 STS 会话,写入内存文件描述符而非持久路径。
设定 TTL 与续租上限:会话时长覆盖构建 P95 的 1.5 倍并设硬上限;续租失败必须告警,不得静默降级到长期密钥。
演练吊销:随机撤销一条信任策略,验证所有地区 Runner 在同一分钟内拒绝新会话且正在运行任务可预期失败。
export RUNNER_FINGERPRINT="$(system_profiler SPHardwareDataType | shasum | awk '{print $1}')"
export OIDC_AUDIENCE="vpsmesh-ci-prod-${RUNNER_REGION}"
node scripts/exchange-oidc-for-sts.mjs \
--issuer "${ACTIONS_ID_TOKEN_REQUEST_URL}" \
--audience "${OIDC_AUDIENCE}" \
--runner-fingerprint "${RUNNER_FINGERPRINT}"
提示:示例中的交换脚本应把 STS 结果写入进程内存或 tmpfs,并在 Job 结束钩子主动吊销;不要把交换结果回写到 golden 镜像。
mesh 的价值在于「同一套流水线可以在不同地区 Mac 上执行」,但身份边界必须与区域亲和与注册表出站策略一起设计,否则会出现「新加坡拉镜像快但 STS 区域不匹配」「美东交换的临时凭据在新加坡桶上 403」。排障顺序建议先核对 issuer 与 audience,再看 Runner 指纹是否写入 claims,最后才怀疑编译缓存。
先看 claims:repository、environment、ref 是否与预期一致;异常通常来自复用 workflow 模板而未参数化。
再看区域亲和:交换 STS 时选择的 region 必须与产物桶及镜像 registry 同区或满足合规白名单。
最后看缓存:当缓存键与 staged publish异常时,再回到字节路径与校验字段。
记录 jti 与剩余 TTL:把 jti 写入构建日志索引,便于把云端审计记录与流水线对齐。
失败域演练:拔掉某一地区 Runner 网络,验证其余地区不会继承其会话文件或 tmpfs 挂载。
与互斥锁对齐:在领取租约前完成凭据交换,避免半截会话占用席位。
注意:把长期密钥临时解密到磁盘再「用完删除」仍可能遇到崩溃残留;优先使用内存与内核密钥链接口,并在 Job 边界做强制卸载。
下列三条来自大量跨区 iOS 与 macOS 流水线的经验区间,用于立项前核对而非安全保证;你应用真实审计样本替换它们,并在评审附件保留原始分布。
job_id、environment 与 jti 其中之一,否则合规问卷无法闭环。| 托管平台 | 合规等级 | 出站镜像策略 | 更稳的第一选择 |
|---|---|---|---|
| GitHub Actions | 标准 | 允许公共 registry | OIDC 到云 STS;Runner 分组按环境拆 audience |
| GitLab | 标准 | 需私有 registry | CI_JOB_JWT 与 IdP 绑定;镜像拉取走同区缓存 |
| 自研调度 | 高 | 禁止任意出站 | 分区签发服务 + mTLS;人工 PAT 仅 break-glass |
| 多外包贡献 | 中 | 混合 | fork 与内部仓库分离 audience;禁止共享 Runner 目录 |
个人笔记本、临时借用机器与「谁有空谁 ssh」在审计隔离与令牌生命周期上会持续欠账;即便 OIDC 设计正确,底层节点休眠与系统更新窗口也会让会话刷新失败。相较之下,可合同化的云端 Mac 节点才能把区域、镜像与可用性条款落在可验收范围。
常见误区:把「交互登录方便」当成「无人值守作业安全」;交互会话与自动化作业对凭据刷新与磁盘残留的要求相反,混用会拖垮整条链。
若团队既要 iOS 与 macOS 持续交付,又要把 OIDC 会话与多地区 Runner 对齐到可审计字段,自建固定资产往往卡在采购周期与多地布线;借用个人设备则难以满足强制吊销与席位隔离。对需要生产级 mesh CI 与可轮换身份边界 的场景,VpsMesh 的 Mac Mini 云端租赁通常是更优解:按周期弹性计费、区域可选、节点专用可审计,让凭据策略与池容量讨论建立在真实可用性之上,而不是口头承诺。