用 GitHub Actions 做 CI/CD 时,通过 OIDC 直接获取 AWS 临时凭证是官方推荐的做法——省去了管理长期 Access Key 的麻烦。但在实际配置过程中,几个不起眼的小细节就能卡住半天。
问题现象
Terraform CI 流水线的第一步 configure-aws-credentials 反复重试后失败:
1
2
3
4
Assuming role with OIDC
Assuming role with OIDC
...
Error: Could not assume role with OIDC: Not authorized to perform sts:AssumeRoleWithWebIdentity
sts:AssumeRoleWithWebIdentity 被拒绝,说明请求到达了 AWS STS,但 IAM 信任策略不允许这次身份交换。
排查过程
Step 1:信任策略看起来没问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::xxxx:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:*"
},
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}
}
}
从格式上看,OIDC provider ARN 指向 token.actions.githubusercontent.com,aud 设为 sts.amazonaws.com, sub 用了通配符——都是标准配置,看不出问题。
Step 2:角色 ARN 确认正确
检查 GitHub Secrets 中的 AWS_ROLE_ARN,与管理账号中的 IAM Role ARN 一致。
Step 3:OIDC Provider 证书指纹
这里我用了 thumbprint_list = [],想用根 CA 验证代替硬编码指纹。但搜索后发现,GitHub 在 2025–2026 年轮换了证书,旧的指纹可能已经失效。
Step 4:根因——仓库名下划线陷阱
直到检查 git remote URL 才发现:
| 配置 | 值 |
|---|---|
| git remote | git@github.com:my-org/my_repo.git |
terraform.tfvars github_repo | my-org/my-repo |
仓库名是 my_repo(下划线),但信任策略条件写的是 my-org/my-repo:*(横线)。
GitHub OIDC token 的 sub claim 使用实际的仓库名即 my_org/my_repo。信任策略里的横线版本根本不匹配,所以 STS 每次都拒绝。
两个修复
修复一:仓库名对齐
terraform.tfvars 修正为:
1
github_repo = "my-org/my_repo"
修复二:证书指纹动态获取
原来硬编码的 thumbprint_list:
1
thumbprint_list = ["xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]
改为用 data.tls_certificate 自动拉取当前指纹:
1
2
3
4
5
6
7
8
9
data "tls_certificate" "github_oidc" {
url = "https://token.actions.githubusercontent.com"
}
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [data.tls_certificate.github_oidc.certificates[0].sha1_fingerprint]
}
这样当 GitHub 轮换证书时,下次 terraform apply 会自动更新,无需手动修改代码。
修复后的连锁发现
信任策略修好后 CI 有了新进展,但紧接着又遇到了新问题。
Provider Profile 问题
主 provider 配置了 profile = "my-profile"——这在本地开发时用于指定 AWS Profile,但 CI 环境中不存在这个命名配置文件,导致 plan 失败。
解法:CI 的 terraform plan 命令追加 -var="aws_profile=",将 profile 覆盖为空,让 CI 直接使用 OIDC 获取的凭证:
1
2
- name: Terraform Plan
run: terraform plan -var="aws_profile=" -out=tfplan
github-script 中的 steps 引用问题
CI 中有一段脚本需要判断 terraform plan 的结果是否有变更:
1
2
3
4
5
6
7
8
9
10
11
12
13
- name: Check Plan Changes
id: plan
run: terraform plan -no-color -out=tfplan
- name: Comment Plan
uses: actions/github-script@v7
env:
PLAN_OUTPUT: $
PLAN_OUTCOME: $
with:
script: |
const output = process.env.PLAN_OUTPUT;
const outcome = process.env.PLAN_OUTCOME;
关键在于把 steps.plan.outcome 通过 env 传进 github-script,而不是在 JavaScript 里直接引用 steps——actions/github-script 的 v7 运行时中并没有 steps 这个上下文对象。
经验总结
| 问题 | 原因 | 教训 |
|---|---|---|
| OIDC 鉴权失败 | 仓库名下划线/横线不匹配 | OIDC token 的 sub claim 使用实际仓库名,注意跟 git remote URL 对齐 |
| 证书指纹失效 | GitHub 轮换证书未同步 | 用 data.tls_certificate 动态获取,不要硬编码 |
| CI plan 失败 | Provider 配置了本地 Profile | CI 环境通过 -var 覆盖无关的变量 |
| github-script 报错 | 直接引用 steps | 通过 env 传入,避免直接访问运行时上下文 |
GitHub Actions OIDC 的官方推荐配置一直在更新——截止 2026 年,最佳实践是使用 data.tls_certificate 动态获取指纹,并确保 IAM Role 的信任策略中的 sub 条件与 GitHub 仓库名精确一致。如果未来 GitHub 再次轮换证书,动态获取的方式可以零改动继续工作。