06. OIDC で AWS にキーレス認証
GitHub Secrets に AWS のアクセスキーを保存するのは、もう 古いやり方 です。GitHub の OIDC を AWS が信頼する設定を 1 度だけ作れば、以後ジョブごとに 15 分だけ有効な一時クレデンシャル でロールを assume できます。鍵が漏れる経路がそもそも存在しません。
この章の目次
なぜ OIDC か
| 従来:アクセスキー方式 | OIDC:キーレス方式 | |
|---|---|---|
| 保存場所 | GitHub Secrets に AKIA... | 保存しない |
| 有効期限 | ローテーションするまで永続 | 15 分(自動失効) |
| 漏洩リスク | 誰かが Secrets を読むとアウト | そもそも秘密がない |
| 監査 | キー作成者しか追えない | STS の AssumeRoleWithWebIdentity が CloudTrail に残る |
仕組み(trust の流れ)
- GitHub Actions のジョブが実行されると、GitHub が OIDC トークン (JWT) を発行する。中身に「どのリポジトリの・どのブランチで・どのワークフローが」走っているかが入る
- ワークフローは
aws-actions/configure-aws-credentials@v4を呼び、その JWT を AWS STS に渡す - AWS は IAM に登録された OIDC プロバイダ(GitHub)を信頼しているので、JWT の署名を検証 → 中身(sub クレーム)が IAM ロールの trust policy に合致するかチェック
- 合致すれば STS が一時クレデンシャル(AccessKeyId / SecretAccessKey / SessionToken)を発行。これがジョブの環境変数に流し込まれる
- 以後、その 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 に詰めてくる文字列です。これを StringEquals や StringLike で絞ることで 「どのジョブが assume できるか」 を制御します。
| パターン | 意味 |
|---|---|
repo:org/repo:ref:refs/heads/main | main ブランチからの実行のみ |
repo:org/repo:pull_request | PR 経由の実行のみ |
repo:org/repo:environment:production | environment: 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 でロールが守られても、ロールが強すぎれば結局壊せます。