github-actions-oidc-aws

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

GitHub Actions OIDC Authentication for AWS

面向AWS的GitHub Actions OIDC身份认证

This is a reference pattern. Learn from the approach, adapt to your context — don't copy verbatim.
Status: 🔴 CRITICAL PATTERN
Category: CI/CD / Infrastructure
Applies To: Any project using GitHub Actions to deploy to AWS

这是一个参考模式。 请学习其实现思路,适配你的业务场景,不要直接原样复制。
状态: 🔴 关键模式
分类: CI/CD / 基础设施
适用场景: 所有使用GitHub Actions部署到AWS的项目

Overview

概述

Secure authentication pattern for GitHub Actions workflows to access AWS resources using OpenID Connect (OIDC) instead of long-lived IAM credentials. Eliminates the need to store AWS access keys in GitHub secrets.
Key Benefits:
  • No long-lived credentials to rotate or leak
  • Temporary credentials with automatic expiration
  • Repository-scoped access control
  • Audit trail via AWS CloudTrail
  • Industry best practice (AWS + GitHub recommended)

这是一种GitHub Actions工作流访问AWS资源的安全认证模式,使用OpenID Connect(OIDC)替代长期有效的IAM凭证,无需在GitHub secrets中存储AWS访问密钥。
核心优势:
  • 无需轮换或担心泄露长期有效凭证
  • 使用自动过期的临时凭证
  • 仓库级别的访问权限控制
  • 可通过AWS CloudTrail生成审计日志
  • 行业最佳实践(AWS和GitHub官方推荐)

Problem

存在的问题

GitHub Actions workflows need to authenticate to AWS to deploy infrastructure, trigger pipelines, or manage resources. Traditional approaches have security issues:
Anti-Pattern: Static IAM Credentials
yaml
undefined
GitHub Actions工作流在部署基础设施、触发流水线或管理资源时需要向AWS进行身份认证,传统实现方式存在安全隐患:
反模式:静态IAM凭证
yaml
undefined

❌ Security risk: long-lived credentials in secrets

❌ 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 }}

**Problems**:
- Credentials never expire (must be manually rotated)
- If leaked, attacker has persistent access
- No way to scope to specific repositories
- Difficult to audit which workflow used credentials
- Violates principle of least privilege

---
  • 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 }}

**存在的问题**:
- 凭证永不过期(必须手动轮换)
- 一旦泄露,攻击者将获得持久访问权限
- 无法限定仅特定仓库可使用凭证
- 难以审计哪个工作流使用了凭证
- 违反最小权限原则

---

Solution

解决方案

Use GitHub's OIDC provider to issue temporary credentials via AWS IAM role assumption.
Flow:
GitHub Actions → OIDC Token → AWS STS → Temporary Credentials → AWS Resources
How it works:
  1. GitHub Actions requests OIDC token from GitHub
  2. Workflow presents token to AWS STS
  3. AWS validates token against IAM OIDC provider
  4. AWS issues temporary credentials (valid 1 hour)
  5. Workflow uses temporary credentials to access AWS

使用GitHub的OIDC提供商,通过AWS IAM角色Assume机制下发临时凭证。
流程:
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资源

Components

组件说明

1. AWS IAM OIDC Provider

1. AWS IAM OIDC提供商

Establishes trust between AWS and GitHub's OIDC issuer.
Configuration:
  • URL:
    https://token.actions.githubusercontent.com
  • Audience:
    sts.amazonaws.com
  • Thumbprint:
    1c58a3a8518e8759bf075b76b750d4f2df264fcd
    (GitHub root CA)
Why root CA thumbprint?
  • More stable than intermediate certificate
  • GitHub can rotate intermediate certs without breaking trust
  • Recommended by AWS documentation
用于建立AWS和GitHub OIDC签发方之间的信任关系。
配置:
  • URL:
    https://token.actions.githubusercontent.com
  • Audience:
    sts.amazonaws.com
  • Thumbprint:
    1c58a3a8518e8759bf075b76b750d4f2df264fcd
    (GitHub根CA证书)
为什么使用根CA指纹?
  • 比中间证书更稳定
  • GitHub轮换中间证书时不会破坏信任关系
  • AWS官方文档推荐方案

2. IAM Role with Trust Policy

2. 配置信任策略的IAM角色

Role that GitHub Actions can assume, with trust policy restricting access.
Trust Policy Structure:
json
{
  "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}:*"
      }
    }
  }]
}
Trust Policy Scoping Options:
undefined
GitHub Actions可Assume的角色,通过信任策略限制访问权限。
信任策略结构:
json
{
  "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}:*"
      }
    }
  }]
}
信任策略范围配置选项:
undefined

All branches and tags

All branches and tags

"repo:owner/repo:*"
"repo:owner/repo:*"

Specific branch only

Specific branch only

"repo:owner/repo:ref:refs/heads/main"
"repo:owner/repo:ref:refs/heads/main"

Multiple branches

Multiple branches

["repo:owner/repo:ref:refs/heads/main", "repo:owner/repo:ref:refs/heads/dev"]
["repo:owner/repo:ref:refs/heads/main", "repo:owner/repo:ref:refs/heads/dev"]

Pull requests

Pull requests

"repo:owner/repo:pull_request"
"repo:owner/repo:pull_request"

Environment-specific

Environment-specific

"repo:owner/repo:environment:production"
undefined
"repo:owner/repo:environment:production"
undefined

3. IAM Permissions Policy

3. IAM权限策略

Defines what the role can do in AWS (principle of least privilege).
Examples by use case:
Pipeline Trigger Only:
json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "codepipeline:StartPipelineExecution",
    "Resource": "arn:aws:codepipeline:*:{account}:pipeline-name-*"
  }]
}
Direct S3 Deployment:
json
{
  "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": "*"
    }
  ]
}
Infrastructure Deployment (use with caution):
json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "*",
    "Resource": "*"
  }]
}
定义角色在AWS中的可执行操作(遵循最小权限原则)。
不同场景的示例:
仅用于触发流水线:
json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "codepipeline:StartPipelineExecution",
    "Resource": "arn:aws:codepipeline:*:{account}:pipeline-name-*"
  }]
}
直接部署到S3:
json
{
  "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": "*"
    }
  ]
}
基础设施部署(谨慎使用):
json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "*",
    "Resource": "*"
  }]
}

4. GitHub Actions Workflow Configuration

4. GitHub Actions工作流配置

Workflow must request OIDC token and assume role.
Required Permissions:
yaml
permissions:
  id-token: write  # Required for OIDC token
  contents: read   # Required to checkout code
Authentication Step:
yaml
- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::{account}:role/{role-name}
    aws-region: {region}

工作流必须请求OIDC令牌并Assume对应角色。
所需权限:
yaml
permissions:
  id-token: write  # Required for OIDC token
  contents: read   # Required to checkout code
认证步骤:
yaml
- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::{account}:role/{role-name}
    aws-region: {region}

Implementation

实现步骤

Step 1: Create OIDC Provider (One-Time Setup)

步骤1:创建OIDC提供商(仅需配置一次)

Choose your infrastructure tool:
AWS CDK (TypeScript):
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:
hcl
resource "aws_iam_openid_connect_provider" "github" {
  url = "https://token.actions.githubusercontent.com"
  client_id_list = ["sts.amazonaws.com"]
  thumbprint_list = ["1c58a3a8518e8759bf075b76b750d4f2df264fcd"]
}
AWS CLI:
bash
aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list 1c58a3a8518e8759bf075b76b750d4f2df264fcd
CloudFormation:
yaml
GitHubOIDCProvider:
  Type: AWS::IAM::OIDCProvider
  Properties:
    Url: https://token.actions.githubusercontent.com
    ClientIdList:
      - sts.amazonaws.com
    ThumbprintList:
      - 1c58a3a8518e8759bf075b76b750d4f2df264fcd
选择你使用的基础设施工具:
AWS CDK (TypeScript):
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:
hcl
resource "aws_iam_openid_connect_provider" "github" {
  url = "https://token.actions.githubusercontent.com"
  client_id_list = ["sts.amazonaws.com"]
  thumbprint_list = ["1c58a3a8518e8759bf075b76b750d4f2df264fcd"]
}
AWS CLI:
bash
aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list 1c58a3a8518e8759bf075b76b750d4f2df264fcd
CloudFormation:
yaml
GitHubOIDCProvider:
  Type: AWS::IAM::OIDCProvider
  Properties:
    Url: https://token.actions.githubusercontent.com
    ClientIdList:
      - sts.amazonaws.com
    ThumbprintList:
      - 1c58a3a8518e8759bf075b76b750d4f2df264fcd

Step 2: Create IAM Role

步骤2:创建IAM角色

AWS CDK (TypeScript):
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:
hcl
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-*"
    }]
  })
}
AWS CDK (TypeScript):
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:
hcl
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-*"
    }]
  })
}

Step 3: Update GitHub Actions Workflow

步骤3:更新GitHub Actions工作流

Before (Static Credentials):
yaml
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
After (OIDC):
yaml
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
改造前(使用静态凭证):
yaml
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):
yaml
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

Step 4: Remove Old Secrets (Cleanup)

步骤4:删除旧的Secrets(清理)

bash
undefined
bash
undefined

List current secrets

List current secrets

gh secret list
gh secret list

Remove old credentials

Remove old credentials

gh secret remove AWS_ACCESS_KEY_ID gh secret remove AWS_SECRET_ACCESS_KEY

---
gh secret remove AWS_ACCESS_KEY_ID gh secret remove AWS_SECRET_ACCESS_KEY

---

Configuration Management

配置管理

Avoid Hardcoding Account IDs

避免硬编码账号ID

Anti-Pattern:
yaml
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
Better: Use Repository Variables:
yaml
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/GitHubActionsRole
Best: Read from Configuration File:
yaml
- 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 }}

反模式:
yaml
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
更优方案:使用仓库变量:
yaml
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/GitHubActionsRole
最佳方案:从配置文件读取:
yaml
- 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 }}

Deployment Patterns

部署模式

Pattern A: Pipeline Trigger

模式A:触发流水线

GitHub Actions triggers AWS CodePipeline, which handles actual deployment.
Use When:
  • Complex multi-stage deployments
  • Need AWS-native deployment history
  • Want to trigger from multiple sources
  • Build requires significant compute resources
Workflow:
yaml
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 Permissions:
json
{
  "Effect": "Allow",
  "Action": "codepipeline:StartPipelineExecution",
  "Resource": "arn:aws:codepipeline:*:*:pipeline-name"
}
GitHub Actions触发AWS CodePipeline,由CodePipeline执行实际部署操作。
适用场景:
  • 复杂的多阶段部署
  • 需要AWS原生的部署历史记录
  • 需要支持多来源触发
  • 构建过程需要大量计算资源
工作流:
yaml
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权限:
json
{
  "Effect": "Allow",
  "Action": "codepipeline:StartPipelineExecution",
  "Resource": "arn:aws:codepipeline:*:*:pipeline-name"
}

Pattern B: Direct Deployment

模式B:直接部署

GitHub Actions performs full deployment (build + deploy).
Use When:
  • Simple static site deployments
  • Want fast feedback loops
  • Prefer GitHub Actions native features
  • Cost-conscious (avoid CodePipeline/CodeBuild costs)
Workflow:
yaml
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 Permissions:
json
{
  "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": "*"
    }
  ]
}
GitHub Actions执行完整部署流程(构建+部署)。
适用场景:
  • 简单的静态站点部署
  • 需要快速的反馈链路
  • 偏好使用GitHub Actions原生功能
  • 对成本敏感(避免CodePipeline/CodeBuild的费用)
工作流:
yaml
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权限:
json
{
  "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": "*"
    }
  ]
}

Pattern C: Infrastructure Deployment

模式C:基础设施部署

GitHub Actions deploys infrastructure changes (CDK, Terraform, CloudFormation).
Use When:
  • Infrastructure as Code workflows
  • Want PR-based infrastructure reviews
  • Need to validate changes before merge
Workflow:
yaml
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 Permissions: Typically requires broad permissions (AdministratorAccess or PowerUserAccess). Consider using separate roles for plan (read-only) vs apply (write).

GitHub Actions部署基础设施变更(CDK、Terraform、CloudFormation)。
适用场景:
  • 基础设施即代码工作流
  • 需要基于PR的基础设施变更审核
  • 需要在合并前验证变更的正确性
工作流:
yaml
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)。建议为plan(只读)和apply(写入)操作使用独立的角色。

Multi-Environment Strategy

多环境策略

Separate Roles per Environment

每个环境使用独立角色

Recommended: Create separate roles for dev/staging/prod with different permissions.
CDK Example:
typescript
['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`
      }
    })
  });
});
Workflow:
yaml
- 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 }}

推荐方案: 为开发/测试/生产环境创建独立角色,配置不同的权限。
CDK示例:
typescript
['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`
      }
    })
  });
});
工作流:
yaml
- 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 }}

Security Considerations

安全注意事项

1. Principle of Least Privilege

1. 最小权限原则

Always grant minimum permissions required:
json
// ❌ Too broad
{
  "Effect": "Allow",
  "Action": "*",
  "Resource": "*"
}

// ✅ Specific
{
  "Effect": "Allow",
  "Action": "codepipeline:StartPipelineExecution",
  "Resource": "arn:aws:codepipeline:us-east-1:123456789012:my-pipeline"
}
始终授予所需的最小权限:
json
// ❌ Too broad
{
  "Effect": "Allow",
  "Action": "*",
  "Resource": "*"
}

// ✅ Specific
{
  "Effect": "Allow",
  "Action": "codepipeline:StartPipelineExecution",
  "Resource": "arn:aws:codepipeline:us-east-1:123456789012:my-pipeline"
}

2. Repository Scoping

2. 仓库范围限定

Always restrict to specific repository:
json
// ❌ 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"
始终限定仅特定仓库可访问:
json
// ❌ 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. Audience Validation

3. 受众验证

Always validate audience:
json
{
  "StringEquals": {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
  }
}
始终验证受众:
json
{
  "StringEquals": {
    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
  }
}

4. Session Duration

4. 会话时长

Default: 1 hour (sufficient for most workflows)
Custom duration (if needed):
yaml
- 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
默认值: 1小时(满足绝大多数工作流需求)
自定义时长(如有需要):
yaml
- 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 Auditing

5. CloudTrail审计

Monitor role assumptions:
bash
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity \
  --max-results 10
Key fields to monitor:
  • userIdentity.principalId
    : GitHub repository and workflow
  • requestParameters.roleArn
    : Which role was assumed
  • sourceIPAddress
    : GitHub Actions IP range
  • userAgent
    : GitHub Actions user agent

监控角色Assume操作:
bash
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity \
  --max-results 10
需要监控的关键字段:
  • userIdentity.principalId
    : GitHub仓库和工作流信息
  • requestParameters.roleArn
    : 被Assume的角色ARN
  • sourceIPAddress
    : GitHub Actions IP段
  • userAgent
    : GitHub Actions user agent

Troubleshooting

问题排查

Error: "Not authorized to perform sts:AssumeRoleWithWebIdentity"

错误:"Not authorized to perform sts:AssumeRoleWithWebIdentity"

Cause: Trust policy doesn't match workflow context.
Check:
  1. Repository name matches trust policy
  2. Branch/tag matches trust policy condition
  3. Workflow has
    id-token: write
    permission
Debug:
yaml
- name: Debug OIDC token
  run: |
    curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
      "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | jq
原因: 信任策略和工作流上下文不匹配。
检查项:
  1. 仓库名称和信任策略配置匹配
  2. 分支/标签符合信任策略的条件
  3. 工作流配置了
    id-token: write
    权限
调试方法:
yaml
- name: Debug OIDC token
  run: |
    curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
      "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | jq

Error: "OpenIDConnect provider not found"

错误:"OpenIDConnect provider not found"

Cause: OIDC provider not created or wrong ARN.
Fix:
bash
undefined
原因: OIDC提供商未创建或ARN配置错误。
修复方案:
bash
undefined

List providers

List providers

aws iam list-open-id-connect-providers
aws iam list-open-id-connect-providers

Check provider details

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
undefined
aws iam get-open-id-connect-provider
--open-id-connect-provider-arn arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com
undefined

Error: "Access Denied" after successful authentication

错误:认证成功后提示"Access Denied"

Cause: Role lacks required permissions.
Fix: Update role's permissions policy to include required actions.
原因: 角色缺少所需的权限。
修复方案: 更新角色的权限策略,添加所需的操作权限。

Workflow doesn't request OIDC token

工作流未请求OIDC令牌

Cause: Missing
id-token: write
permission.
Fix:
yaml
permissions:
  id-token: write  # Add this
  contents: read

原因: 缺少
id-token: write
权限配置。
修复方案:
yaml
permissions:
  id-token: write  # Add this
  contents: read

Migration Checklist

迁移检查清单

Migrating from static credentials to OIDC:
  • Create OIDC provider in AWS account
  • Create IAM role with trust policy
  • Attach permissions policy to role
  • Test role assumption manually (optional)
  • Update workflow to use OIDC
  • Add
    permissions
    block to workflow
  • Replace credential secrets with role ARN
  • Test workflow in non-production environment
  • Verify CloudTrail logs show role assumption
  • Deploy to production
  • Remove old AWS credential secrets from GitHub
  • Revoke/delete old IAM user (if applicable)
  • Document role ARN and permissions

从静态凭证迁移到OIDC的检查项:
  • 在AWS账号中创建OIDC提供商
  • 创建配置了信任策略的IAM角色
  • 为角色绑定权限策略
  • 手动测试角色Assume操作(可选)
  • 更新工作流使用OIDC认证
  • 为工作流添加
    permissions
    配置块
  • 用角色ARN替换凭证secrets
  • 在非生产环境测试工作流
  • 验证CloudTrail日志中有角色Assume记录
  • 部署到生产环境
  • 从GitHub中删除旧的AWS凭证secrets
  • 吊销/删除旧的IAM用户(如有)
  • 记录角色ARN和权限配置

References

参考资料

Progressive Improvement

持续优化

If the developer corrects a behavior that this skill should have prevented, suggest a specific amendment to this skill to prevent the same correction in the future.
如果开发者修正了本指南本应避免的错误行为,请提出具体的修订建议,避免未来再次出现同类问题。