GitHub ActionsAWSOIDCCI/CDSecurityIAMTerraformSAM

GitHub ActionsからAWSへOIDC認証でデプロイする方法

Sloth255
Sloth255
·4 min read·780 words

はじめに

GitHub ActionsからAWSリソースへデプロイする際、従来はIAMユーザーのアクセスキーをGitHub Secretsに保存する方法が一般的でした。しかし、この方法には以下の課題があります。

  • アクセスキーの漏洩リスク
  • 定期的なローテーション作業が必要
  • 管理するシークレットの増加

これらの課題を解決するのがOIDC(OpenID Connect)認証です。この記事では、GitHub ActionsとAWSをOIDCで連携させる設定方法を詳しく解説します。

OIDCとは

OIDCは、OAuth 2.0をベースにした認証プロトコルです。GitHub ActionsがAWSに対して「自分はこのリポジトリのワークフローです」と証明できるトークンを発行し、AWSがそれを検証して一時的な認証情報を提供します。

OIDCのメリット

  • シークレット不要: アクセスキーをGitHubに保存する必要がない
  • セキュリティ向上: 一時的な認証情報のみを使用
  • 細かい権限制御: リポジトリやブランチ単位で権限を制限可能
  • 管理コスト削減: キーローテーションが不要

AWS側の設定

1. IAM IDプロバイダーの作成

まず、AWSマネジメントコンソールでIAM IDプロバイダーを作成します。

  1. IAMコンソールを開く
  2. 「IDプロバイダー」→「プロバイダーを追加」をクリック
  3. 以下の情報を入力:
プロバイダーのタイプ: OpenID Connect
プロバイダーのURL: https://token.actions.githubusercontent.com
対象者: sts.amazonaws.com

Terraformでの作成例

resource "aws_iam_openid_connect_provider" "github_actions" {
  url = "https://token.actions.githubusercontent.com"

  client_id_list = [
    "sts.amazonaws.com",
  ]

  thumbprint_list = [
    "6938fd4d98bab03faadb97b34396831e3780aea1"
  ]
}

AWS SAMでの作成例

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: GitHub Actions OIDC Provider

Resources:
  GitHubOIDCProvider:
    Type: AWS::IAM::OIDCProvider
    Properties:
      Url: https://token.actions.githubusercontent.com
      ClientIdList:
        - sts.amazonaws.com
      ThumbprintList:
        - 6938fd4d98bab03faadb97b34396831e3780aea1

2. IAMロールの作成

次に、GitHub Actionsが引き受けるIAMロールを作成します。

信頼ポリシーの設定

{
  "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:your-org/your-repo:*"
        }
      }
    }
  ]
}

条件の詳細

token.actions.githubusercontent.com:subの値で、アクセスを制限できます。

# 特定のリポジトリ全体
repo:your-org/your-repo:*

# 特定のブランチのみ
repo:your-org/your-repo:ref:refs/heads/main

# 特定の環境のみ
repo:your-org/your-repo:environment:production

# プルリクエストのみ
repo:your-org/your-repo:pull_request

Terraformでの作成例

data "aws_iam_policy_document" "github_actions_assume_role" {
  statement {
    actions = ["sts:AssumeRoleWithWebIdentity"]

    principals {
      type        = "Federated"
      identifiers = [aws_iam_openid_connect_provider.github_actions.arn]
    }

    condition {
      test     = "StringEquals"
      variable = "token.actions.githubusercontent.com:aud"
      values   = ["sts.amazonaws.com"]
    }

    condition {
      test     = "StringLike"
      variable = "token.actions.githubusercontent.com:sub"
      values   = ["repo:your-org/your-repo:*"]
    }
  }
}

resource "aws_iam_role" "github_actions" {
  name               = "github-actions-deploy-role"
  assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role.json
}

# 必要な権限ポリシーをアタッチ
resource "aws_iam_role_policy_attachment" "deploy_policy" {
  role       = aws_iam_role.github_actions.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonECS_FullAccess"
}

AWS SAMでの作成例

template.yaml
Resources:
  GitHubActionsRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: github-actions-deploy-role
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Federated: !GetAtt GitHubOIDCProvider.Arn
            Action: sts:AssumeRoleWithWebIdentity
            Condition:
              StringEquals:
                token.actions.githubusercontent.com:aud: sts.amazonaws.com
              StringLike:
                token.actions.githubusercontent.com:sub: repo:your-org/your-repo:*
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonECS_FullAccess

Outputs:
  GitHubActionsRoleArn:
    Description: ARN of the GitHub Actions IAM Role
    Value: !GetAtt GitHubActionsRole.Arn
    Export:
      Name: GitHubActionsRoleArn

SAMでデプロイする場合:

sam build
sam deploy --guided

初回デプロイ後、出力されたロールのARNをGitHub SecretsにAWS_ROLE_ARNとして登録します。

3. 権限ポリシーのアタッチ

デプロイに必要な権限をロールにアタッチします。例えば:

  • S3へのデプロイ: AmazonS3FullAccess
  • ECSへのデプロイ: AmazonECS_FullAccess
  • Lambda: AWSLambda_FullAccess

本番環境では、最小権限の原則に基づいてカスタムポリシーを作成することを推奨します。

GitHub Actions側の設定

ワークフローファイルの作成

.github/workflows/deploy.ymlを作成します。

name: Deploy to AWS

on:
  push:
    branches:
      - main

# OIDCトークンを取得するための権限設定(重要!)
permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy-role
          aws-region: ap-northeast-1

      - name: Deploy to S3
        run: |
          aws s3 sync ./dist s3://your-bucket-name --delete

      - name: Verify deployment
        run: |
          aws s3 ls s3://your-bucket-name

重要なポイント

1. permissions設定

permissions:
  id-token: write  # OIDCトークンの取得に必須
  contents: read   # リポジトリのチェックアウトに必要

この設定がないと、OIDCトークンを取得できずエラーになります。

2. configure-aws-credentialsアクションのバージョン

v4以降を使用してください。古いバージョンではOIDCに対応していない場合があります。

uses: aws-actions/configure-aws-credentials@v4

実践例: SAMアプリケーションのデプロイ

AWS SAMを使用したサーバーレスアプリケーションのデプロイ例です。

name: Deploy SAM Application

on:
  push:
    branches:
      - main

permissions:
  id-token: write
  contents: read

env:
  AWS_REGION: ap-northeast-1
  SAM_STACK_NAME: my-sam-app

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'

      - name: Setup SAM CLI
        uses: aws-actions/setup-sam@v2
        with:
          use-installer: true

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ${{ env.AWS_REGION }}

      - name: SAM Build
        run: sam build --use-container

      - name: SAM Deploy
        run: |
          sam deploy \
            --stack-name ${{ env.SAM_STACK_NAME }} \
            --capabilities CAPABILITY_IAM \
            --resolve-s3 \
            --no-fail-on-empty-changeset \
            --no-confirm-changeset

      - name: Get Stack Outputs
        run: |
          aws cloudformation describe-stacks \
            --stack-name ${{ env.SAM_STACK_NAME }} \
            --query 'Stacks[0].Outputs' \
            --output table

実践例: ECSへのデプロイ

より実践的な例として、ECSへのデプロイワークフローを紹介します。

name: Deploy to ECS

on:
  push:
    branches:
      - main

permissions:
  id-token: write
  contents: read

env:
  AWS_REGION: ap-northeast-1
  ECR_REPOSITORY: my-app
  ECS_SERVICE: my-service
  ECS_CLUSTER: my-cluster
  CONTAINER_NAME: my-container

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build and push image to ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT

      - name: Download task definition
        run: |
          aws ecs describe-task-definition \
            --task-definition my-task \
            --query taskDefinition > task-definition.json

      - name: Update task definition
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-definition.json
          container-name: ${{ env.CONTAINER_NAME }}
          image: ${{ steps.build-image.outputs.image }}

      - name: Deploy to ECS
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ steps.task-def.outputs.task-definition }}
          service: ${{ env.ECS_SERVICE }}
          cluster: ${{ env.ECS_CLUSTER }}
          wait-for-service-stability: true

トラブルシューティング

エラー: "Not authorized to perform sts:AssumeRoleWithWebIdentity"

原因: 信頼ポリシーの条件が一致していない

解決策:

  1. IAMロールの信頼ポリシーでtoken.actions.githubusercontent.com:subの値を確認
  2. リポジトリ名、ブランチ名が正しいか確認
  3. GitHub Actionsログで実際のsubクレームの値を確認
- name: Debug OIDC token
  run: |
    curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
      "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | \
      jq -R 'split(".") | .[1] | @base64d | fromjson'

エラー: "Error: Credentials could not be loaded"

原因: permissionsの設定が不足している

解決策: ワークフローファイルに以下を追加

permissions:
  id-token: write
  contents: read

認証は成功するが権限エラーが発生

原因: IAMロールに必要な権限ポリシーがアタッチされていない

解決策: IAMロールに適切なポリシーをアタッチ

aws iam attach-role-policy \
  --role-name github-actions-deploy-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess

セキュリティのベストプラクティス

1. 最小権限の原則

必要最小限の権限のみを付与します。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::your-specific-bucket",
        "arn:aws:s3:::your-specific-bucket/*"
      ]
    }
  ]
}

2. ブランチやタグで制限

本番環境へのデプロイは特定のブランチのみに制限します。

{
  "Condition": {
    "StringEquals": {
      "token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"
    }
  }
}

3. 環境ごとにロールを分離

開発、ステージング、本番で異なるロールを使用します。

- name: Configure AWS credentials (Production)
  if: github.ref == 'refs/heads/main'
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: ${{ secrets.AWS_ROLE_ARN_PROD }}
    aws-region: ap-northeast-1

- name: Configure AWS credentials (Development)
  if: github.ref == 'refs/heads/develop'
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: ${{ secrets.AWS_ROLE_ARN_DEV }}
    aws-region: ap-northeast-1

4. CloudTrailでの監査

すべてのAPI呼び出しを記録し、定期的に確認します。

# CloudTrail用のS3バケット
resource "aws_s3_bucket" "cloudtrail" {
  bucket = "my-cloudtrail-logs-bucket"
}

resource "aws_s3_bucket_policy" "cloudtrail" {
  bucket = aws_s3_bucket.cloudtrail.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "AWSCloudTrailAclCheck"
        Effect = "Allow"
        Principal = {
          Service = "cloudtrail.amazonaws.com"
        }
        Action   = "s3:GetBucketAcl"
        Resource = aws_s3_bucket.cloudtrail.arn
      },
      {
        Sid    = "AWSCloudTrailWrite"
        Effect = "Allow"
        Principal = {
          Service = "cloudtrail.amazonaws.com"
        }
        Action   = "s3:PutObject"
        Resource = "${aws_s3_bucket.cloudtrail.arn}/*"
        Condition = {
          StringEquals = {
            "s3:x-amz-acl" = "bucket-owner-full-control"
          }
        }
      }
    ]
  })
}

# CloudTrail
resource "aws_cloudtrail" "github_actions_audit" {
  name                          = "github-actions-audit"
  s3_bucket_name               = aws_s3_bucket.cloudtrail.id
  include_global_service_events = true
  is_multi_region_trail        = true
  enable_logging               = true

  depends_on = [aws_s3_bucket_policy.cloudtrail]
}

まとめ

GitHub ActionsとAWSのOIDC連携により、以下のメリットが得られます。

  • アクセスキー不要でセキュアなデプロイが可能
  • 一時的な認証情報のみを使用
  • リポジトリやブランチ単位での細かい権限制御
  • 管理コストの削減

初期設定は少し複雑ですが、一度設定すれば長期的なセキュリティと運用性の向上につながります。ぜひ導入を検討してみてください。

参考リンク