1. 项目概述:为什么用 OIDC 连接 GitHub Actions 和 AWS 不再是“高级玩法”,而是默认安全实践?
最近三个月,我帮六家不同规模的团队重构 CI/CD 权限体系,其中五家都卡在同一个问题上:AWS 访问密钥(Access Key)硬编码进 GitHub Secrets,导致每次轮换密钥都要改 workflow、重发通知、手动验证权限、半夜被告警叫醒——最夸张的一次,某 SaaS 公司因密钥泄露被扫描出未授权 S3 列表操作,安全团队直接叫停所有部署流水线 48 小时。而真正让我下定决心写这篇实操笔记的,是上周一位刚转岗 DevOps 的朋友发来截图:他把 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY 直接写进 .github/workflows/deploy.yml 的 env 字段里,还加了注释“临时用一下,下周就改”。这根本不是“临时”,这是把生产环境的门钥匙焊死在玻璃门上,还贴了张“欢迎光临”的纸条。
这就是为什么今天我们要聊Connect GitHub Actions and AWS using OIDC——它不是又一个炫技的新功能,而是解决密钥管理顽疾的外科手术刀。OIDC(OpenID Connect)在这里扮演的是“可信身份中介”:GitHub Actions 运行器不再需要长期有效的 AWS 凭据,而是在每次 job 启动时,向 GitHub 的 OIDC 提供商申请一个短时效、作用域精确、可审计的 JWT(JSON Web Token),再把这个 token 拿去 AWS STS(Security Token Service)兑换成临时凭证。整个过程不经过任何人工环节,不落地密钥,不依赖 Secrets 管理策略的执行力度,甚至不需要 AWS IAM 用户——只靠角色(IAM Role)和信任策略(Trust Policy)就能完成授权闭环。
核心关键词“GitHub Actions”“AWS”“OIDC”不是并列关系,而是因果链:因为 GitHub Actions 原生支持 OIDC 身份令牌(2021 年底上线),所以 AWS 能通过sts:AssumeRoleWithWebIdentity接收并校验该令牌;因为 AWS 支持基于 OIDC 提供商的动态角色假设,所以 GitHub 工作流能按需获取最小权限凭证;最终结果是:你删掉仓库里所有 AWS_* 类 Secrets,CI 流水线依然能推镜像、更新 Lambda、同步 CloudFormation 堆栈,且每次凭证有效期默认 15 分钟(可配),过期即废,无法复用。适合谁?所有正在用 GitHub Actions 部署 AWS 资源的团队,无论你是用 Terraform 管 IaC、用 CDK 写基础设施、还是用 shell 脚本调 AWS CLI——只要你的 workflow 中出现过aws configure或export AWS_ACCESS_KEY_ID,这篇就是为你写的。它不教你怎么写 YAML,而是帮你把权限这根绷紧的弦,换成一根有弹性的橡皮筋。
2. 整体设计思路与方案选型:为什么不用 IAM 用户 + Secrets?为什么非得走 OIDC 这条“绕路”?
很多人第一反应是:“我用 Secrets 存密钥,定期轮换,加个审批流程,不也挺安全?”——这话在 2018 年成立,但在 2024 年,它暴露的是对现代云原生权限模型的理解断层。我们来拆解三种主流方案的底层逻辑和真实代价:
2.1 方案对比:IAM 用户密钥 vs GitHub OIDC + IAM 角色 vs 自建 OIDC 中继
| 维度 | IAM 用户密钥(传统方式) | GitHub OIDC + IAM 角色(本文方案) | 自建 OIDC 中继(如 HashiCorp Vault OIDC) |
|---|---|---|---|
| 凭证生命周期 | 长期有效(默认无过期,人为轮换) | 每次 job 动态生成,JWT 默认 15 分钟,STS 临时凭证默认 1 小时 | 可控,但需额外维护中继服务健康度与证书续期 |
| 权限粒度 | 绑定到 IAM 用户,难以按 workflow/job 细分 | 可为每个仓库、分支、甚至 job 名称设置独立 IAM 角色,信任策略中用sub字段精准匹配 | 粒度取决于中继配置,通常不如 GitHub 原生细 |
| 泄露影响面 | 单密钥泄露 = 该用户所有权限永久暴露 | JWT 泄露仅影响单次 job(15 分钟内),STS 凭证泄露仅影响单次 assume(1 小时内),且无法跨 job 复用 | 影响面取决于中继策略,但引入新攻击面(中继本身) |
| 运维复杂度 | 表面简单,实则轮换成本高:改 Secrets → 改 workflow → 测试 → 通知 → 回滚预案 | 初始配置稍重(需建角色、写信任策略、配 workflow),后续零维护 | 需部署、监控、升级、备份中继服务,DevOps 成本翻倍 |
| 审计能力 | CloudTrail 日志中显示为固定 IAM 用户,无法区分哪个 workflow 触发 | CloudTrail 明确记录AssumeRoleWithWebIdentity调用,subject字段含repo:org/repo:ref:refs/heads/main,可直接关联到具体代码提交 | 审计日志分散在中继服务与 AWS 两端,关联分析成本高 |
提示:别被“OIDC 听起来很重”吓退。GitHub 的 OIDC 是开箱即用的——你不需要部署 Keycloak,不需要配置 OAuth2 流程,不需要管理证书。GitHub 就是那个现成的、受信的 OIDC 提供商(Issuer),它的
https://token.actions.githubusercontent.com地址和 JWKS 端点(https://token.actions.githubusercontent.com/.well-known/jwks)已预置在 AWS STS 白名单中。你唯一要做的,是告诉 AWS:“我信任这个 Issuer,并允许它代表特定 repo 去扮演我的某个角色”。
2.2 为什么必须用 IAM 角色而非 IAM 用户?
关键在于sts:AssumeRoleWithWebIdentity这个 API 的设计哲学。它要求调用方提供三个要素:
- OIDC Token(由 GitHub 签发,含
iss、sub、aud等声明); - Role ARN(你要扮演的 AWS 角色);
- Role Session Name(会话名,用于 CloudTrail 标识)。
AWS STS 收到请求后,会做三件事:
- 用 GitHub 的公钥(从 JWKS 获取)验证 token 签名是否有效;
- 检查 token 的
iss是否为https://token.actions.githubusercontent.com(硬编码白名单); - 核对角色的信任策略(Trust Policy)中是否明确允许该
iss和sub组合。
而 IAM 用户没有“被扮演”的概念——它本身就是凭证主体。你无法让一个 IAM 用户去“假设”另一个 IAM 用户的身份,这违反了 AWS 的权限隔离原则。角色才是 AWS 授权模型的基石:它不拥有长期凭证,只定义“谁能以什么条件来扮演我”。把 GitHub Actions 当作一个动态的、一次性的“服务主体”,让它通过 OIDC 证明自己身份,再由角色授予最小权限,这才是云原生权限的正解。
2.3 信任策略(Trust Policy)的设计逻辑:sub字段是权限的“DNA”
很多人卡在信任策略写不对,根本原因是没理解sub(Subject)字段的生成规则。它不是随便写的字符串,而是 GitHub 动态拼接的、不可伪造的标识符。格式为:
repo:ORG_NAME/REPO_NAME:ref:refs/heads/BRANCH_NAME例如,你的仓库是acme-inc/frontend-app,workflow 在main分支触发,则sub为:
repo:acme-inc/frontend-app:ref:refs/heads/main注意:
ORG_NAME是 GitHub 组织名或用户名,区分大小写(GitHub API 返回全小写,但策略中必须严格匹配);REPO_NAME同理,不能写成Frontend-App;ref:refs/heads/main中的refs/heads/是固定前缀,不能省略或改成branch:main;- 如果你想让 PR 构建也能用同一角色,需额外添加
repo:acme-inc/frontend-app:pull_request; - 若想限制只允许
main和staging分支,策略中要写两条StringLike条件,而非用通配符*(AWS 不支持sub的通配符匹配,只能精确或StringLike)。
这就是为什么我在实操中坚持手写策略而非用 AWS 控制台向导——向导会帮你填sub为*,看似省事,实则开了个天窗:任何人的任意仓库,只要知道你的角色 ARN,都能伪造一个sub: *的 token(虽然技术上不可能,但策略层面等于没设防)。
3. 核心细节解析与实操要点:从 AWS 角色创建到 GitHub workflow 配置的每一步深挖
现在进入真正的“抄作业”环节。我会把每个步骤背后的原理、常见错误、以及我踩过的坑,掰开揉碎讲清楚。这不是配置清单,而是带你理解每一行代码在做什么。
3.1 AWS 侧:创建 IAM 角色并配置信任策略(Trust Policy)
第一步永远是 AWS 控制台或 CLI 创建角色。重点不是“怎么点”,而是为什么这样点。
操作路径(控制台):
IAM 控制台 → 角色 → 创建角色 → “Web 身份” → “提供商”选https://token.actions.githubusercontent.com→ “受众”填sts.amazonaws.com(这是 AWS STS 的标准 audience,别写错)→ 下一步 → 附加权限策略(如AmazonEC2FullAccess,先给足权限测试,后续再收紧)→ 角色名填github-actions-deploy-role→ 创建。
注意:很多教程跳过“受众(Audience)”这步,直接下一步。这是大忌。
aud是 JWT 的核心声明之一,AWS STS 会严格校验传入的 token 中aud字段是否等于你在此处填写的值。如果填错(比如填成https://github.com),STS 会直接返回InvalidIdentityToken错误,且 CloudTrail 日志里只显示“拒绝”,不提示具体原因。
关键动作:编辑信任策略(Trust Policy)
创建完角色后,立刻点击“编辑信任策略”。默认策略长这样(别直接用!):
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" } } } ] }你需要追加Condition块,精准锁定仓库和分支。完整策略如下(以acme-inc/frontend-app的main分支为例):
{ "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" }, "StringLike": { "token.actions.githubusercontent.com:sub": "repo:acme-inc/frontend-app:ref:refs/heads/main" } } } ] }为什么用StringLike而非StringEquals?
因为sub字段在实际 token 中可能包含额外声明(如job_id、run_id),AWS 的 OIDC 实现允许StringLike进行前缀匹配。StringEquals要求完全一致,一旦 GitHub 更新 token 结构,你的策略就失效。StringLike更健壮,且符合 AWS 官方文档推荐写法。
实操心得:
- 我第一次配置时,把
sub写成repo:acme-inc/frontend-app:main,漏了ref:refs/heads/,结果 STS 返回Not authorized to perform sts:AssumeRoleWithWebIdentity。CloudTrail 日志里errorMessage字段是空的,只能靠反复比对文档。 - 如果你用 Terraform 管理 IAM,务必用
dynamic块生成多分支策略。例如,同时支持main和staging:dynamic "statement" { for_each = ["main", "staging"] content { effect = "Allow" principals { type = "Federated" identifiers = ["arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"] } actions = ["sts:AssumeRoleWithWebIdentity"] condition { test = "StringLike" variable = "token.actions.githubusercontent.com:sub" values = ["repo:acme-inc/frontend-app:ref:refs/heads/${statement.value}"] } condition { test = "StringEquals" variable = "token.actions.githubusercontent.com:aud" values = ["sts.amazonaws.com"] } } }
3.2 GitHub 侧:在仓库中启用 OIDC 并配置 workflow
GitHub 侧配置极简,但有两个隐藏开关必须打开。
第一步:确认组织/仓库级 OIDC 设置
- 组织管理员需进入
Settings → Security → Code security and analysis → GitHub Actions → General,确保 “Allow GitHub Actions to create OIDC tokens for AWS” 已勾选(默认开启,但有些老组织可能关闭)。 - 仓库 Maintainer 需进入
Settings → Actions → General → Workflow permissions,将权限从 “Read and write permissions” 改为 “Read repository contents permission” + “Allow specified actions”(更安全),并务必勾选 “Allow GitHub Actions to create OIDC tokens”。这是最关键的一步!如果没勾,workflow 中id-token: write会静默失败,job 日志里只显示Error: Unable to get ID token, make sure you have the required permissions,没有任何上下文。
第二步:编写 workflow 文件(.github/workflows/deploy.yml)
核心是三处改动:
- 在
permissions中显式声明id-token: write; - 在
env中注入AWS_ROLE_ARN和AWS_WEB_IDENTITY_TOKEN_FILE; - 使用
aws-actions/configure-aws-credentials@v2Action 加载凭证。
完整示例:
name: Deploy to AWS on: push: branches: [main] # 关键!必须声明 id-token 权限 permissions: id-token: write contents: read jobs: deploy: runs-on: ubuntu-latest steps: # Step 1: Checkout code - uses: actions/checkout@v4 # Step 2: Configure AWS credentials using OIDC - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v2 with: role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy-role aws-region: us-east-1 # Step 3: Now you can use AWS CLI freely - name: List S3 buckets run: aws s3 ls # Step 4: Deploy CloudFormation stack - name: Deploy Stack run: | aws cloudformation deploy \ --template-file template.yaml \ --stack-name frontend-app-prod \ --capabilities CAPABILITY_IAM为什么permissions必须单独写?
GitHub Actions 的权限模型是“最小化默认”。id-token: write不是继承自contents: read,它是一个独立的、高危的权限(因为 token 可用于扮演角色),必须显式声明。如果你省略这一行,configure-aws-credentialsAction 会因拿不到 token 而报错,且错误信息极其模糊。
configure-aws-credentials@v2的工作原理:
这个 Action 并非黑盒。它内部做了三件事:
- 读取 GitHub 提供的环境变量
ACTIONS_ID_TOKEN_REQUEST_URL和ACTIONS_ID_TOKEN_REQUEST_TOKEN; - 向
ACTIONS_ID_TOKEN_REQUEST_URL发起 GET 请求,获取 JWT(存入/tmp/...临时文件); - 设置环境变量
AWS_WEB_IDENTITY_TOKEN_FILE=/tmp/...和AWS_ROLE_ARN=...,并调用aws sts assume-role-with-web-identity换取临时凭证,最后将凭证写入~/.aws/credentials。
这意味着:你不需要在 workflow 中手动curl或jq解析 token,configure-aws-credentials已封装全部逻辑。但你必须确保它运行在ubuntu-latest(或其他支持aws-cli的 runner)上,且aws-cli版本 ≥ 2.0(v1 不支持 web identity)。
3.3 权限收紧实战:从“FullAccess”到“JustEnough”
初始配置用AmazonEC2FullAccess是为了快速验证流程,但上线前必须收紧。这里分享我总结的“最小权限四步法”:
第一步:跑一次完整 workflow,收集 CloudTrail 日志
在 AWS CloudTrail 控制台,筛选Event source = sts.amazonaws.com且Event name = AssumeRoleWithWebIdentity的事件,找到你的角色调用记录。点击详情,展开requestParameters,你会看到roleSessionName(通常是 GitHub job ID)。再用这个 session name 去查所有eventSource = ec2.amazonaws.com的事件,导出 CSV。
第二步:用 AWS IAM Access Analyzer 分析权限
将导出的 CloudTrail 日志上传到 S3,然后在 IAM 控制台 → Access Analyzer → “Analyze activity” → 选择 S3 日志桶 → 运行分析。Analyzer 会生成一份报告,列出所有被调用的 EC2 API(如RunInstances,CreateTags,DescribeImages),并标注哪些权限是“未使用”的。
第三步:手写最小策略(不要用“Policy Generator”)
根据 Analyzer 报告,编写只包含必要 API 的策略。例如,如果你只启动 EC2 实例并打标签,策略应为:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ec2:RunInstances", "ec2:CreateTags", "ec2:DescribeImages", "ec2:DescribeSubnets", "ec2:DescribeSecurityGroups" ], "Resource": "*" } ] }注意:Resource设为*是因为RunInstances需要创建新实例(ARN 未知),但CreateTags必须限制到特定实例前缀,可用Condition实现:
"Condition": { "StringLike": { "ec2:ResourceTag/Project": "frontend-app-*" } }第四步:用iam:SimulatePrincipalPolicy验证
在 IAM 控制台 → 角色 → “模拟角色策略”,输入你要测试的 API(如ec2:RunInstances)和资源 ARN(如arn:aws:ec2:us-east-1:123456789012:instance/*),运行模拟。绿色 ✅ 才算通过。我曾因漏掉DescribeSubnets导致 VPC 部署失败,错误日志只显示InvalidSubnetID.NotFound,根本看不出是权限问题。
4. 实操过程与核心环节实现:一次完整的端到端部署演示(含参数计算与现场记录)
现在,我们用一个真实场景走一遍:将一个 Node.js 应用打包成 Docker 镜像,推送到 ECR,再用 ECS Fargate 启动任务。全程不碰任何 AWS 密钥。
4.1 环境准备:账号、仓库、基础架构
- AWS 账号:主账号
123456789012,区域us-east-1; - GitHub 仓库:
acme-inc/frontend-app,已启用 OIDC(见 3.2); - ECS 集群:已存在
frontend-cluster,VPC 和子网已就绪; - ECR 仓库:已创建
frontend-app,策略已允许ecr:GetAuthorizationToken(此权限需在角色策略中显式添加,因为configure-aws-credentials不自动处理 ECR 登录)。
提示:
ecr:GetAuthorizationToken是 ECR 的特殊 API,它返回的 token 用于docker login,且该 token 本身有 12 小时有效期。但它不依赖 OIDC,而是由configure-aws-credentials换取的临时凭证直接调用。所以你只需在角色策略中加上:{ "Effect": "Allow", "Action": "ecr:GetAuthorizationToken", "Resource": "*" }
4.2 workflow 编写:分阶段详解每行命令意图
name: Build and Deploy to ECS on: push: branches: [main] paths: - 'src/**' - 'Dockerfile' - 'ecs-task-definition.json' permissions: id-token: write contents: read jobs: build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # Step 1: Configure AWS credentials (OIDC flow) - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v2 with: role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecs-deploy-role aws-region: us-east-1 # Step 2: Login to Amazon ECR - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 # Step 3: Extract metadata (sha, tags) for Docker - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 with: images: 123456789012.dkr.ecr.us-east-1.amazonaws.com/frontend-app # Step 4: Build, tag, and push Docker image to ECR - name: Build and push uses: docker/build-push-action@v4 with: context: . push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} # Step 5: Deploy to ECS - name: Deploy to ECS uses: aws-actions/amazon-ecs-deploy-task-definition@v1 with: task-definition: ecs-task-definition.json service: frontend-service cluster: frontend-cluster wait-for-service-stability: trueStep-by-step 意图解析:
- Step 1(Configure AWS Credentials):触发 OIDC 流程,获取临时凭证。此时
~/.aws/credentials已写入,后续所有aws命令均可直接使用。 - Step 2(Login to Amazon ECR):
amazon-ecr-login@v1Action 内部调用aws ecr get-login-password,并将输出传给docker login。它依赖 Step 1 的凭证,所以顺序不能错。 - Step 3(Extract metadata):
docker/metadata-action自动生成镜像标签,如sha-abc123、latest。它不接触 AWS,纯本地计算。 - Step 4(Build and push):
docker/build-push-action使用 Step 2 的docker login凭据,将镜像推送到 ECR。注意:push: true表示推送到远程 registry,而非仅构建。 - Step 5(Deploy to ECS):
amazon-ecs-deploy-task-definition@v1读取ecs-task-definition.json,更新其中的image字段为新推送的 ECR URI,然后调用ecs register-task-definition和ecs update-service。它同样依赖 Step 1 的凭证。
参数计算实录:
- ECR URI 格式:
<ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com/<REPO_NAME>。我第一次写成<REGION>.ecr.amazonaws.com/<REPO_NAME>,导致docker push报错unauthorized: authentication required。 ecs-task-definition.json中的image字段必须是占位符,如"image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/frontend-app:${IMAGE_TAG}",amazon-ecs-deploy-task-definition会自动替换${IMAGE_TAG}为实际 tag。wait-for-service-stability: true是关键开关。它会让 Action 轮询describe-services,直到所有任务处于RUNNING状态且健康检查通过。否则,job 可能提前结束,而 ECS 任务还在启动中。
4.3 一次失败的调试记录:如何从 CloudTrail 日志定位AssumeRoleWithWebIdentity失败
上周,一个客户的 workflow 卡在Configure AWS Credentials步骤,日志只显示:
Error: Error in AssumeRoleWithWebIdentity call: Not authorized to perform sts:AssumeRoleWithWebIdentity我立刻登录 AWS CloudTrail,筛选:
Event source:sts.amazonaws.comEvent name:AssumeRoleWithWebIdentityTime range: 最近 15 分钟
找到失败事件,点击“View event”。关键字段:
errorCode:AccessDeniederrorMessage: (空)requestParameters:{ "roleArn": "arn:aws:iam::123456789012:role/github-actions-ecs-deploy-role", "roleSessionName": "github-workflow-1234567890", "webIdentityToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..." }userIdentity:{ "type": "FederatedUser", "principalId": "https://token.actions.githubusercontent.com:repo:acme-inc/frontend-app:ref:refs/heads/main" }
问题来了:principalId显示的是https://token.actions.githubusercontent.com:...,但信任策略中Principal.Federated是arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com。两者不匹配!
根因:客户在创建 OIDC 提供商时,ARN 写成了arn:aws:iam::123456789012:oidc-provider/https://token.actions.githubusercontent.com(多了https://)。AWS 要求 ARN 中的 provider 名称必须是域名部分,即token.actions.githubusercontent.com,不能带协议头。
修复:删除旧 OIDC 提供商,重新创建,ARN 填token.actions.githubusercontent.com。5 分钟后,workflow 恢复正常。
实操心得:CloudTrail 是你的第一道防线。当 OIDC 报错时,永远先看
AssumeRoleWithWebIdentity事件,而不是在 GitHub 日志里猜。principalId字段会告诉你 GitHub 实际声称的身份,requestParameters.roleArn告诉你它想扮演谁,两者对比就能定位信任策略问题。
5. 常见问题与排查技巧实录:整理成速查表,附独家避坑技巧
以下是我在 6 个客户现场、32 次 OIDC 配置中,高频遇到的 12 个问题及解决方案。按发生概率排序,附真实错误日志和修复命令。
5.1 常见问题速查表
| 问题现象 | 错误日志片段 | 根本原因 | 修复方案 | 验证方法 |
|---|---|---|---|---|
1.Unable to get ID token | Error: Unable to get ID token, make sure you have the required permissions | GitHub 仓库未开启id-token: write权限 | 进入Settings → Actions → General → Workflow permissions,勾选 “Allow GitHub Actions to create OIDC tokens” | 在 workflow 中加 step:run: echo $ACTIONS_ID_TOKEN_REQUEST_URL,应输出非空 URL |
2.Not authorized to perform sts:AssumeRoleWithWebIdentity | Error: Error in AssumeRoleWithWebIdentity call: Not authorized to perform sts:AssumeRoleWithWebIdentity | AWS 角色信任策略中Principal.FederatedARN 错误(如多写了https://)或sub字段不匹配 | 检查 OIDC 提供商 ARN 是否为token.actions.githubusercontent.com;用jq -r '.sub' <(echo $TOKEN)解析实际sub值,对比策略 | CloudTrail 中principalId字段应与策略中sub完全匹配 |
3.InvalidIdentityToken | Error: InvalidIdentityToken: The web identity token that was passed could not be validated. | aud字段不匹配(GitHub token 的aud是sts.amazonaws.com,但策略中写了其他值) | 编辑信任策略,确保Condition.StringEquals."token.actions.githubusercontent.com:aud"等于"sts.amazonaws.com" | 用jwt.io解析 token,查看aud声明 |
4.AccessDeniedon ECR push | denied: User: arn:aws:sts::123456789012:assumed-role/... is not authorized to perform: ecr:GetAuthorizationToken | 角色策略未包含ecr:GetAuthorizationToken权限 | 在角色策略中添加"Action": "ecr:GetAuthorizationToken", "Resource": "*" | 运行aws ecr get-login-password --region us-east-1,应返回密码字符串 |
5.InvalidSubnetID.NotFoundduring ECS deploy | An error occurred (InvalidSubnetID.NotFound) when calling the RegisterTaskDefinition operation | 角色缺少ec2:DescribeSubnets权限 | 添加"ec2:DescribeSubnets"到角色策略 | 运行aws ec2 describe-subnets --region us-east-1 --filters "Name=vpc-id,Values=vpc-xxx" |
6.NoCredentialProvidersin custom script | error: NoCredentialProviders: no valid providers in chain | 自定义脚本未使用~/.aws/credentials,或AWS_PROFILE未设置 | 在脚本开头加export AWS_PROFILE=default;或直接调用aws --profile default ... | aws sts get-caller-identity --profile default应返回角色 ARN |
5.2 独家避坑技巧(来自血泪教训)
技巧 1:用aws sts get-caller-identity在 workflow 中实时验证凭证
在Configure AWS Credentials步骤后,立即加一个 debug step:
- name: Debug - Check assumed role run: | echo "Current identity:" aws sts get-caller-identity --query 'Arn' --output text echo "Available regions:" aws ec2 describe-regions --query 'Regions[].RegionName' --output table这能第一时间确认:
- 凭证是否成功加载(
get-caller-identity不报错); - 角色是否正确扮演(ARN 应为
arn:aws:sts::ACCOUNT:assumed-role/ROLE_NAME/SESSION_NAME); - 区域是否配置正确(
describe-regions列出的 region 应包含你的目标 region)。
技巧 2:为不同环境创建独立角色,用sub字段天然隔离
不要用一个角色服务所有分支。为main、staging、feature/*创建三个角色,信任策略中sub分别为:
main:"repo:acme-inc/frontend-app:ref:refs/heads/main"staging:"repo:acme-inc/frontend-app:ref:refs/heads/staging"feature/*:"repo:acme-inc/frontend-app:ref:refs/heads/feature/*"(注意:StringLike支持*通配符)
这样,feature分支的 workflow 即使被恶意篡改,也无法部署到 production 环境,因为它的sub不匹配main角色的信任策略。
技巧 3:用aws-actions/configure-aws-credentials@v2的role-duration-seconds参数控制凭证有效期
默认 STS 凭证有效期是 1 小时,但你的 workflow 可能 5 分钟就结束了。缩短有效期能进一步降低风险:
- name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v2 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }} aws-region: us-east-1 role-duration-seconds: 900 # 15 minutesrole-duration-seconds必须 ≤ 角色的MaxSessionDuration(默认