资讯动态

GitHub Actions OIDC配置:锁定audience防凭证泄露

发布时间:2026/8/29 10:36:20 来源:尧图企业网站定制
很多团队在把 GitHub Actions 作为 CI/CD 主线后都会遇到同一个问题部署到云资源时到底该怎么安全地提供凭证。早期方案很直接——把 Access Key 存进 Secrets构建时取出来用。项目小的时候确实够用但一旦密钥被提交到日志、被第三方依赖读取、或者权限范围配得过大凭证泄露就成了定时炸弹。后来 OIDC 临时凭证方案逐渐普及但我在实际帮团队排查时发现大多数人只关注了 subject主题限制却漏掉了 audience受众限制或者干脆没理解 audience 的校验链条。本文就来完整拆解为什么 GitHub Actions 需要 OIDC audience constraints以及在 AWS、Azure 等主流云平台上到底该怎么配置。1. 为什么 GitHub Actions 需要 OIDC 而不是长期密钥1.1 长期密钥的痛点传统做法里GitHub Actions 要访问云资源最常见的方式就是把云厂商的 Access Key ID 和 Secret Access Key 成对写进 GitHub Secrets。构建时通过环境变量或者配置文件注入之后所有步骤都能直接使用。这个方案的优点只有一个简单。但它的问题非常明显密钥生命周期难以管理一旦泄露很难快速发现和轮换。权限范围控制困难很多团队图省事直接给了高权限角色。密钥会被复制到多个仓库、多个环境攻击面越来越大。审计困难无法准确追踪某个部署任务到底使用了哪份身份。最要命的是密钥一旦进入构建日志、缓存或者第三方 Action 的上下文泄露路径就完全不可控了。很多安全事件并不是云厂商被攻破而是 CI 里的长期凭证被窃取后攻击者拿着高权限密钥在云环境里横向移动。1.2 OIDC 临时凭证如何解决这个问题OIDC 是 OpenID Connect 的缩写它建立在 OAuth 2.0 之上用于让 GitHub Actions 在每次运行时向云厂商申请一个短时凭证而不是长期保存密钥。整个流程可以简化成四步Workflow 运行时GitHub 的 OIDC Provider 签发一个 JWTJSON Web Token。云平台通过配置好的 OIDC 身份提供商校验这个 JWT 的签名和声明。云平台确认身份合法后根据角色或权限策略临时颁发一个短期凭证。Actions 使用这个临时凭证去调用云资源凭证过期后自动失效。这样一来CI 里不再需要保存任何长期密钥。即使某个构建节点被攻破攻击者能拿到的也只是一个几分钟内有效、权限被严格限定的临时凭证。不过这只是第一步。JWT 的签发和校验过程里有三个字段决定了这个安全模型是否成立它们分别是iss、aud和sub。很多人只关注了sub却把aud当成了可有可无的配置项这恰恰是最容易留下隐患的地方。2. 拆解 OIDC Token 里的三个关键字段2.1 先看一个真实的 JWT 结构在 GitHub Actions 中开启permissions: id-token: write之后Runner 内部会准备两个环境变量ACTIONS_ID_TOKEN_REQUEST_URL和ACTIONS_ID_TOKEN_REQUEST_TOKEN。通过它们可以拉取到当前工作流的 OIDC Token解码后 Payload 大致长这样{ iss: https://token.actions.githubusercontent.com, aud: sts.amazonaws.com, sub: repo:octocat/demo-app:ref:refs/heads/main, exp: 1700000000, nbf: 1699996400, iat: 1699996400, jti: a1b2c3d4e5f6, ref: refs/heads/main, sha: a1b2c3d4e5f67890abcdef1234567890abcdef12, repository: octocat/demo-app, repository_owner: octocat, run_id: 1234567890, run_number: 10, workflow: deploy.yml }上面的字段在不同云平台的实机环境中会略有差异但核心声明是一致的。GitHub 官方文档将iss、aud、sub称为判定身份安全的三个关键声明。只要其中一个校验不严格整个信任链就可能被绕过。2.2 issToken 的签发者issIssuer表示 JWT 是谁签发的。在 GitHub Actions 场景下这个值固定为https://token.actions.githubusercontent.com云平台的 OIDC 身份提供商配置里必须把同一地址填为 Issuer URL平台才会接受这个 JWT。如果 Issuer 不一致云平台会直接拒绝。2.3 audToken 的目标受众audAudience表示这个 Token 是给谁用的可以理解为“访问卡上的门禁编号”。在 GitHub Actions 的 JWT 中aud的值取决于请求 Token 时携带的 audience 参数以及云平台侧身份提供商的配置。举两个最常见的例子AWS 的 OIDC Provider 需要 audience 为sts.amazonaws.com。Azure 的 Federated Credential 需要 audience 为api://AzureADTokenExchange。云平台在验证 JWT 时会检查 Token 中的aud是否与身份提供商设定的 Client ID 一致。如果两者不一致即使iss和sub都正确也一样会被拒绝。2.4 subToken 代表的实体subSubject表示这个 Token 代表哪个实体。在 GitHub Actions 场景下它通常是一个仓库、分支、环境或拉取请求的组合。常见的sub格式有repo:octocat/demo-app:ref:refs/heads/main repo:octocat/demo-app:environment:production repo:octocat/demo-app:ref:refs/pull/123/merge云平台的信任策略里面通常会把sub作为角色与仓库绑定的核心条件。只允许特定仓库、特定分支或特定环境使用该角色。2.5 三者如何共同完成授权用门禁卡来类比iss是发卡机构告诉你卡是哪个官方机构发的。aud是门禁编号告诉你这张卡只能刷哪栋楼的门禁。sub是持卡人身份告诉你这张卡具体属于谁。平台放行时三者必须同时对得上。如果只校验发卡机构和持卡人却不校验门禁编号那么这张卡只要发卡机构相同、持卡人信息类似就可能被其他门禁系统错误接受。这就是为什么要强调 GitHub Actions 需要 OIDC audience constraints。3. 不加 Audience 限制会出什么问题3.1 授权边界的泛化在云平台创建 OIDC 身份提供商时平台通常会要求填写一个 Client ID这个 Client ID 在校验时对应的就是 JWT 的aud值。如果角色信任策略里只配置了iss和sub却忽略了aud那么整个授权的边界就被拓宽了。攻击者只要能在自己的仓库中构造出符合sub条件的上下文或者通过其他手段拿到一个满足条件的 JWT就有可能直接调用云平台角色换取临时凭证。即使这种攻击在现实中还需要满足其他条件但从安全模型角度看aud缺失相当于少了一道非常重要的隔离层。尤其需要注意的是很多团队会用自定义 Action 或自写脚本来请求 OIDC Token。在请求时Action 可以自由指定 audience。如果云平台侧对 audience 没有严格限制任何能够控制仓库上下文的人都可以尝试用自己构造的 JWT 去匹配受信角色。3.2 从纵深防御角度看 Audience 约束Cloud 安全的基本原则是纵深防御。即使你认为iss和sub已经足够精确aud仍然是整个信任链里不可省略的一环。举一个实际场景假设 AWS 角色信任策略只检查了token.actions.githubusercontent.com:sub而没有检查aud。一旦攻击者接管了某个 GitHub 仓库或者某个仓库名被人为注册成与受信仓库相似的名称那么攻击者在自己的仓库中运行时可以尝试用不同 audience 向 GitHub 请求 Token。只要这个 Token 的sub能匹配到受信角色平台端就可能放行。虽然云平台自身的 OIDC Provider 也会校验 audience但如果在角色策略层继续叠加aud检查就能在身份提供商配置失误或平台行为变化时仍然保住最后一道防线。GitHub 官方安全文档也建议在使用 OIDC 将 GitHub Actions 与云服务集成时要严格限制 Token 的 audience 和 subject而不是只限制其中一个。把aud看成可选项的配置方式无论在哪一家云平台上都不应该出现在生产环境。4. 主流云平台 Audience 配置实战4.1 AWS创建 IdP 与角色信任策略在 AWS 上使用 GitHub Actions 的 OIDC 集成时通常需要先手动或通过 CloudFormation/Terraform 创建 OIDC Identity Provider。创建时核心参数如下参数值Provider URLhttps://token.actions.githubusercontent.comClient IDaudiencests.amazonaws.comThumbprint从 GitHub 的 OIDC 端点获取按 AWS 控制台提示操作创建完成后还需要配置一个 IAM Role并在信任策略中精确限制aud和sub。下面是一个最小可用的信任策略示例{ 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:octocat/demo-app:ref:refs/heads/main } } } ] }这里最关键的是Condition部分token.actions.githubusercontent.com:aud里写死为sts.amazonaws.com。token.actions.githubusercontent.com:sub精确指定仓库和分支防止其他分支或仓库使用该角色。如果需要允许整个仓库的 main 分支、某个 environment或者多个仓库使用同一个角色可以把sub改成StringLike配合通配符但通配符越少越好。尤其是aud不要用StringLike或通配符必须用StringEquals精确匹配。4.2 AzureFederated Credential 与 azure/login 的 audienceAzure 上 GitHub Actions 使用 OIDC 的原理与 AWS 类似但配置入口不同。在 Azure AD / Entra ID 中创建一个应用注册App Registration。进入该应用的 Certificates secrets添加 Federated credentials。选择 GitHub 场景填写仓库、分支或环境并设置 Subject。在 Audienceaudiences字段中设置为api://AzureADTokenExchange。这里需要特别注意Azure 的 Federated Credential 支持配置多个 audience但通常只需要保留默认值api://AzureADTokenExchange。不要为了省事把 audience 配置成通配符也不要在这个数组里随意添加其他值。Workflow 侧使用azure/login时可以直接指定 audiencepermissions: id-token: write contents: read jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Azure Login uses: azure/loginv2 with: client-id: ${{ secrets.AZURE_CLIENT_ID }} tenant-id: ${{ secrets.AZURE_TENANT_ID }} subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} audience: api://AzureADTokenExchange当 Workflow 运行时azure/login会向 GitHub 请求一个 audience 为api://AzureADTokenExchange的 Token然后交给 Azure AD 校验。如果 Azure 侧的 Federated Credential 中 audience 与这里不一致登录就会失败。4.3 其他云平台除了 AWS 和 AzureGoogle Cloud、阿里云、腾讯云也都支持类似的身份集成。例如 Google Cloud 的 Workload Identity Federation在创建 Provider 时需要配置 Issuer 和 Audience并在 GitHub Actions 的认证 Action 中指定对应的audience参数。各平台入口不同但核心原则高度一致Issuer 固定为 GitHub 的https://token.actions.githubusercontent.com。Audience 必须与云平台身份提供商中的配置精确一致。Subject 必须精确限制到仓库、分支或环境。生产环境不要使用宽泛的通配符。具体参数请以各云平台官方文档为准本文不再逐一编造控制台路径。重点是无论在哪个平台都要把 audience 当作强制条件来配置。5. 完整 GitHub Actions Workflow 示例5.1 一个带 Audience 约束的 AWS 部署示例下面用一个常见的静态网站部署场景为例演示包含 audience 约束的完整 Workflow。# 文件路径.github/workflows/deploy-aws.yml name: Deploy to AWS on: push: branches: - main permissions: id-token: write contents: read jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkoutv4 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentialsv4 with: role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy aws-region: us-east-1 - name: Sync to S3 run: | aws s3 sync ./dist s3://my-demo-site-bucket这个 Workflow 中有三个关键点在permissions中显式开启id-token: write否则 Runner 无法获取 OIDC Token。role-to-assume指向第 4.1 节中配置的 IAM Role该 Role 信任策略里已经写死了aud: sts.amazonaws.com和sub: repo:octocat/demo-app:ref:refs/heads/main。aws-actions/configure-aws-credentials会自动以sts.amazonaws.com作为 audience 请求 Token所以不需要在 Workflow 中额外配置 AWS 侧 audience。这种模式里整个 CI 没有任何长期密钥AWS 侧通过信任策略中的aud和sub双重限制把角色使用边界锁得很死。5.2 调试 OIDC Token 的常用命令如果部署失败需要排查 Token 内容可以在 Workflow 中临时增加一个调试步骤- name: Debug OIDC token env: ACTIONS_ID_TOKEN_REQUEST_URL: ${{ env.ACTIONS_ID_TOKEN_REQUEST_URL }} ACTIONS_ID_TOKEN_REQUEST_TOKEN: ${{ env.ACTIONS_ID_TOKEN_REQUEST_TOKEN }} run: | ID_TOKEN$(curl -sS -H Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN \ $ACTIONS_ID_TOKEN_REQUEST_URLaudiencests.amazonaws.com) echo $ID_TOKEN将脚本输出的 JWT 复制到 jwt.io 或任意 JWT 解码工具中检查 Payload 中的iss、aud、sub字段是否与云平台配置一致。注意audience参数要与目标云平台配置完全一致。上面示例中用于 AWS所以使用sts.amazonaws.com如果是 Azure则要替换为api://AzureADTokenExchange。5.3 预期执行结果配置正确时Configure AWS credentials步骤会输出成功信息后面的 AWS CLI 命令能够正常执行。配置错误时常见报错包括Error: Failed to get credentials from session token或者AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity这类错误通常指向角色信任策略不匹配下一步要按照第 6 节的排查清单逐项核对。6. 常见问题与排查思路6.1 常见问题对照表问题现象常见原因解决思路提示 id-token 权限不足Workflow 或 Job 未开启permissions: id-token: write在 Workflow 或 Job 级显式声明该权限AssumeRoleWithWebIdentity 被拒绝角色信任策略中的sub或aud不匹配解码 JWT逐一比对iss、aud、subJWT 的aud与云平台配置的 Client ID 不一致请求 Token 时使用了错误的 audience 参数确认 Action 和云平台侧 audience 配置是否一致仓库改名后部署失败sub中的仓库名与当前仓库不一致更新信任策略中的sub值或避免使用易变仓库名自托管 Runner 无法获取 OIDC Token自托管 Runner 网络策略阻断了 OIDC 端点访问检查 Runner 到token.actions.githubusercontent.com的网络连通性使用StringLike并且写了过宽的通配符安全策略过宽收窄sub范围aud必须使用StringEquals6.2 排查步骤遇到 OIDC 集成失败时建议按以下顺序排查先看 Workflow 日志确认permissions: id-token: write是否生效。打开云平台侧的角色信任策略或 Federated Credential 配置确认 Issuer URL 是否为https://token.actions.githubusercontent.com。确认 Audience 值是否与平台要求一致不要用通配符。确认 Subject 值是否与你的仓库名、分支或环境完全匹配。在 Workflow 中增加调试步骤输出 JWT 并解码把实际看到的iss、aud、sub与策略中对比。检查云平台审计日志找到具体拒绝原因通常信息会指向策略中的某个条件不满足。有了这张图谱大部分 OIDC 对接问题都能在 10 分钟内定位而不是靠猜。7. 最佳实践与工程建议7.1 永远使用最小权限原则OIDC 临牌只解决了凭证泄露的问题并不代表角色本身应该拥有大权限。角色的权限策略应当只包含当前部署流程需要的 API 操作并且建议明确限制资源范围。部署到 S3 就只给该桶的读写权限部署到 Kubernetes 就只给对应命名空间的操作权限。不要因为有了 OIDC 就认为安全无忧角色权限过大仍然是最常见的隐患。7.2 精确限制 Subject 和 Environment实际项目中建议把 Environment 纳入 subject 限制。GitHub 的 OIDC Token 支持environment前缀例如repo:octocat/demo-app:environment:production在云平台信任策略中可以只允许 production 环境使用生产角色。同时为了避免每次代码改动都直接部署生产环境推荐在 GitHub Actions 中启用 Environment 的 Required reviewers 审批机制。这样可以做到代码合并后先走审批再由 CI 使用受限的临时凭证完成发布。7.3 不要把 Audience 配成通配符这是反复强调的一点。aud的作用就是精确指定使用场景任何时候都不应该用*或StringLike来处理。如果云平台控制台允许你填写任意字符串请务必填写平台要求的固定值。AWS 固定为sts.amazonaws.comAzure 固定为api://AzureADTokenExchange具体值以各平台官方文档为准。7.4 锁版本、加审计、留兜底GitHub Actions 的第三方 Action 建议锁定到 tag 或 commit SHA避免供应链攻击。云平台侧开启审计日志记录每次身份交换的 Source IP、Actor、仓库和角色。即使使用了 OIDC也可以在 Secrets 中临时保留一对用于紧急回滚的低权限密钥但要确保它们平时不出现、不使用、定期轮换。另外在实施 OIDC 改造前建议先在测试仓库和小权限角色上验证完整流程再逐步推广到生产仓库。任何涉及生产角色和权限策略的变更都应该走评审和审批流程并保留回滚方案。8. 总结与学习路线通过本文你应该已经理解为什么 GitHub Actions 需要 OIDC audience constraints也知道了iss、aud、sub三者如何共同锁定一条安全的部署链路。实际落地时最优先做的三件事是在所有云平台的 OIDC 集成中为aud使用精确的StringEquals或固定 Audience 配置。为sub增加仓库、分支或环境的严格限制。移除所有不需要的长期密钥让临时凭证成为唯一的默认通道。下一步可以继续学习 OIDC 的可复用工作流Reusable Workflow场景、自定义 OIDC 声明以及如何用 Terraform 或 CloudFormation 统一管理各云平台的身份提供商和信任策略。这些话题都能进一步把 CI/CD 安全从“能用”推进到“可信”。如果在你的项目里遇到 OIDC 对接问题可以直接把本文第 6 节的排查清单拿来对照。配置类问题多数不是原理多复杂而是某个字段写了一个字符的偏差。把这个安全基线做扎实再看其他的 CI/CD 优化会省心非常多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价