🔑

GitHub ActionsからOIDCを使って安全にAWS ECSへデプロイする

に公開

背景

GitHub Actionsを使ってECSへデプロイする際、これまでアクセスキーをSecretsに保存していました。

しかし、このやり方には以下の問題があります:

  • 権限が広すぎる
  • 有効期限の管理が面倒
  • キー漏洩のリスク

OIDC(OpenID Connect)を使えば、一時的な認証情報だけで安全にAWSへアクセスできます。

この記事では、GitHub ActionsからOIDCを使ってECSタスクをデプロイする方法を記録します。

OIDCを使うメリット

GitHub ActionsとAWSを連携する方法を以下に比較してみます。

方法 セキュリティ 運用負荷
アクセスキーをSecretsに登録 中(漏洩リスクあり) 高(ローテーション管理)
OIDCで一時認証情報を取得 高(キー不要) 低(管理自動化)

OIDCのメリット

  • アクセスキーの管理が不要
  • GitHubが発行するJWTトークンでAWSにアクセス
  • 最小権限の原則に則った安全な構成が可能

OIDCの仕組み

GitHub ActionsからOIDCを使ってAWSにアクセスする流れです。

① GitHub ActionsでJWTトークンを取得

GitHub Actionsワークフローが実行されると、GitHub内蔵のOIDCプロバイダーからJWTトークン(JSON Web Token)を取得します。

② AWSにJWTトークンを送信

取得したJWTトークンを使って、AWS STSのAssumeRoleWithWebIdentity APIを呼び出します。事前に設定したIAMロールの引き受けを要求します。

# 内部的に実行されるAPI呼び出し(イメージ)
aws sts assume-role-with-web-identity \
  --role-arn arn:aws:iam::123456789012:role/GitHubActionsRole \
  --role-session-name github-actions-session \
  --web-identity-token <JWT-Token>

③ AWSがJWTトークンを検証

AWSは受け取ったJWTトークンを検証します:

  1. 発行者の確認: isshttps://token.actions.githubusercontent.comであることを確認
  2. 署名の検証: GitHubの公開鍵でJWTトークンの署名を検証
  3. 条件の確認: IAMロールの信頼ポリシーで設定した条件(リポジトリ名、ブランチなど)とJWTトークンが一致するかチェック

検証に成功すると、AWSは一時的な認証情報を返します:

  • アクセスキーID
  • シークレットアクセスキー
  • セッショントークン
  • 有効期限(デフォルトで最大1時間)

④ 一時的な認証情報でECSにデプロイ

GitHub Actionsは、取得した一時的な認証情報を使ってAWS操作を実行します:

  • ECRへのPUSH: コンテナイメージをECRリポジトリにアップロード
  • タスク定義の更新: 新しいイメージを使用するタスク定義を登録
  • ECSサービスの更新: update-service --force-new-deploymentでサービスを再起動

この認証情報には、IAMロールに付与したポリシーの権限のみが含まれるため、最小権限の原則に従った安全な操作が可能です。

設定手順

1. IAMロールの作成(AWS側)

GitHubからのOIDC接続を許可するIAMロールを作成します。

# OIDCプロバイダーの作成
resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = [
    "6938fd4d98bab03faadb97b34396831e3780aea1",
    "1c58a3a8518e8759bf075b76b750d4f2df264fcd"
  ]
}

# IAMロールの作成
resource "aws_iam_role" "github_actions_role" {
  name = "GitHubActionsRole"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Federated = "arn:aws:iam::<account-id>:oidc-provider/token.actions.githubusercontent.com"
        }
        Action = "sts:AssumeRoleWithWebIdentity"
        Condition = {
          StringLike = {
            "token.actions.githubusercontent.com:sub" = "repo:<your-org>/<your-repo>:*"
          }
        }
      }
    ]
  })
}

# 必要なポリシーをアタッチ(例:ECS, ECR, CloudWatch Logsなど)

thumbprintについて

thumbprintは、OIDCプロバイダー(GitHub)のSSL証明書の指紋です。AWSがGitHubからのリクエストを信頼するために使用されます。

GitHub ActionsのOIDCプロバイダー用のthumbprintは以下の値です:

  • 6938fd4d98bab03faadb97b34396831e3780aea1
  • 1c58a3a8518e8759bf075b76b750d4f2df264fcd

thumbprintは以下のコマンドでも取得できます:

echo | openssl s_client -servername token.actions.githubusercontent.com \
  -connect token.actions.githubusercontent.com:443 2>/dev/null \
  | openssl x509 -fingerprint -noout -sha1 \
  | sed 's/://g' | sed 's/.*=//g' | tr '[:upper:]' '[:lower:]'

2. GitHub Actionsワークフローの作成

name: Deploy to ECS

on:
  push:
    branches: [main]

permissions:
  id-token: write  # JWTトークンの取得に必要
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest

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

      - name: Configure AWS Credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::<account-id>:role/GitHubActionsRole
          aws-region: ap-northeast-1

      - name: Build and Push to ECR
        run: |
          docker build -t myapp .
          docker tag myapp:latest <account-id>.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:latest
          aws ecr get-login-password | docker login --username AWS --password-stdin <account-id>.dkr.ecr.ap-northeast-1.amazonaws.com
          docker push <account-id>.dkr.ecr.ap-northeast-1.amazonaws.com/myapp:latest

      - name: Deploy to ECS
        run: |
          aws ecs update-service \
            --cluster my-cluster \
            --service my-service \
            --force-new-deployment

注意点

個人的にハマったポイント:

  • permissions.id-token: writeは必須: JWTトークンの取得権限として必要
  • 信頼ポリシーは最小限に: リポジトリ単位で権限を制限する
  • IAMポリシーの調整: ECRへのPUSH、ECSのタスク定義更新、CloudWatchの権限が必要。動かないから調べてPolicy追加→実機で試すというループを何度も繰り返しました

セキュアで運用性が高くなったぜ!

アクセスキーの管理が不要になり、セキュリティと運用性が大幅に向上しました。

GitHubで編集を提案

Discussion