★★★ 上級

06. OIDC で AWS にキーレス認証

GitHub Secrets に AWS のアクセスキーを保存するのは、もう 古いやり方 です。GitHub の OIDC を AWS が信頼する設定を 1 度だけ作れば、以後ジョブごとに 15 分だけ有効な一時クレデンシャル でロールを assume できます。鍵が漏れる経路がそもそも存在しません。

なぜ OIDC か

従来:アクセスキー方式OIDC:キーレス方式
保存場所GitHub Secrets に AKIA...保存しない
有効期限ローテーションするまで永続15 分(自動失効)
漏洩リスク誰かが Secrets を読むとアウトそもそも秘密がない
監査キー作成者しか追えないSTS の AssumeRoleWithWebIdentity が CloudTrail に残る

仕組み(trust の流れ)

  1. GitHub Actions のジョブが実行されると、GitHub が OIDC トークン (JWT) を発行する。中身に「どのリポジトリの・どのブランチで・どのワークフローが」走っているかが入る
  2. ワークフローは aws-actions/configure-aws-credentials@v4 を呼び、その JWT を AWS STS に渡す
  3. AWS は IAM に登録された OIDC プロバイダ(GitHub)を信頼しているので、JWT の署名を検証 → 中身(sub クレーム)が IAM ロールの trust policy に合致するかチェック
  4. 合致すれば STS が一時クレデンシャル(AccessKeyId / SecretAccessKey / SessionToken)を発行。これがジョブの環境変数に流し込まれる
  5. 以後、その job 内の AWS CLI / Terraform はそのまま AWS API を叩ける

設定 3 ステップ

① IAM に OIDC プロバイダを登録

aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1

1 アカウントにつき 1 つあれば良いので、最初の 1 回だけ。

② GitHub Actions が assume する IAM ロールを作成

後述の trust policy 付きで作る。例: github-actions-terraform

③ ワークフロー側で role-to-assume を指定

permissions:
  id-token: write     # ← これがないと OIDC トークンが発行されない
  contents: read

steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/github-actions-terraform
      aws-region: ap-northeast-1

IAM ロールの trust policy

これが 本体 です。「GitHub の OIDC で来た JWT のうち、特定リポジトリ・特定ブランチのものだけ assume を許可する」と書きます。

{
  "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:ref:refs/heads/main"
        }
      }
    }
  ]
}

sub クレームでアクセス制御

sub の中身は GitHub が JWT に詰めてくる文字列です。これを StringEqualsStringLike で絞ることで 「どのジョブが assume できるか」 を制御します。

パターン意味
repo:org/repo:ref:refs/heads/mainmain ブランチからの実行のみ
repo:org/repo:pull_requestPR 経由の実行のみ
repo:org/repo:environment:productionenvironment: production 指定のジョブのみ
repo:org/repo:*このリポジトリ全部(緩め、開発用)
ベストプラクティス 本番用ロールenvironment:production 縛り。開発用ロールrepo:org/repo:*。両者を別 IAM ロールに分けて、本番ロールには破壊的権限のみ付ける。これで「PR の plan で誤って本番を壊す」事故が物理的に起きない。

Terraform で OIDC プロバイダとロールを作る例

OIDC の設定自体も Terraform で管理しましょう(このサイトのテーマ通り)。

data "aws_caller_identity" "current" {}

# OIDC プロバイダ(1 アカウント 1 つ)
resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}

# GitHub Actions が assume するロール
resource "aws_iam_role" "github_actions_terraform" {
  name = "github-actions-terraform"

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

# 必要な権限ポリシーを付ける(最小権限を心がける)
resource "aws_iam_role_policy_attachment" "tf" {
  role       = aws_iam_role.github_actions_terraform.name
  policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess"  # 例。本番では絞る
}
権限の絞り方 PowerUserAccess は学習用の便宜です。本番運用では「このリポジトリで触る AWS リソースだけに絞ったカスタムポリシー」を作って付ける。OIDC でロールが守られても、ロールが強すぎれば結局壊せます