mcpField
G
github-actions-oidc-aws

github-actions-oidc-aws

github.com/loxosceles-dev云与 DevOps136 次安装
代码仓库

关于 github-actions-oidc-aws

面向使用 GitHub Actions 部署到 AWS 的团队,介绍以 OIDC 扮演 IAM 角色、替代长期凭证的参考方案。内容涵盖配置、仓库范围限制、权限设计、部署模式与故障排查。

主要功能

  1. 01通过 OIDC 获取临时 AWS 凭证
  2. 02按仓库和分支限制信任策略
  3. 03遵循最小权限原则设置 IAM
  4. 04提供 CDK、Terraform、CLI 和 CloudFormation 配置
  5. 05涵盖部署模式与故障排查

适用场景

  1. 01部署前为 GitHub Actions 配置 AWS 身份验证
  2. 02将 AWS 角色限制到指定仓库或分支
  3. 03替换存储的 AWS 访问密钥
  4. 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

工作原理:

  1. GitHub Actions 向 GitHub 请求 OIDC 令牌
  2. 工作流将令牌提交给 AWS STS
  3. AWS 根据 IAM OIDC 提供商验证令牌
  4. AWS 签发临时凭证(有效期为 1 小时)
  5. 工作流使用临时凭证访问 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"

原因:信任策略与工作流上下文不匹配。

检查:

  1. 仓库名称与信任策略一致
  2. 分支或标签与信任策略条件一致
  3. 工作流具有 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 和权限

参考资料


渐进式改进

如果开发者纠正了本技能本应避免的行为,请提出对本技能的具体修订建议,以防止今后再次出现相同问题。

评论

支持 Markdown