1. 项目概述:为什么用 OIDC 连接 GitHub Actions 和 AWS 不再是“高级玩法”,而是生产环境的标配
你有没有在 GitHub Actions 里写过这样的 secrets:AWS_ACCESS_KEY_ID和AWS_SECRET_ACCESS_KEY?我试过,而且不止一次——第一次是凌晨三点部署失败后手抖复制粘贴进仓库 secrets,第二次是给新同事配环境时顺手把临时密钥塞进了 workflow 文件。结果呢?一次误提交触发了 PR 检查,密钥明文出现在 diff 里;另一次是某次 CI 流水线日志意外开启 debug 模式,echo $AWS_SECRET_ACCESS_KEY那行输出被完整记录在 GitHub 的运行日志中,虽然日志设为私有,但权限模型本身已埋下风险种子。这根本不是小概率事件,而是所有依赖静态凭证打通 CI/CD 与云平台的团队迟早会踩的坑。Connect GitHub Actions and AWS using OIDC这个标题背后,不是一个技术名词堆砌的教程,而是一套彻底重构身份信任链的实践路径:它让 GitHub Actions 运行器在执行 job 时,能以“临时、短时效、最小权限、可审计”的方式,向 AWS 请求访问令牌,全程不触碰任何长期有效的 API 密钥。核心关键词——OIDC(OpenID Connect)、GitHub Actions、AWS IAM Roles、Federated Identity——每一个都不是概念玩具。OIDC 是现代云原生身份联邦的事实标准;GitHub Actions 是当前最主流的开源 CI/CD 平台;AWS IAM Roles 是云上权限控制的基石;Federated Identity 则是打破“密钥孤岛”的关键机制。这个方案适合三类人:正在设计新项目 CI/CD 架构的 DevOps 工程师、负责合规审计的安全负责人、以及被密钥轮换和权限失控折磨已久的 SRE。它解决的不是“能不能部署”,而是“敢不敢在生产环境持续部署”。实测下来,切换 OIDC 后,我们团队的密钥泄露风险评级从“高危”直接降为“低风险”,IAM 权限审计报告里“未绑定角色的长期凭证”条目清零,更重要的是,新成员入职当天就能安全地触发生产环境镜像构建,无需任何人手动分发或审批密钥。
2. 整体架构设计与选型逻辑:为什么是 OIDC,而不是 Web Identity、SAML 或自建 STS?
要真正理解为什么选择 OIDC 而非其他方案,得先拆解清楚整个身份流转链条里每个环节的“信任锚点”在哪里。很多人一上来就抄配置,却没想明白:GitHub Actions 怎么证明自己是“可信的 GitHub”?AWS 怎么相信这个来自 GitHub 的声明是真的?中间有没有可能被伪造或重放?这就引出了架构设计的核心矛盾:信任必须可验证、可追溯、可撤销,且不能依赖共享密钥这种“单点失效”模式。
2.1 为什么不是 AWS Web Identity Federation(传统方式)?
Web Identity Federation 是 AWS 提供的早期联邦方案,支持 Google、Amazon Cognito 等身份提供商。但它有个硬伤:它要求你在 AWS IAM 中显式配置一个“身份提供商(Identity Provider)”,并上传该提供商的公钥证书(如 Google 的 JWK Set)。GitHub 并不提供静态、长期有效的 OIDC 证书——它的 OIDC 发行方(issuer)是https://token.actions.githubusercontent.com,其 JWKS URI(JSON Web Key Set)是动态轮转的,且 GitHub 明确不承诺证书有效期超过 7 天。如果你强行用 Web Identity 方式去配置,就得每几天手动更新一次 IAM 中的证书,这违背了自动化、不可变基础设施的原则。我试过写脚本自动拉取并更新,结果发现 GitHub 的 JWKS 响应头里Cache-Control: max-age=3600,意味着一小时内可能多次变更,脚本反而成了故障源。这不是运维懒,而是设计哲学冲突:Web Identity 适配的是“稳定身份源”,而 GitHub Actions 是“瞬态工作流执行环境”。
2.2 为什么不是 SAML?
SAML 协议更重,需要双向元数据交换、XML 签名、时间戳校验等复杂流程。GitHub Actions 官方根本不支持 SAML 断言生成;AWS 虽然支持 SAML 联邦,但要求你部署一个 SAML IdP(如 Okta、AD FS),这等于在 GitHub 和 AWS 之间硬插一层中间件,不仅增加架构复杂度,还引入新的单点故障和延迟。我们曾评估过用 Auth0 做中转,结果发现每次 workflow 触发都要经历 GitHub → Auth0 → AWS 三次网络跳转,平均耗时增加 2.3 秒,且 Auth0 的免费层对 OIDC token 的签发频次有限制,CI 流水线高峰期直接触发限流。SAML 解决的是企业统一登录问题,不是 CI/CD 自动化授权问题。
2.3 为什么 OIDC 是唯一合理解?——基于 JWT 的信任链闭环
OIDC 的精妙之处在于它用一个轻量、标准化、可编程的协议,构建了一个端到端可验证的信任链。整个过程本质是:GitHub Actions 运行器在 job 启动时,向https://token.actions.githubusercontent.com请求一个 JWT(JSON Web Token),这个 JWT 包含了aud(受众,固定为sts.amazonaws.com)、sub(主题,形如repo:myorg/myrepo:ref:refs/heads/main)、iss(发行方)、exp(过期时间,最长 15 分钟)等关键声明。AWS STS 的AssumeRoleWithWebIdentityAPI 在收到这个 JWT 后,会做三件事:第一,根据iss字段,向 GitHub 的 JWKS URI(https://token.actions.githubusercontent.com/.well-known/jwks)拉取当前有效的公钥;第二,用该公钥验证 JWT 签名是否有效;第三,检查aud是否匹配、exp是否未过期、sub是否符合你预设的命名规则(比如只允许repo:myorg/*下的仓库)。整个过程没有共享密钥,没有中间代理,所有验证逻辑都在 AWS STS 内部完成,且 GitHub 的 JWKS 是公开可访问的,AWS 可以随时刷新缓存。这就是为什么 OIDC 是“生产环境标配”——它把信任锚点从“谁持有密钥”转移到了“谁签发了这个不可篡改的声明”,而签名密钥由 GitHub 全权管理,我们只需信任 GitHub 这个发行方本身。这就像你去银行办业务,不再需要随身携带一把物理钥匙(Access Key),而是出示一张由央行(GitHub)实时签发、带防伪码(JWT Signature)和有效期(exp)的电子身份证(OIDC Token),银行(AWS)只要联网验证防伪码真伪,就能放行。
2.4 架构全景图:从 workflow 到 AWS 资源的 7 步信任传递
整个流程不是黑盒,而是清晰可追踪的 7 个步骤,每一步都对应一个可审计、可调试的环节:
- Workflow 触发:用户 push 代码或手动触发 workflow,GitHub 创建一个 runner 实例;
- Token 请求:runner 内置的
id-token机制自动向https://token.actions.githubusercontent.com发起 GET 请求,携带audience=sts.amazonaws.com参数; - JWT 签发:GitHub 验证请求来源(确保是自家 runner),生成一个包含
sub、iss、aud、exp、jti(唯一 ID)等声明的 JWT,并用其私钥签名; - Token 传递:GitHub Actions 将此 JWT 存入 runner 环境变量
ACTIONS_ID_TOKEN_REQUEST_TOKEN和ACTIONS_ID_TOKEN_REQUEST_URL,供后续步骤使用; - AssumeRole 调用:workflow 中的
aws-actions/configure-aws-credentials@v2动作读取该 JWT,调用 AWS STS 的AssumeRoleWithWebIdentityAPI,传入 JWT、角色 ARN、角色会话名称; - STS 验证与签发:AWS STS 根据 JWT 的
iss去 GitHub JWKS 获取公钥,验证签名;检查aud和exp;若sub匹配 IAM 角色的信任策略,则签发一组临时的AccessKeyId、SecretAccessKey和SessionToken; - 资源访问:临时凭证被注入 runner 环境,后续所有 AWS CLI 或 SDK 调用均自动使用这些凭证,权限严格受限于所关联的 IAM Role。
这个链条里,最关键的两个可控点是:JWT 的sub声明格式(决定了你能精确控制哪个仓库、哪个分支、甚至哪个 workflow 可以扮演该角色),以及IAM Role 的信任策略(决定了 AWS STS 接受哪些iss和sub)。其他环节(GitHub 签发、STS 验证)都是托管服务,你无需、也不应干预。这种“声明即策略”的设计,正是云原生安全的精髓——把权限控制前移到身份声明生成那一刻,而不是在资源访问时做粗粒度过滤。
3. 核心细节解析与实操要点:从 IAM 角色创建到 workflow 配置的避坑指南
光知道原理不够,落地时每一步都有深坑。我整理了从零开始配置 OIDC 联邦的完整路径,并标注了所有官方文档不会明说、但实际踩过才懂的关键细节。整个过程分为三大块:AWS 侧准备(IAM 角色与信任策略)、GitHub 侧配置(OIDC 提供商注册)、Workflow 侧集成(凭证注入与使用)。别跳步,顺序错了,90% 的失败都源于此。
3.1 AWS 侧:创建 IAM Role 并编写精准的信任策略
这是整个链条的“终点”和“权限闸门”,必须一次写对。很多教程直接给你一段 JSON,但没告诉你为什么这么写,导致权限过大或过小。
首先,创建一个 IAM Role,类型选Web identity。注意,这里不是“Another AWS account”或“Custom trust policy”,必须明确选择 Web identity,因为只有这个类型才会在控制台里自动出现Provider配置项。
然后,编辑该角色的信任策略(Trust Policy)。这是最核心、最容易出错的部分。一个典型的、安全的策略如下:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringLike": { "token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:ref:refs/heads/main" }, "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" } } } ] }提示:
Principal.Federated的 ARN 中的123456789012是你的 AWS 账户 ID,token.actions.githubusercontent.com是固定的 GitHub OIDC 发行方域名,绝不能写成https://token.actions.githubusercontent.com或其他变体。我见过太多人在这里加https://前缀,导致 STS 拒绝所有请求,错误日志里只显示模糊的InvalidIdentityToken,排查起来极其痛苦。
Condition块是权限收敛的灵魂。StringLike用于匹配sub声明,它支持通配符*,但必须谨慎:
repo:myorg/myrepo:*—— 允许该仓库下所有 ref(branches, tags, pull requests),但不包括其他仓库;repo:myorg/*:ref:refs/heads/main—— 允许 myorg 下所有仓库的 main 分支;repo:myorg/myrepo:pull_request—— 专门用于 PR 检查,权限可以更细(比如只读 S3);- 绝对避免
repo:*:*或*,这等于把整个 AWS 账户的控制权交给了 GitHub。
StringEquals对aud的校验是强制性的,且值必须是sts.amazonaws.com,这是 AWS STS 的服务标识,不能省略或更改。
注意:IAM 角色创建后,不要立即附加权限策略(Permission Policy)。先测试信任链是否打通,再逐步添加最小权限。我习惯先附加一个极简的
ReadOnlyAccess,确认 OIDC 登录成功后,再根据 workflow 实际需求(如s3:GetObject,ecr:GetAuthorizationToken)定制策略。
3.2 GitHub 侧:在仓库或组织级别注册 OIDC 提供商
GitHub 侧的配置极其简单,但位置隐蔽。它不在仓库 Settings 的 Secrets 里,而是在Settings > Environments(环境)中。很多人卡在这一步,因为找不到入口。
- 进入你的 GitHub 仓库,点击
Settings标签页; - 在左侧菜单中找到并点击
Environments; - 点击右上角
New environment,输入一个名字,比如aws-production; - 在新环境页面,向下滚动到
Environment secrets区域,你会看到一个醒目的按钮:Add environment secret; - 点击它,在弹出框中,
Name输入任意字符串(如AWS_ROLE_TO_ASSUME),Value输入你在 AWS 上创建的 IAM Role 的完整 ARN(例如arn:aws:iam::123456789012:role/github-actions-oidc-role); - 最关键一步:在同一个环境页面,找到
Environment protection rules区域,点击Require approval for deployments旁边的Edit,然后勾选Require reviewers,并添加至少一个 reviewer(可以是你自己)。这一步不是为了审批,而是为了激活该环境的 OIDC 功能!GitHub 的机制是:只有启用了保护规则(approval 或 required reviewers)的 Environment,才会在 workflow 运行时,为该环境下的 job 注入id-token。如果没启用,ACTIONS_ID_TOKEN_REQUEST_URL环境变量将为空,后续所有操作都会失败。这个设计非常反直觉,但官方文档里确实写了:“Environments with protection rules enabled will have the ID token available.” 我花了整整一个下午才在 GitHub 社区的一个 issue 评论里找到这条线索。
实操心得:对于多环境(dev/staging/prod),建议为每个环境创建独立的 GitHub Environment,并绑定不同权限等级的 IAM Role。比如
aws-dev环境绑定一个拥有s3:ListBucket权限的 Role,而aws-prod环境绑定一个只允许ecr:BatchGetImage的 Role。这样,即使某个 workflow 文件被误推送到 prod 分支,它也只能拿到 prod 环境定义的、最严格的权限,实现纵深防御。
3.3 Workflow 侧:使用aws-actions/configure-aws-credentials的正确姿势
这是最“看得见摸得着”的一步,但也是最容易因版本或参数错误而失败的环节。官方推荐使用aws-actions/configure-aws-credentials@v2,但v2有很多子版本,行为差异巨大。
一个健壮的 workflow 片段如下:
name: Deploy to AWS on: push: branches: [main] paths: - 'src/**' jobs: deploy: # 必须指定 runs-on 为 ubuntu-latest 或其他支持 OIDC 的 runner runs-on: ubuntu-latest # 必须指定 environment,否则 id-token 不可用 environment: aws-production steps: - name: Checkout code uses: actions/checkout@v4 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: # 这里引用的是 GitHub Environment 中定义的 secret role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} # audience 必须是 sts.amazonaws.com,这是硬编码 role-session-name: github-actions-deploy-${{ github.run_id }} # aws-region 必须显式指定,否则默认 us-east-1,可能导致跨区域调用失败 aws-region: us-west-2关键参数解析:
role-to-assume:必须是 GitHub Environment secrets 中定义的值,不能是 workflow secrets 或 repository secrets。这是因为 OIDC token 的sub声明包含了环境信息,只有 Environment secrets 才能与之匹配。role-session-name:这个字符串会成为 AWS CloudTrail 日志里的userIdentity.sessionContext.sessionIssuer.userName。强烈建议包含github.run_id,这样你可以在 CloudTrail 控制台里,通过搜索run_id,瞬间定位到某次具体部署的所有 AWS API 调用,实现端到端审计。我曾经用这个技巧,在一次生产事故中,5 分钟内就锁定了是哪个 workflow 的哪个 step 调用了错误的 Lambda 函数。aws-region:必须显式指定。configure-aws-credentials动作内部会调用sts:AssumeRoleWithWebIdentity,这个 API 必须在特定区域调用。如果你不指定,它会用默认的us-east-1,而你的 IAM Role 可能在us-west-2,导致InvalidInput错误。这不是 bug,是 AWS STS 的设计约束。
常见误区:有人试图在 workflow 中手动
curlGitHub 的 OIDC endpoint 来获取 token,然后自己调用aws sts assume-role-with-web-identity。这完全没必要,且极易出错。aws-actions/configure-aws-credentials已经封装了所有细节,包括自动处理 token 刷新(虽然 OIDC token 本身只活 15 分钟,但该动作会在必要时重新请求)、环境变量注入、错误重试。你只需要告诉它role-to-assume和aws-region,剩下的交给它。
4. 实操过程与核心环节实现:从零搭建一个可审计的 ECR 镜像推送流水线
理论讲完,现在来一个完整的、可直接复制粘贴的实战案例。我们将构建一个典型的场景:当代码推送到main分支时,自动构建 Docker 镜像,并推送到 AWS ECR 仓库。整个过程不使用任何静态密钥,所有 AWS 访问都通过 OIDC 联邦完成。我会展示每一步的命令、预期输出和关键验证点,让你像在现场跟着我一起操作。
4.1 第一步:在 AWS 控制台创建 ECR 仓库和 IAM Role
打开 AWS 控制台,进入 ECR 服务,点击Create repository。填写:
- Repository name:
myapp-frontend - Repository settings: 勾选
Tag immutability(防止覆盖已有 tag) - 点击
Create repository
记下生成的仓库 URI,格式为123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp-frontend。这个 URI 后面会用到。
接着,进入 IAM 控制台,点击Roles→Create role→Web identity。在Identity provider下拉框中,你应该能看到token.actions.githubusercontent.com(如果没看到,说明你还没在 GitHub Environment 中启用保护规则,回去补上)。选择它,Audience保持默认的sts.amazonaws.com,点击Next: Permissions。
在权限策略页面,不要选择现成的AmazonEC2ContainerRegistryPowerUser。我们要定制最小权限。点击Create policy,切换到JSON标签页,粘贴以下策略:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ecr:GetAuthorizationToken", "ecr:BatchCheckLayerAvailability", "ecr:GetDownloadUrlForLayer", "ecr:GetRepositoryPolicy", "ecr:DescribeRepositories", "ecr:ListImages", "ecr:DescribeImages", "ecr:BatchGetImage" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "ecr:InitiateLayerUpload", "ecr:UploadLayerPart", "ecr:CompleteLayerUpload", "ecr:PutImage", "ecr:SetRepositoryPolicy" ], "Resource": "arn:aws:ecr:us-west-2:123456789012:repository/myapp-frontend" } ] }这个策略精准地分离了“通用 ECR 操作”(如获取授权令牌、列出镜像)和“针对本仓库的写入操作”(如上传 layer、推送镜像)。Resource字段明确指定了仓库 ARN,杜绝了越权风险。策略名填ECR-Push-Policy,点击Create policy。
回到角色创建页面,搜索并附加这个新策略ECR-Push-Policy,然后为角色命名,比如github-actions-ecr-push-role,点击Create role。
4.2 第二步:配置 GitHub Environment 并设置 secrets
进入你的 GitHub 仓库,Settings→Environments→New environment,输入aws-ecr。在环境页面,点击Add environment secret,创建一个名为AWS_ROLE_TO_ASSUME的 secret,值为你刚创建的 IAM Role ARN:arn:aws:iam::123456789012:role/github-actions-ecr-push-role。
然后,在Environment protection rules区域,点击Edit,勾选Require reviewers,添加一个 reviewer(比如your-github-username)。保存。
4.3 第三步:编写并部署 workflow 文件
在你的仓库根目录,创建.github/workflows/ecr-push.yml,内容如下:
name: Push Docker Image to ECR on: push: branches: [main] paths: - 'Dockerfile' - 'src/**' jobs: build-and-push: runs-on: ubuntu-latest environment: aws-ecr permissions: # 关键!必须显式授予 id-token 权限,否则无法获取 JWT id-token: write contents: read steps: - name: Checkout uses: actions/checkout@v4 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v2 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} role-session-name: ecr-push-${{ github.run_id }} aws-region: us-west-2 - name: Login to Amazon ECR # 使用 AWS CLI v2 的 login 命令,它会自动读取 configure-aws-credentials 注入的凭证 id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 with: images: | 123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp-frontend tags: | type=raw,value=latest type=sha,prefix= - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} # 必须指定 registry,否则默认推送到 Docker Hub platforms: linux/amd64,linux/arm64 # 这里引用 login-ecr 步骤的输出,确保使用正确的 ECR registry registry: ${{ steps.login-ecr.outputs.registry }}提示:
permissions块中的id-token: write是强制要求。GitHub Actions 默认关闭了对 OIDC token 的访问,你必须显式声明write权限,才能让configure-aws-credentials动作读取到ACTIONS_ID_TOKEN_REQUEST_*环境变量。如果漏掉这一行,workflow 会卡在Configure AWS credentials步骤,报错Error: Unable to get ID token from GitHub Actions。这个错误信息很误导人,因为它听起来像是 GitHub 的问题,其实是你的 workflow 权限没开。
4.4 第四步:触发、监控与验证
提交这个 workflow 文件到main分支。GitHub 会自动触发一次运行。进入Actions标签页,找到这次运行,点击进入详情页。
重点观察Configure AWS credentials步骤的日志。成功的日志应该包含类似这样的行:
Setting credentials for role arn:aws:iam::123456789012:role/github-actions-ecr-push-role in us-west-2 Assuming role with web identity Retrieved credentials from STS如果看到Error: Error in STS AssumeRoleWithWebIdentity call,那一定是前面某步错了。此时,立刻去 AWS CloudTrail 查看日志。在 CloudTrail 控制台,筛选Event name为AssumeRoleWithWebIdentity,时间范围设为最近 15 分钟。找到失败的事件,点击查看详情,errorMessage字段会给出精确原因,比如InvalidIdentityToken(JWT 无效,通常是sub不匹配)或AccessDenied(IAM Role 信任策略没生效)。
一旦Configure AWS credentials成功,后续的Login to Amazon ECR和Build and push步骤就会顺畅进行。最终,你可以在 ECR 控制台的myapp-frontend仓库里,看到新推送的镜像,tag 为latest和一串 SHA 值。
实操心得:为了快速验证 OIDC 是否生效,我总是在 workflow 开头加一个 debug 步骤:
- name: Debug - Print env vars run: | echo "ACTIONS_ID_TOKEN_REQUEST_URL: $ACTIONS_ID_TOKEN_REQUEST_URL" echo "ACTIONS_ID_TOKEN_REQUEST_TOKEN: ${ACTIONS_ID_TOKEN_REQUEST_TOKEN:0:10}..." echo "AWS_DEFAULT_REGION: $AWS_DEFAULT_REGION"这样一眼就能看到 token URL 是否为空,以及 region 是否正确。比看一堆抽象的错误日志高效得多。
5. 常见问题与排查技巧实录:那些让你抓狂的 5 个错误及其真实解决方案
在上百次 OIDC 配置实践中,我总结出最常遇到、也最让人崩溃的 5 个问题。它们不像语法错误那样一眼能看出来,而是隐藏在配置的犄角旮旯里,需要一套系统性的排查方法论。下面不是罗列错误代码,而是还原真实的排障现场。
5.1 问题一:“Error: Unable to get ID token from GitHub Actions” —— 最常见的幻觉
现象:workflow 卡在Configure AWS credentials步骤,日志第一行就是这个错误。你反复检查permissions: id-token: write,确认无误,甚至重启 runner,依然如此。
真相与排查:这不是 GitHub 的问题,而是你的 workflow 没有被“环境”所包裹。回忆一下,我们强调过,id-token只有在 job 指定了environment且该 environment 启用了保护规则时,才会被注入。请立刻检查你的 workflow YAML:
jobs.<job_id>.environment是否存在?值是否拼写正确(大小写敏感)?- 该 environment 在 GitHub UI 中,
Environment protection rules是否真的处于Enabled状态?有时候你点了Edit,但忘记点Save,状态还是Disabled。
速查表:
| 检查项 | 正确状态 | 错误状态 | 如何验证 |
|---|---|---|---|
environment字段 | environment: aws-prod | 缺失或拼写错误(如enviroment) | 直接看 YAML 文件 |
| Protection rules | Enabled,且Required reviewers有至少一人 | Disabled或No reviewers configured | 进入 GitHubSettings > Environments > your-env-name查看顶部状态栏 |
终极验证:在 workflow 中加一个 debug 步骤,打印env:
- name: Verify ID Token Env run: env | grep ACTIONS_ID_TOKEN如果输出为空,100% 是 environment 配置问题。
5.2 问题二:“Error: Error in STS AssumeRoleWithWebIdentity call” —— 信任策略的无声杀手
现象:Configure AWS credentials步骤开始执行,但几秒后失败,报这个泛泛的错误。CloudTrail 日志里对应的AssumeRoleWithWebIdentity事件,errorCode是InvalidIdentityToken。
真相与排查:InvalidIdentityToken是个“万能错误”,它掩盖了 JWT 验证失败的所有可能原因。你需要像侦探一样,逐层剥开:
- JWT
sub声明是否匹配?这是最常见原因。sub的格式是repo:org/repo:ref:refs/heads/branch或repo:org/repo:pull_request。去 CloudTrail 日志里,找到失败事件的requestParameters字段,里面有一个roleArn和webIdentityToken。复制webIdentityToken(就是那个长 JWT 字符串),用在线 JWT 解码器(如 https://jwt.io)解码。查看sub字段的值。然后,去你的 IAM Role 信任策略里,检查StringLike条件是否能精确匹配这个sub。例如,如果sub是repo:myorg/myrepo:ref:refs/heads/develop,但你的策略写的是repo:myorg/myrepo:ref:refs/heads/main,那就必然失败。 - JWT
aud声明是否正确?解码 JWT,检查aud字段是否为sts.amazonaws.com。如果不是,说明你在configure-aws-credentials动作里错误地设置了audience参数(这个参数是只读的,不应该手动设置)。 - JWT 是否过期?查看
exp字段的时间戳,转换为北京时间,看是否已过期。正常情况下,GitHub 签发的 token 有效期是 15 分钟,足够完成整个 workflow。如果exp是过去的时间,说明你的 runner 系统时间严重不准,需要 NTP 同步。
速查表:
| JWT 声明 | 正确值 | 常见错误 | 修复方式 |
|---|---|---|---|
iss | https://token.actions.githubusercontent.com | https://token.actions.githubusercontent.com/(多了一个/) | 检查 IAM Role 的Principal.FederatedARN |
aud | sts.amazonaws.com | https://sts.amazonaws.com或空 | 删除configure-aws-credentials中的audience参数 |
sub | repo:myorg/myrepo:ref:refs/heads/main | repo:myorg/myrepo:refs/heads/main(缺少ref:) | 修改 IAM Role 信任策略的StringLike值 |
5.3 问题三:“AccessDenied: User: arn:aws:sts::123456789012:assumed-role/... is not authorized to perform: ecr:GetAuthorizationToken” —— 权限策略的迷雾
现象:Configure AWS credentials成功了,但Login to Amazon ECR步骤失败,报AccessDenied,明确指出缺少ecr:GetAuthorizationToken权限。
真相与排查:这说明 OIDC 联邦本身是通的(STS 成功返回了临时凭证),但这些临时凭证所绑定的 IAM Role,没有被赋予足够的权限策略(Permission Policy)。这是一个典型的“信任链通了,但权限闸门关着”的问题。
排查路径:
- 确认权限策略已附加:进入 IAM 控制台,找到你创建的 Role,点击
Permissions标签页,确认ECR-Push-Policy(或你命名的策略)确实在列表中。 - 检查策略语法:在策略 JSON 中,
"Resource": "*"对于GetAuthorizationToken是必需的,因为这个 API 是全局的,不作用于特定资源。如果你的策略里,GetAuthorizationToken这一行的Resource写成了具体的仓库 ARN,它就会失败。 - 检查策略是否被拒绝:IAM 策略支持
Deny语句。检查该 Role 的所有附加策略,是否有任何Deny语句意外地否决了ecr:GetAuthorizationToken。一个Deny会覆盖所有Allow。
速查表:
| 检查点 | 如何验证 | 修复方式 |
|---|---|---|
| 策略是否附加 | IAM Role 页面的Permissions标签页 | 点击Add permissions→Attach existing policies directly,搜索并附加 |
GetAuthorizationToken的 Resource | 策略 JSON 中,该 Action 对应的Resource字段 | 必须是"*",不能是具体 ARN |
| 是否存在 Deny 语句 | 查看 Role 的所有策略,搜索"Effect": "Deny" | 删除或修改冲突的 Deny 语句 |
5.4 问题四:Workflow 成功,但 CloudTrail 里看不到任何AssumeRoleWithWebIdentity事件 —— 审计的盲区
现象:workflow 运行成功,镜像也推上去了,但你去 CloudTrail 查找AssumeRoleWithWebIdentity事件,却一无所获。你怀疑 OIDC 根本没走,是不是还在用别的凭据?
真相与排查:这通常是因为你启用了CloudTrail Lake或者CloudTrail Insights,而默认的 CloudTrail 控制台视图只显示“管理事件”(Management Events),不显示“数据事件”(Data Events)。AssumeRoleWithWebIdentity属于管理事件,但它的日志可能被过滤掉了。
排查路径:
- 在 CloudTrail 控制台,点击左侧
Event history。 2