关于 github-actions-oidc-aws
面向使用 GitHub Actions 部署到 AWS 的团队,介绍以 OIDC 扮演 IAM 角色、替代长期凭证的参考方案。内容涵盖配置、仓库范围限制、权限设计、部署模式与故障排查。
主要功能
- 01通过 OIDC 获取临时 AWS 凭证
- 02按仓库和分支限制信任策略
- 03遵循最小权限原则设置 IAM
- 04提供 CDK、Terraform、CLI 和 CloudFormation 配置
- 05涵盖部署模式与故障排查
适用场景
- 01部署前为 GitHub Actions 配置 AWS 身份验证
- 02将 AWS 角色限制到指定仓库或分支
- 03替换存储的 AWS 访问密钥
- 04排查 OIDC 错误与角色权限问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ loxosceles-dev/ dev-skills --skill github-actions-oidc-aws安装后在 Claude Code 中输入/github-actions-oidc-aws确认已加载。
详细介绍
用于 AWS 的 GitHub Actions OIDC 身份验证
这是一个参考模式。 从这种方法中汲取经验,并根据你的情况进行调整——不要照搬。
状态:🔴 关键模式
类别:CI/CD / 基础设施
适用范围:任何使用 GitHub Actions 部署到 AWS 的项目
概述
一种安全的身份验证模式,可让 GitHub Actions 工作流使用 OpenID Connect (OIDC) 访问 AWS 资源,而不是使用长期有效的 IAM 凭证。这样就无需在 GitHub secrets 中存储 AWS 访问密钥。
主要优势:
- 无需轮换或担心泄露长期有效的凭证
- 凭证会自动过期
- 基于存储库的访问控制
- 通过 AWS CloudTrail 留下审计记录
- 符合行业最佳实践(由 AWS 和 GitHub 推荐)
问题
GitHub Actions 工作流需要通过身份验证访问 AWS,才能部署基础设施、触发流水线或管理资源。传统方法存在安全问题:
反模式:静态 IAM 凭证
# ❌ Security risk: long-lived credentials in secrets
- uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
问题:
- 凭证永不过期(必须手动轮换)
- 如果泄露,攻击者就能持续访问
- 无法将访问范围限定到特定存储库
- 难以审计哪个工作流使用了凭证
- 违反最小权限原则
解决方案
使用 GitHub 的 OIDC 提供商,通过 AWS IAM 角色承担签发临时凭证。
流程:
GitHub Actions → OIDC Token → AWS STS → Temporary Credentials → AWS Resources
工作原理:
- GitHub Actions 向 GitHub 请求 OIDC 令牌
- 工作流将令牌提交给 AWS STS
- AWS 根据 IAM OIDC 提供商验证令牌
- AWS 签发临时凭证(有效期为 1 小时)
- 工作流使用临时凭证访问 AWS
组件
1. AWS IAM OIDC 提供商
在 AWS 与 GitHub 的 OIDC 颁发者之间建立信任关系。
配置:
- URL:
https://token.actions.githubusercontent.com - 受众:
sts.amazonaws.com - 指纹:
1c58a3a8518e8759bf075b76b750d4f2df264fcd(GitHub 根 CA)
为什么使用根 CA 指纹?
- 比中间证书更稳定
- GitHub 可以轮换中间证书,而不会破坏信任关系
- AWS 文档推荐此做法
2. 带有信任策略的 IAM 角色
GitHub Actions 可以承担的角色,其信任策略用于限制访问范围。
信任策略结构:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::{account}: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:{owner}/{repo}:*"
}
}
}]
}
信任策略范围限定选项:
# All branches and tags
"repo:owner/repo:*"
# Specific branch only
"repo:owner/repo:ref:refs/heads/main"
# Multiple branches
["repo:owner/repo:ref:refs/heads/main", "repo:owner/repo:ref:refs/heads/dev"]
# Pull requests
"repo:owner/repo:pull_request"
# Environment-specific
"repo:owner/repo:environment:production"
3. IAM 权限策略
定义该角色在 AWS 中可以执行的操作(遵循最小权限原则)。
按使用场景划分的示例:
仅触发流水线:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "codepipeline:StartPipelineExecution",
"Resource": "arn:aws:codepipeline:*:{account}:pipeline-name-*"
}]
}
直接部署到 S3:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:DeleteObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::bucket-name", "arn:aws:s3:::bucket-name/*"]
},
{
"Effect": "Allow",
"Action": "cloudfront:CreateInvalidation",
"Resource": "*"
}
]
}
基础设施部署(请谨慎使用):
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}]
}
4. GitHub Actions 工作流配置
工作流必须请求 OIDC 令牌并承担角色。
所需权限:
permissions:
id-token: write # Required for OIDC token
contents: read # Required to checkout code
身份验证步骤:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::{account}:role/{role-name}
aws-region: {region}
实施
步骤 1:创建 OIDC 提供商(一次性设置)
选择你的基础设施工具:
AWS CDK(TypeScript):
import * as iam from 'aws-cdk-lib/aws-iam';
const provider = new iam.OpenIdConnectProvider(this, 'GitHubProvider', {
url: 'https://token.actions.githubusercontent.com',
clientIds: ['sts.amazonaws.com'],
thumbprints: ['1c58a3a8518e8759bf075b76b750d4f2df264fcd']
});
Terraform:
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = ["1c58a3a8518e8759bf075b76b750d4f2df264fcd"]
}
AWS CLI:
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 1c58a3a8518e8759bf075b76b750d4f2df264fcd
CloudFormation:
GitHubOIDCProvider:
Type: AWS::IAM::OIDCProvider
Properties:
Url: https://token.actions.githubusercontent.com
ClientIdList:
- sts.amazonaws.com
ThumbprintList:
- 1c58a3a8518e8759bf075b76b750d4f2df264fcd
步骤 2:创建 IAM 角色
AWS CDK(TypeScript):
const role = new iam.Role(this, 'GitHubActionsRole', {
roleName: 'GitHubActionsRole',
assumedBy: new iam.WebIdentityPrincipal(provider.openIdConnectProviderArn, {
StringEquals: {
'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com'
},
StringLike: {
'token.actions.githubusercontent.com:sub': `repo:${owner}/${repo}:*`
}
}),
inlinePolicies: {
DeploymentPermissions: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['codepipeline:StartPipelineExecution'],
resources: [`arn:aws:codepipeline:*:${this.account}:pipeline-*`]
})
]
})
}
});
Terraform:
resource "aws_iam_role" "github_actions" {
name = "GitHubActionsRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = {
Federated = aws_iam_openid_connect_provider.github.arn
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:${var.github_owner}/${var.github_repo}:*"
}
}
}]
})
}
resource "aws_iam_role_policy" "github_actions" {
name = "deployment-permissions"
role = aws_iam_role.github_actions.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "codepipeline:StartPipelineExecution"
Resource = "arn:aws:codepipeline:*:${data.aws_caller_identity.current.account_id}:pipeline-*"
}]
})
}
步骤 3:更新 GitHub Actions 工作流
之前(静态凭证):
name: Deploy
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- run: aws s3 sync ./dist s3://my-bucket
之后(OIDC):
name: Deploy
on: [push]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: us-east-1
- run: aws s3 sync ./dist s3://my-bucket
步骤 4:移除旧的 secrets(清理)
# List current secrets
gh secret list
# Remove old credentials
gh secret remove AWS_ACCESS_KEY_ID
gh secret remove AWS_SECRET_ACCESS_KEY
配置管理
避免硬编码账户 ID
反模式:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
更好的做法:使用存储库变量:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/GitHubActionsRole
最佳做法:从配置文件读取:
- name: Load config
id: config
run: |
echo "account=$(grep '^AWS_ACCOUNT_ID=' .env | cut -d'=' -f2)" >> $GITHUB_OUTPUT
echo "region=$(grep '^AWS_REGION=' .env | cut -d'=' -f2)" >> $GITHUB_OUTPUT
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ steps.config.outputs.account }}:role/GitHubActionsRole
aws-region: ${{ steps.config.outputs.region }}
部署模式
模式 A:触发流水线
GitHub Actions 触发 AWS CodePipeline,由它处理实际部署。
适用场景:
- 复杂的多阶段部署
- 需要 AWS 原生的部署历史记录
- 希望支持从多个来源触发
- 构建需要大量计算资源
工作流:
permissions:
id-token: write
contents: read
jobs:
trigger:
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/GitHubActionsRole
aws-region: ${{ vars.AWS_REGION }}
- run: aws codepipeline start-pipeline-execution --name my-pipeline
IAM 权限:
{
"Effect": "Allow",
"Action": "codepipeline:StartPipelineExecution",
"Resource": "arn:aws:codepipeline:*:*:pipeline-name"
}
模式 B:直接部署
GitHub Actions 执行完整部署(构建 + 部署)。
适用场景:
- 简单的静态站点部署
- 希望快速获得反馈
- 更倾向于使用 GitHub Actions 原生功能
- 注重成本(避免 CodePipeline/CodeBuild 费用)
工作流:
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci && npm run build
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/GitHubActionsRole
aws-region: ${{ vars.AWS_REGION }}
- run: |
aws s3 sync dist/ s3://my-bucket/ --delete
aws cloudfront create-invalidation --distribution-id ${{ vars.CF_DIST_ID }} --paths "/*"
IAM 权限:
{
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:DeleteObject", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::bucket/*", "arn:aws:s3:::bucket"]
},
{
"Effect": "Allow",
"Action": "cloudfront:CreateInvalidation",
"Resource": "*"
}
]
}
模式 C:基础设施部署
GitHub Actions 部署基础设施变更(CDK、Terraform、CloudFormation)。
适用场景:
- 基础设施即代码工作流
- 希望通过 PR 审查基础设施变更
- 需要在合并前验证变更
工作流:
permissions:
id-token: write
contents: read
pull-requests: write # For PR comments
jobs:
plan:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/GitHubActionsTerraformRole
aws-region: ${{ vars.AWS_REGION }}
- run: terraform plan -out=plan.tfplan
- uses: actions/github-script@v7
with:
script: |
// Post plan to PR comment
apply:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/GitHubActionsTerraformRole
aws-region: ${{ vars.AWS_REGION }}
- run: terraform apply -auto-approve
IAM 权限:通常需要较广泛的权限(AdministratorAccess 或 PowerUserAccess)。可以考虑为计划(只读)和应用(写入)分别使用不同角色。
多环境策略
每个环境使用单独的角色
推荐做法:为开发、预发布和生产环境分别创建权限不同的角色。
CDK 示例:
['dev', 'prod'].forEach(env => {
new iam.Role(this, `GitHubActionsRole-${env}`, {
roleName: `GitHubActionsRole-${env}`,
assumedBy: new iam.WebIdentityPrincipal(provider.openIdConnectProviderArn, {
StringEquals: {
'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com'
},
StringLike: {
'token.actions.githubusercontent.com:sub':
env === 'prod'
? `repo:${owner}/${repo}:ref:refs/heads/main`
: `repo:${owner}/${repo}:ref:refs/heads/dev`
}
})
});
});
工作流:
- name: Determine environment
id: env
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "name=prod" >> $GITHUB_OUTPUT
else
echo "name=dev" >> $GITHUB_OUTPUT
fi
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/GitHubActionsRole-${{ steps.env.outputs.name }}
aws-region: ${{ vars.AWS_REGION }}
安全注意事项
1. 最小权限原则
始终仅授予所需的最低权限:
// ❌ Too broad
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
// ✅ Specific
{
"Effect": "Allow",
"Action": "codepipeline:StartPipelineExecution",
"Resource": "arn:aws:codepipeline:us-east-1:123456789012:my-pipeline"
}
2. 仓库范围限制
始终将范围限制为特定仓库:
// ❌ Any repository in organization
"token.actions.githubusercontent.com:sub": "repo:my-org/*"
// ✅ Specific repository
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:*"
// ✅ Even more specific (main branch only)
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
3. 受众验证
始终验证受众:
{
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}
}
4. 会话时长
默认值:1 小时(足以满足大多数工作流的需求)
自定义时长(如有需要):
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
role-duration-seconds: 3600 # 1 hour (default)
aws-region: us-east-1
5. CloudTrail 审计
监控角色的担任情况:
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity \
--max-results 10
需要监控的关键字段:
userIdentity.principalId:GitHub 仓库和工作流requestParameters.roleArn:被担任的角色sourceIPAddress:GitHub Actions 的 IP 范围userAgent:GitHub Actions 的用户代理
故障排查
错误:"Not authorized to perform sts:AssumeRoleWithWebIdentity"
原因:信任策略与工作流上下文不匹配。
检查:
- 仓库名称与信任策略一致
- 分支或标签与信任策略条件一致
- 工作流具有
id-token: write权限
调试:
- name: Debug OIDC token
run: |
curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | jq
错误:"OpenIDConnect provider not found"
原因:未创建 OIDC 提供方,或 ARN 不正确。
修复方法:
# List providers
aws iam list-open-id-connect-providers
# Check provider details
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com
成功通过身份验证后出现“Access Denied”
原因:角色缺少所需权限。
修复方法:更新角色的权限策略,加入所需操作。
工作流未请求 OIDC 令牌
原因:缺少 id-token: write 权限。
修复方法:
permissions:
id-token: write # Add this
contents: read
迁移检查清单
从静态凭证迁移到 OIDC:
- 在 AWS 账户中创建 OIDC 提供方
- 创建包含信任策略的 IAM 角色
- 为角色附加权限策略
- 手动测试角色担任情况(可选)
- 更新工作流以使用 OIDC
- 在工作流中添加
permissions块 - 将凭证密钥替换为角色 ARN
- 在非生产环境中测试工作流
- 确认 CloudTrail 日志记录了角色担任情况
- 部署到生产环境
- 从 GitHub 中删除旧的 AWS 凭证密钥
- 撤销或删除旧的 IAM 用户(如适用)
- 记录角色 ARN 和权限
参考资料
- GitHub Actions OIDC 文档
- AWS IAM OIDC 提供方文档
- aws-actions/configure-aws-credentials
- GitHub OIDC 令牌声明
- AWS 安全博客:使用 IAM 角色连接 GitHub Actions
渐进式改进
如果开发者纠正了本技能本应避免的行为,请提出对本技能的具体修订建议,以防止今后再次出现相同问题。