尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

GitHub Actions OIDC 安全:用 audience 约束守住云凭证边界

GitHub Actions OIDC 安全:用 audience 约束守住云凭证边界 如果你已经走完了“把 AWS/Azure/GCP 的密钥从 GitHub Secrets 里移除换成 GitHub Actions OIDC”这一步那么接下来最容易被忽视也最值得花时间检查的就是 OIDC 的 audience 约束。很多团队接入 OIDC 的时候只验证了身份来源也就是“这个 token 确实来自 GitHub Actions”却没有进一步限制“这个 token 到底能激活哪个角色”。结果就是token 是临时的权限却是放开的。相比之下硬编码密钥的风险只是从“依赖长期凭证”变成了“依赖临时凭证”安全问题并没有彻底解决。这篇文章从 GitHub Actions 的 OIDC 机制入手重点讲清楚 audience 是什么、为什么必须加约束、在主流的 AWS / Azure / GCP 上如何配置以及一个完整的端到端最小示例。如果你正在优化 CI/CD 安全或者准备从静态密钥迁移到 OIDC这篇文章能帮你避开最容易踩的坑。1. 为什么要关注 GitHub Actions 的 OIDC 安全CI/CD 流程里的凭证管理一直是供应链安全最薄弱的环节之一。早期做法是创建一对长期有效的 Access Key ID / Secret Access Key写进 GitHub Secrets。这种方式最大的问题是密钥一旦泄露攻击者就有了和正常发布流程一样的权限你根本无法区分这次调用的发起者到底是你的工作流还是拿到了密钥的恶意第三方。OIDC 改变了这个思路。GitHub Actions 工作流运行时会向 GitHub 的 OIDC 提供方请求一个短时 JWT token工作流里的第三方 Action 拿到这个 token 后可以在云服务商那里换取一个临时角色凭证。token 几分钟后过期角色凭证也有时效限制所以不再需要把长期密钥放到仓库里。这里就是真正值得注意的地方OIDC 只是解决了“身份怎么证明”的问题没有解决“这个身份能做什么”的问题。我在实际项目里见过不少配置GitHub 侧声明了id-token: write云服务商侧也创建了 OIDC provider但信任策略里只校验了iss甚至只校验了是否来自该 OIDC provider。相当于任何能触发工作流的人都能通过这个 token 去扮演那个云上角色。出现这种问题的原因很容易理解官方文档和大多数教程都在演示“怎么配置 OIDC”而很少强调 OIDC token 里面那几条 claim 的组合使用。尤其是 audience 这一条很多人误以为它只是 OIDC 协议里的一个形式化字段不关心它的具体值。但在 AWS 的AssumeRoleWithWebIdentity、Azure 的 Federated Identity Credential、GCP 的 Workload Identity Federation 里audience 都是服务端信任策略的裁决依据之一。这篇文章适合开发运维工程师、平台工程团队以及任何负责 GitHub Actions 工作流安全的人。读完你可以回答三个问题我的 OIDC token 可以被谁使用我的云角色信任策略是否真的最小化了如果工作流被恶意提交影响攻击者能不能拿到我生产环境的权限2. OIDC 与 audience 的核心概念要理解 audience 约束先要知道 OIDC token 是什么样子。GitHub Actions 在工作流满足条件时会向https://token.actions.githubusercontent.com签发一个 JWT这个 JWT 是一个 JSON Web Token包含以下几个关键声明Claim含义示例值iss签发者固定为 GitHub OIDC 提供方https://token.actions.githubusercontent.comaud受众表示这个 token 计划给谁使用sts.amazonaws.com或自定义值sub主题表示 token 对应的主体repo:org/repo:ref:refs/heads/mainrepository所属仓库org/repoenvironment当前工作流使用的环境productionsha触发的 commit SHAe7f3...aud全称是 audience直译就是“受众”。在 OAuth 2.0 和 OIDC 协议里aud表示这个 token 的接收方。就像一张入场券上会写明“仅限 XX 会场使用”如果持票人拿它去另一个会场验票方应该拒绝。当 GitHub Actions 请求 OIDC token 时第三方 Action 会根据目标云服务商设置aud而云服务商在验证 token 时需要确认 token 中的aud与自己期望的值一致。sub和aud的区别也很容易被忽略。sub描述的是“谁发起的请求”用来限制身份来源例如必须是某个仓库、某个分支或某个环境aud描述的是“这个 token 是发给谁的”用来限制 token 的使用范围。两者不是替代关系而是互补关系。只看sub等于只验证了来的人是对的却不验证他拿的票是不是发给他这张座位号的只看aud等于验证了票的会场却没有验证你是不是合法购票人。真实的 GitHub Actions OIDC token 中aud并不是固定值。GitHub 官方文档允许工作流通过第三方 Action 传递自定义 audience。常用的 Action 都有对应的audience输入参数比如aws-actions/configure-aws-credentials、azure/login、google-github-actions/auth。也就是说你可以为不同环境、不同应用设置不同的aud让每个云角色只接受属于自己业务范围的 token。这个机制很重要因为它让“某一类工作流只能换取某一个角色”成为可能。如果你没有显式设置Action 通常会用默认值比如 AWS 的默认 aud 是sts.amazonaws.com。默认值不是不能用但在多环境共存的场景下一旦所有工作流都用相同的默认 aud服务端就只能靠sub来区分权限条件判断会变得非常笨重也容易出错。3. 没有 audience 约束时会发生什么只配置 OIDC provider、不配置 audience 约束在真实攻击场景里会露出几个明显的口子。第一个场景第三方 Action 供应链投毒。GitHub Actions 工作流里通常会引用大量第三方 Action如果某个 Action 被恶意维护者修改或者某个 Action 依赖的底层脚本被篡改恶意代码就可以在工作流运行时读取ACTIONS_ID_TOKEN_REQUEST_TOKEN向 GitHub 请求 OIDC token然后用这个 token 去云平台换取角色凭证。如果云角色的信任策略只校验了iss和sub没有校验aud恶意代码就能把 token 用于任何接入同一 OIDC provider 的服务。这等于把一个仓库的凭证风险扩散到了所有信任同一 provider 的资源上。第二个场景环境权限越界。假设一个组织有staging和production两个环境两个环境对应两个云角色。如果两个工作流都使用相同的默认 audience且角色信任策略里的sub条件写得比较宽松攻击者通过伪造提交或者污染主分支就能触发 staging 工作流然后尝试用获取到的 token 去扮演 production 角色。虽然sub条件会限制仓库名但如果没有把environment一起写进条件生产角色也可能被 staging 工作流 assume。第三个场景pull_request 事件滥用。很多团队会在pull_request事件上运行测试工作流。如果这个工作流声明了id-token: write那么任何向仓库提交 PR 的人都能触发一次 OIDC token 请求。虽然sub中可以包含pull_request相关信息但如果服务端信任策略没有做严格限制攻击者可以构造一个恶意 PR在修改后的工作流配置里直接调用云服务 API。这也是 GitHub 官方文档特别提醒过的风险不要在pull_request触发的任务中配置高权限身份除非你能保证服务端做了足够细的约束。说白了没有 audience 约束的 OIDC只是“把静态密钥换成了动态票据”。票据的时效性确实提升了但票据的可控范围没有缩小。一个票据可以从 A 场景拿到又能在 B 场景使用这本质上还是权限边界模糊。4. 主流云平台如何配置 audience 约束不同云服务商对 OIDC audience 的校验方式不同。下面分别看 AWS、Azure、GCP 的配置思路。4.1 AWS通过在 IAM 角色信任策略中校验sts:audAWS 使用 IAM OIDC 身份提供商来描述 GitHub 的 OIDC provider。角色信任策略中AWS 为 GitHub OIDC token 提供了条件键token.actions.githubusercontent.com:aud和token.actions.githubusercontent.com:sub。实际配置时两个条件都应该写。只写sub不写aud是不能完全控制使用范围的。推荐的信任策略如下{ Version: 2012-10-17, Statement: [ { Effect: Allow, Principal: { Federated: arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com }, Action: sts:AssumeRoleWithWebIdentity, Condition: { StringEquals: { token.actions.githubusercontent.com:aud: sts.amazonaws.com, token.actions.githubusercontent.com:sub: repo:your-org/your-repo:environment:production } } } ] }这里aud要和 GitHub Actions 工作流中请求 OIDC token 时使用的 audience 保持一致。如果工作流使用aws-actions/configure-aws-credentials的默认配置aud 就是sts.amazonaws.com如果自定义了audience参数这里就要改成自定义值。4.2 Azure在联邦身份凭证中设置受众Azure 使用应用注册App Registration中的 Federated Identity Credential 来配置 GitHub Actions OIDC。创建联邦凭证时会要求填写Subject Identifier和Audience。Audience的默认值通常是api://AzureADTokenExchange。如果你在 GitHub 工作流里使用azure/loginAction并指定了自定义 audience就必须保证这个值与联邦凭证中配置的受众一致。Azure 的校验逻辑是把 GitHub 签发的 token 中的aud与联邦凭证里保存的受众做匹配。这个字段不是可选填项配置时必须显式设置。很多人在本地调试时遇到AADSTS70021原因之一就是联邦凭证里的受众和工作流请求 token 时指定的 audience 不匹配。4.3 GCP在 Workload Identity Provider 中使用 attribute_conditionGCP 的 Workload Identity Federation 允许你配置一个 Workload Identity Provider然后通过attribute_condition对 token 中的各种属性做条件判断。GitHub Actions 的 OIDC token 会被映射成 provider 属性其中attribute.aud就是 token 中的aud字段。可以为不同应用创建不同的 provider或者在同一个 provider 中配置不同的attribute_condition。attribute_condition attribute.aud my-custom-audience attribute.repository your-org/your-repoGCP 官方建议在attribute_condition里同时处理aud和sub映射为attribute.repository、attribute.environment等原因和 AWS 一致不能让 token 的用途超出预期范围。5. GitHub Actions 工作流侧如何配合云平台侧的 audience 约束不是唯一决定因素GitHub Actions 工作流里的声明方式同样重要。首先要确认工作流显式声明了permissions并且包含id-token: write。在 GitHub 默认权限策略改为只读之后如果你的工作流没有在顶层声明permissionsOIDC token 请求会失败。permissions: id-token: write contents: read其次要理解第三方 Action 是怎么申请 OIDC token 的。以aws-actions/configure-aws-credentials为例它内部会访问 GitHub 提供的内部端点拿到 JWT然后用这个 JWT 调用sts:AssumeRoleWithWebIdentity。Action 支持audience输入参数比如- name: Configure AWS credentials uses: aws-actions/configure-aws-credentialsv4 with: role-to-assume: arn:aws:iam::123456789012:role/github-actions-production aws-region: us-east-1 audience: sts.amazonaws.com如果这里使用了自定义 audience比如production-oidc-aud那么同一台云平台上的角色信任策略里也必须写这个值。这里真正容易踩坑的地方是默认值很好用所以很多团队从来不改audience参数后来为了做环境隔离新建了多个云角色但所有角色的信任策略还是写着同一个默认aud。这时候你能做的区分只有sub一旦角色数量变多信任策略管理会越来越麻烦还容易出现条件写错导致越权。另外如果要在工作流中直接获取 OIDC token 做调试不建议临时写一个 Action 去请求ACTIONS_ID_TOKEN_REQUEST_URL。这会破坏权限模型的可审计性。最好使用官方或社区维护良好的 Action确保 token 传递过程可控。6. 完整示例AWS GitHub Actions 最小可信闭环下面用一个完整的示例说明“GitHub Actions 工作流 AWS IAM 角色 audience 约束”的最小配置闭环。示例场景是生产环境推送主分支后构建并部署到 AWS且只有 production 环境下运行的 job 才能假设这个角色。6.1 GitHub Actions 工作流文件路径.github/workflows/deploy-production.ymlname: Deploy Production on: push: branches: - main permissions: id-token: write contents: read jobs: deploy: runs-on: ubuntu-latest environment: production steps: - name: Checkout uses: actions/checkoutv4 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentialsv4 with: role-to-assume: arn:aws:iam::123456789012:role/github-actions-prod-deploy aws-region: us-east-1 audience: sts.amazonaws.com - name: Verify identity run: aws sts get-caller-identity这段配置的关键逻辑是environment: production让这个 job 带上了 production 环境的标识permissions.id-token: write允许 Action 获取 OIDC tokenaudience: sts.amazonaws.com则明确了 token 的接收方。6.2 AWS IAM 角色信任策略文件路径role-trust-policy.json{ Version: 2012-10-17, Statement: [ { Effect: Allow, Principal: { Federated: arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com }, Action: sts:AssumeRoleWithWebIdentity, Condition: { StringEquals: { token.actions.githubusercontent.com:aud: sts.amazonaws.com, token.actions.githubusercontent.com:sub: repo:your-org/your-repo:environment:production } } } ] }对比一下如果只有sub条件而省略aud理论上另一个使用相同 OIDC provider 的 GitHub 仓库也能拿到 token 来假设这个角色前提是它满足 sub 条件。加上了aud之后token 必须明确指向sts.amazonaws.com才会被接受。6.3 角色权限策略文件路径role-policy.json{ Version: 2012-10-17, Statement: [ { Effect: Allow, Action: [ s3:PutObject, s3:GetObject ], Resource: arn:aws:s3:::your-production-bucket/* } ] }光有信任策略还不够角色本身的权限策略决定了 assume 之后能做什么。这里使用最小权限只允许对生产桶写入对象和读取对象不允许列出所有的桶也不允许操作 IAM。6.4 验证方式工作流运行后在 Actions 日志里找到 “Verify identity” 这一步预期输出类似{ UserId: AROA1234567890EXAMPLE:GitHubActions, Account: 123456789012, Arn: arn:aws:sts::123456789012:assumed-role/github-actions-prod-deploy/GitHubActions }如果看到AccessDenied优先检查角色信任策略里的aud和sub是否和工作流中的配置一致。可以把 GitHub 签发的 JWT 保存下来用本地工具解析确认aud字段的实际值。jq .aud, .sub token.jsonjq对于 JWT 的解析是取第二段 payload前提是先把第三段签名去掉。更直观的做法是直接使用在线 JWT 解析工具或者用 Python 的jwt库这里不展开。7. 常见问题与排查思路GitHub Actions OIDC 的报错很多时候都集中在云平台侧错误信息并不直观。下面整理一份排查清单。问题现象可能原因排查方式解决方案GitHub 工作流提示权限不足缺少id-token: write检查工作流permissions段在 job 或顶层添加id-token: writeAWS 返回AccessDenied角色信任策略中的aud或sub不匹配解析 OIDC JWT对比 claims 与信任策略条件统一工作流 audience 参数和角色信任策略Azure 报AADSTS70021联邦凭证受众不匹配检查应用注册中的 federated credential 配置客户端配置的 audience 与联邦凭证受众保持一致GCP 报permission deniedworkload identity provider 的attribute_condition没匹配查看请求日志中的 token 属性修正attribute_condition条件staging 工作流可以 assume 生产角色sub条件中缺少 environment检查环境是否在工作流中声明在sub中追加:environment:production自定义 audience 后调用失败工作流 Action 和云角色配置用了不同 audience对比所有配置项中的 audience 值统一使用同一个 audience 字符串排查时第一件事永远是解析当前 JWT拿到aud、sub、environment等真实值再和云平台上的信任策略逐字对比。不要凭记忆改配置很多时候就是多了一个空格或者少了一个environment段。8. 工程最佳实践与安全建议8.1 为每个环境分配唯一的 audience在多环境共存的场景下建议使用可读性高的自定义 audience例如prod-oidc-aud、staging-oidc-aud。这样就算sub条件写漏了环境云角色也只能被特定 audience 的 token 访问权限边界更清晰。缺点是每个环境需要多维护一个 audience 值但相比权限失控带来的风险这点成本非常值得。8.2 组合使用aud和sub不要只依赖某个单一 claim。aud控制 token 的用户范围sub控制 token 的来源范围。两者组合才是真正的“最小权限”信任策略。在 AWS 中条件键建议同时写aud和sub在 GCP 中attribute_condition同时判断attribute.aud和attribute.repository。8.3 严格限制触发事件包含id-token: write并且会换取高权限角色的工作流尽量避免在pull_request事件上执行。如果必须运行某些检查可以拆分为两个 job一个无权限、一个带权限且只允许push到受保护分支后触发。8.4 锁定第三方 Action 版本OIDC token 的申请和传递是由第三方 Action 完成的这意味着第三方 Action 的可信度直接决定生产凭证的安全性。建议在引用 Action 时固定到具体版本而不是使用master或main等可变引用。- uses: aws-actions/configure-aws-credentialsv4这样至少能避免在不可控的更新中引入恶意或存在缺陷的代码。更严格的做法是使用满足依赖审查的完整版本号。8.5 定期审计 OIDC 使用记录AWS CloudTrail 会记录AssumeRoleWithWebIdentity事件Azure 和 GCP 也有对应的审计日志。建议定期检查谁在使用你的云端角色、使用来源是什么、是否出现了异常的调用序列。如果发现某个非预期工作流频繁换取高权限角色应当立即收缩信任策略。8.6 角色权限策略按最小化原则编写即使信任策略已经限制了来源和 audience角色本身能做什么仍然由权限策略决定。生产角色只授予部署所需的操作测试角色不要挂到生产资源上。这个原则和普通 IAM 权限管理保持一致不要因为有了 OIDC 就放松对权限策略的要求。9. 总结与后续学习方向GitHub Actions OIDC 确实解决了长期密钥的问题但它并不是配置完就万事大吉。真正决定安全边界的是云平台侧对 token 的约束其中 audience 是最容易被忽略、也最关键的一环。没有 audience 约束OIDC token 就可能被用于非预期场景恶意提交、供应链投毒、环境越权都会变成实际风险。建议你现在就做一个简单检查打开你的云平台 OIDC provider 配置看看角色信任策略或联邦凭证里有没有校验aud。如果没有先补上如果已经有确认它和你工作流中的 audience 参数是一致的。这个动作花不了十分钟但能显著缩小工作流凭证的爆炸半径。后续可以继续学习的方向包括在 Azure 和 GCP 上复现同样的 audience 约束配置了解 OIDC token 的生命周期与自动刷新机制以及如何把类似方案推广到自建 CI 平台或 GitLab CI。对任何 CI 系统来说临时凭证的方向是对的但只有把 audience 这类约束做到位才算真正把安全落地。
返回列表