GitHub Actions OIDC の仕組み — JWT で AWS を静的キーレス操作
GitHub Actions から AWS を操作する時、過去は IAM ユーザーのアクセスキーを GitHub Secrets に置く方式が主流でした。今は OIDC + AssumeRole でアクセスキーなしに一時クレデンシャルを取得する方式が標準。本記事ではその仕組みと、Terraform での IAM Role 設定を解説します。
静的アクセスキーの危険性
GitHub Secrets に AWS_ACCESS_KEY_ID と AWS_SECRET_ACCESS_KEY を入れる方式の問題点:
- 有効期限が無い: ローテーションしないと永続的に使える
- 漏洩リスク: ログに出力される事故・誤って Issue に貼る事故が定期的に話題になる
- 権限スコープが粗い: 「このリポジトリの main ブランチからのみ」のような細かい制御が困難
- 監査が難しい: 誰が・どこから・いつ使ったかが追跡しづらい
OIDC は これらをすべて構造的に解決 します。
OIDC + AssumeRole の流れ
処理は 5 ステップ:
- GitHub Actions のワークフロー実行時、OIDC ID トークン (JWT) を GitHub から発行
- JWT には
sub: repo:owner/repo:ref:refs/heads/main等の claim(主張)が入っている - このトークンを AWS STS の
AssumeRoleWithWebIdentityAPI に渡す - AWS は IAM Role の Trust Policy で「このトークンは信頼できるか」を検証
- OK なら 1 時間有効 の AccessKey / SecretKey / SessionToken を返す
Runner はこの一時クレデンシャルを使って AWS API を叩きます。終わったらクレデンシャルは破棄。永続的なキーは存在しません。
JWT の claim を見てみる
GitHub が発行する OIDC JWT には次のような claim が含まれます:
{
"iss": "https://token.actions.githubusercontent.com",
"aud": "sts.amazonaws.com",
"sub": "repo:iigtn/iigtn-platform:ref:refs/heads/main",
"repository": "iigtn/iigtn-platform",
"ref": "refs/heads/main",
"ref_type": "branch",
"actor": "<github-username>",
"workflow": "frontend-deploy",
"run_id": "12345678",
...
}
sub claim が「誰が・どこのリポジトリの・どのブランチから」を表します。これを Trust Policy で検証することで、「特定のリポジトリの main ブランチからの実行だけ許可」 が実現できます。
本シリーズの ci_oidc モジュール
このモジュールが作るリソースは 3 個:
aws_iam_openid_connect_provider— GitHub を AWS の IdP として登録aws_iam_role— GitHub Actions が AssumeRole する役割aws_iam_role_policy— Role になった結果できる API(最小権限)
OIDC Provider の登録
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}
| 引数 | 意味 |
|---|---|
url | GitHub の OIDC token 発行 URL(固定値) |
client_id_list | audience。AWS STS は sts.amazonaws.com 固定 |
thumbprint_list | GitHub IdP の TLS 証明書サムプリント。AWS が内部で自動検証する |
1 アカウントに 1 個だけ作れば良い。複数のリポジトリで共有可能。
Trust Policy(誰が AssumeRole できるか)
Trust Policy は「この Role を AssumeRole できる Principal の条件」を定義:
data "aws_iam_policy_document" "trust" {
statement {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.github.arn]
}
# 条件 1: aud (audience) は AWS STS のみ
condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:aud"
values = ["sts.amazonaws.com"]
}
# 条件 2: sub (subject) は指定 owner/repo の指定ブランチのみ
condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = [
for branch in var.allowed_branches :
"repo:${var.github_owner}/${var.github_repo}:ref:refs/heads/${branch}"
]
}
}
}
これにより:
- 他人のリポジトリは AssumeRole 不可
- 自リポジトリの PR からも AssumeRole 不可(main ブランチ等のみ許可)
- fork 先からも AssumeRole 不可
Permissions Policy(Role になって何ができるか)
本シリーズの GitHub Actions は「フロントエンドのデプロイ」のみ行うので、必要最低限の権限:
data "aws_iam_policy_document" "deploy" {
# S3: バケット情報の読み取り
statement {
actions = ["s3:ListBucket", "s3:GetBucketLocation"]
resources = [var.deploy_bucket_arn] # この 1 バケットだけ
}
# S3: オブジェクト操作 (sync で必要)
statement {
actions = ["s3:GetObject", "s3:PutObject",
"s3:DeleteObject", "s3:PutObjectAcl"]
resources = ["${var.deploy_bucket_arn}/*"]
}
# CloudFront: 特定 Distribution の Invalidation のみ
statement {
actions = [
"cloudfront:CreateInvalidation",
"cloudfront:GetInvalidation",
"cloudfront:ListInvalidations"
]
resources = [var.cloudfront_distribution_arn] # この 1 Distribution だけ
}
}
このポリシーでは 他のバケット・他の Distribution・IAM・Lambda・etc には一切触れない。万が一 Role が乗っ取られても被害が限定的。
IAM Role 本体
resource "aws_iam_role" "deploy" {
name = var.role_name # iigtn-github-actions-deploy
description = "GitHub Actions OIDC deploy role for ${var.github_owner}/${var.github_repo}"
assume_role_policy = data.aws_iam_policy_document.trust.json
tags = var.tags
}
resource "aws_iam_role_policy" "deploy" {
name = "${var.role_name}-policy"
role = aws_iam_role.deploy.id
policy = data.aws_iam_policy_document.deploy.json
}
description は ASCII / Latin-1 のみ。日本語を入れると ValidationError で apply 失敗します。本シリーズで実際にハマりました。
GitHub Actions ワークフロー側の最小実装
permissions:
id-token: write # ← OIDC token を発行するために必須
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::XXXXXXXXXXXX:role/iigtn-github-actions-deploy
aws-region: ap-northeast-1
id-token: write permission を忘れると、OIDC token 発行が拒否されて AssumeRole が失敗します。これも初見でハマるポイント。
動作確認の見方
- name: Verify caller identity
run: aws sts get-caller-identity
# 出力例
{
"UserId": "AROAxxxxx:gha-frontend-deploy-12345", ← Session ID 付き = AssumeRole 経由
"Account": "XXXXXXXXXXXX",
"Arn": "arn:aws:sts::XXXXXXXXXXXX:assumed-role/iigtn-github-actions-deploy/gha-frontend-deploy-12345"
}
ARN に assumed-role が含まれていれば「Role を AssumeRole した状態」が確認できます。
OIDC のメリット再掲
| 項目 | 静的キー | OIDC |
|---|---|---|
| 有効期限 | 無期限 | 1 時間 |
| 漏洩時の被害 | 大(永続) | 限定(1 時間で失効) |
| repo / branch 限定 | 困難 | Trust Policy で完結 |
| ローテーション | 手動 | 毎回自動 |
| 監査 | 難 | CloudTrail で session 単位で追跡可能 |
静的キーをまだ使っている人は、今すぐ OIDC に移行する価値があります。本シリーズの実装をテンプレに使えば 30 分で切替可能。
次の記事
OIDC で IAM Role が用意できたところで、次の記事では実際の GitHub Actions ワークフロー YAML を書き、git push から CloudFront 反映までを自動化します。
📚 用語集
- OIDC (OpenID Connect)
- OAuth 2.0 の上に乗った認証プロトコル。ID トークン (JWT) を発行する標準仕様。
- JWT (JSON Web Token)
- Header / Payload / Signature の 3 部からなる署名付きトークン。Payload に claim を入れて、署名で改ざん検知する。
- claim (クレーム)
- JWT の Payload に入る「主張」。
sub(誰)/aud(audience)/exp(有効期限)等。 - sub claim
- 「subject = この JWT が誰を表すか」の claim。GitHub の場合
repo:owner/repo:ref:refs/heads/branch形式。 - aud claim (audience)
- 「この JWT は誰宛か」の claim。AWS STS 経由で使う場合は
sts.amazonaws.com固定。 - STS (Security Token Service)
- AWS の一時クレデンシャル発行サービス。AssumeRole 系 API がここで提供される。
- AssumeRoleWithWebIdentity
- OIDC や OAuth 等の Web Identity を使って IAM Role を Assume する STS API。
- IdP (Identity Provider)
- 認証情報を発行する側。GitHub Actions OIDC では GitHub が IdP。
- OIDC Provider (AWS IAM)
- AWS 側で「この IdP を信頼する」と登録する IAM リソース。
aws_iam_openid_connect_provider。 - Federated Principal
- IAM の Principal タイプの 1 つで、外部 IdP からの認証を受け入れる。
- Trust Policy (信頼ポリシー)
- 「この IAM Role を誰が AssumeRole できるか」を定義するポリシー。
aws_iam_role.assume_role_policyに書く。 - Permissions Policy (権限ポリシー)
- 「IAM Role になった結果、何ができるか」を定義するポリシー。Role に attach する。
- 最小権限 (Least Privilege)
- 「必要最小限の権限だけ与える」セキュリティ原則。漏洩時の被害を限定する。
- Resource ARN
- AWS リソースを一意に指す文字列。Permissions Policy の Resource 句で「このバケットだけ」「この Distribution だけ」と絞る。
- thumbprint
- TLS 証明書のフィンガープリント(ハッシュ)。OIDC Provider 登録時に GitHub IdP の thumbprint を指定する(AWS が自動検証)。
- id-token: write permission
- GitHub Actions のワークフロー permission の 1 つ。これが無いと OIDC token 発行が拒否される。OIDC を使う Job では必須。
- aws-actions/configure-aws-credentials@v4
- AWS が公式提供する GitHub Actions。OIDC で AssumeRole し、後続 step に一時クレデンシャルを env vars として注入。
- session credentials
- AssumeRole で発行される一時クレデンシャル。AccessKey / SecretKey / SessionToken の 3 点セット。有効期限あり。
- CloudTrail
- AWS の API コール履歴サービス。AssumeRole の session ID 単位で「誰が何を叩いたか」が追跡できる。