Trusted Publishing 替代长期密钥:VS Code 扩展发布链路的信任边界怎么划
Microsoft 为 Visual Studio Marketplace 推出 Trusted Publishing,用 GitHub Actions 的 OIDC 或 Azure Pipelines 的 Entra 背书 WIF 身份替代长期 Azure DevOps PAT。信任边界因此从“仓库里的一枚长期密钥”移到“CI 平台签发的短时身份 + Marketplace 侧的策略或身份绑定”。文章拆解 GitHub 策略匹配字段、私有仓库 48 小时激活窗口、Azure 侧不走 Marketplace policy 的差异,以及先并行后撤除 PAT 的迁移顺序,并列出材料未覆盖、仍需自行
- Trusted Publishing 把发布凭证从 CI 密钥换成 CI 平台签发的短时身份,GitHub Action
- GitHub Actions 的边界由 Marketplace 策略决定,策略精确匹配仓库所有者、仓库名、工作流文件名和
- 私有仓库新建策略只有 48 小时临时激活窗口,首次成功换取令牌后永久激活。
- 迁移应先让 OIDC 发布跑通再删除 PAT;现有 PAT 发布方式继续可用。
如果你现在用一条长期 Azure DevOps 个人访问令牌(PAT)在 CI 里发布 VS Code 或 Visual Studio 扩展,那么信任边界其实划在“谁能读到那个 secret”上。Microsoft 为 Visual Studio Marketplace 推出的 Trusted Publishing,把这个边界往上游挪了一层:CI 平台签发的短时身份取代了可存储的发布凭证,Marketplace 侧只验证“这个工作流或这个身份被允许发布”。Microsoft 的发布说明给出的关键事实是:没有需要存储或轮换的发布密钥,GitHub Actions 场景下临时凭证 15 分钟后过期。
下文围绕“信任边界怎么划”拆解这条链路:谁签发身份、谁做验证、策略粒度落在哪里、失败或过期时回退到什么状态。所有具体字段与行为均来自上述公告;材料未覆盖的部分会明确标为待核实。
先看旧模型:PAT 把信任集中在 secret 上#
按公告描述,传统的自动化发布要求把 PAT 存成 CI/CD secret。这条链路的信任边界有三个特征:凭证长期有效、任何能读到该 secret 的步骤都能使用它、轮换需要人工介入。它的风险不在于 PAT 一定会泄露,而在于一旦泄露,有效窗口由凭证本身决定,而不是由某次发布决定。
Trusted Publishing 改变的是这一层:CI/CD 系统向 Marketplace 出示联邦身份,Marketplace 验证 GitHub 的 Trusted Publishing 策略或 Azure Pipelines 的工作负载身份,发布工具凭已验证身份发布,全程没有 PAT 可取用。
信任链上的三个角色#
- 签发方:GitHub Actions 的 OIDC,或 Azure Pipelines 中由 Microsoft Entra 背书的 Workload Identity Federation(WIF,工作负载身份联合)。它决定“这次运行为谁作证”。
- 验证方:Visual Studio Marketplace。GitHub 场景下校验你预先创建的 Trusted Publishing 策略;Azure 场景下校验以 publisher contributor 身份出现的工作负载身份。
- 使用方:发布工具本身。公告的表述是“用已验证身份发布,无需 PAT”,并强调走 OIDC 时整个过程自动完成。
值得注意的是两侧的验证机制并不对称:GitHub 依赖 Marketplace 侧的一条策略,Azure 依赖 Entra 侧的服务连接加 Marketplace 侧的贡献者角色。这种不对称直接决定了后面两节的配置差异。
GitHub Actions:策略就是边界的定义#
在 Marketplace 的 publisher 管理页打开 Trusted Publishing,展开 Create 后需要填写四个必填项和一个可选项,公告给出的语义如下:
| 字段 | 填写内容 | 边界含义 |
|---|---|---|
| Policy name | 描述性名称,如 Release workflow | 仅用于识别,不影响授权 |
| Repository owner | 精确的 GitHub 组织或用户登录名,如 contoso | 绑定所有者,不接受近似匹配 |
| Repository | 不含所有者的仓库名,如 sample-extension | 绑定到具体仓库 |
| Workflow | .github/workflows 下的工作流文件名,如 publish.yml | 绑定到具体工作流文件 |
| Environment(可选) | 工作流使用的 GitHub environment 精确名称,如 production | 需要环境审批时补上这一层 |
把它读成边界:这条策略授权的是“某个仓库里的某个工作流文件(以及可选的某个环境)”,而不是“这个仓库的所有自动化”。公告未说明策略是否支持通配符、是否区分分支或提交,也未说明同一仓库能否并存多条策略——这些都需要按官方文档核实,不能从字段列表反推。
私有仓库的 48 小时窗口是个容易踩的点
公告明确区分了仓库可见性:私有仓库新建的策略先处于临时激活状态,有效期 48 小时,首次成功完成令牌交换后永久激活;若窗口内没有被使用,策略过期,可以重新打开再获得 48 小时。策略的状态和最后使用时间都能在 Trusted Publishing 页面查看。对排期发布的项目来说,这意味着“建完策略、隔一周再接 CI”的顺序会失败,正确顺序是先建策略、尽快跑一次真实发布。
工作流侧只需要一个权限
迁移步骤中与工作流文件相关的改动只有一处:给发布 job 加上 id-token: write。以下片段为未执行的示例,仅示意权限位置,具体发布命令参数以 Visual Studio Code 文档中的安全自动发布指南为准。
# 未执行示例:仅展示 OIDC 所需权限声明
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # 允许该 job 申请 OIDC 令牌
steps:
- uses: actions/checkout@v4
# 发布步骤的具体参数请对照官方指南,本文不代为给出
这里有一个材料没展开、但工程上必须自己回答的问题:id-token: write 是 job 级权限,任何进入该 job 的步骤理论上都能申请令牌。公告没有讨论第三方 action 混入同一 job 的情况,因此这条边界需要你在工作流结构上自行收窄,而不是指望 Marketplace 策略替你区分步骤。
Azure Pipelines:边界在身份和角色,不在策略#
Azure 侧的路径完全不同,公告用加粗级别的措辞提醒:不要为 Azure Pipelines 在 Marketplace 的 Trusted Publishing 页面创建策略。正确做法分三步:
- 配置一个使用 Workload Identity Federation 的 Azure Pipelines 服务连接,后端可以是 Microsoft Entra 应用注册加服务主体,也可以是托管标识。
- 把该身份加入 Visual Studio Marketplace 的 publisher,并授予 Contributor 角色。
- 把发布步骤配置为使用这个 WIF 服务连接。
于是 Azure 侧的信任边界落在两处:Entra 侧“谁能使用这个服务连接”,以及 Marketplace 侧“这个身份在 publisher 里是什么角色”。公告只说明需要 Contributor 角色,未逐条列出该角色在 Marketplace 中的全部权限范围,这一点属于需要自行核实的部分,尤其当同一 publisher 下有多个扩展时。
迁移顺序:先并行,再撤除#
公告给出的 GitHub 迁移路径是渐进的,顺序本身就是风险控制:
- 为仓库和工作流创建 Trusted Publishing 策略。
- 给发布 job 加
id-token: write。 - 把发布命令改为走 OIDC。
- 运行工作流,确认 OIDC 发布成功。
- 成功之后再从工作流和仓库 secret 中移除 Marketplace PAT。
公告明确建议在第一次 Trusted Publishing 运行验证通过前保留 PAT,成功后再删除未使用的 secret,避免它被其他工作流步骤误用。Azure 侧同理:先配好 WIF 服务连接与 publisher 身份,更新发布步骤使用该连接,联邦发布成功后再移除 PAT。
这个顺序隐含一个判断:迁移期间两种凭证并存,此时实际信任边界是两者中较宽的那个。把窗口压到最短,比一次性切换更稳妥。
四类边界,一张对照表#
| 边界问题 | GitHub Actions | Azure Pipelines |
|---|---|---|
| 身份由谁签发 | GitHub OIDC | Microsoft Entra 背书的 WIF 服务连接 |
| Marketplace 侧配置 | 需创建 Trusted Publishing 策略 | 不创建策略,只把身份加为 Contributor |
| 授权粒度 | 仓库所有者 + 仓库 + 工作流文件(+ 可选 environment) | 服务连接身份 + publisher 角色 |
| 凭证有效期 | 临时凭证 15 分钟过期 | 公告未给出具体时长,待核实 |
这套模型没有替你解决的问题#
- 工作流内部的可信度:策略保证“是这个工作流文件发起的”,不保证文件里的每一步都可信。依赖的第三方 action、脚本注入、被篡改的分支,仍要靠在仓库侧控制。
- 分支与评审约束:公告未说明策略是否限定分支,也未说明环境审批如何与策略交互。若需要“只有合并到主分支且通过评审才能发布”,这部分要靠 GitHub 侧规则补足。
- 失败路径与重试:公告未描述令牌交换失败时的错误语义、限流或重试建议,排障应按官方文档进行。
- 审计:公告提到可在页面查看策略状态和最后使用时间,但未说明事件留存周期与导出方式。
归纳起来,Trusted Publishing 把“有没有一枚长期密钥泄露”这个高频问题,换成了“身份签发条件、策略匹配范围、角色权限、迁移窗口”四个可逐项检查的控制点。前两个由 Marketplace 策略和 CI 平台共同决定,第三个在 Azure 侧尤其要自己确认,第四个则是迁移期最容易放松的地方。
公告同时说明现有 PAT 发布方式继续可用,因此采用节奏可以自定;但一旦决定迁移,建议按公告顺序执行:先建策略、再改工作流、验证成功、最后删密钥。具体配置细节与排障,应以 Visual Studio Code 和 Visual Studio 文档中的对应指南为准。
常见问题(FAQ)
启用 Trusted Publishing 后,原来的 PAT 会立刻失效吗?
Azure Pipelines 也需要在 Marketplace 的 Trusted Publishing 页面建策略吗?
私有仓库创建的 GitHub 策略为什么过一段时间就不能用了?
参考来源
以上为本文写作时的参考资料,引用观点以原文为准。