はじめに
GitHub ActionsのOIDCトークンの sub クレームは、これまで repo:組織名/リポジトリ名:... の形式でした。2026年7月15日以降に作成されたリポジトリでは、オーナーIDとリポジトリIDを含む「不変(immutable)」な形式が既定になっています。
AWSのIAMロールの信頼ポリシーで sub を完全一致やワイルドカードで書いていると、新しい形式のトークンではAssumeRoleに失敗する可能性があります。この記事では、変更内容と、Terraformでの信頼ポリシーの書き方をまとめます。
本記事の仕様は、GitHub公式ドキュメント「OpenID Connect reference」(2026-11-09時点)を根拠にしています。
前提・バージョン
- Terraform: v1.17.1
- AWS Provider: v6.72.0
-
確認内容: 記事中のHCLは、上記のバージョンで
terraform fmt -checkとterraform validateを通しています。
何が変わるのか
OIDCの仕様では、sub は局所的に一意で、再割り当てされないことが求められます。従来の形式は名前だけで構成されていました。そのため、組織名やリポジトリ名が再利用されると、別のオーナーが同じ sub を作れる余地がありました。これを防ぐため、名前にIDを併記します。
| 項目 | 形式 |
|---|---|
| 構文 | repo:OWNER@OWNER-ID/REPO@REPO-ID:ref:refs/heads/BRANCH |
| 従来 | repo:octo-org/octo-repo:ref:refs/heads/main |
| 不変 | repo:octo-org@123456/octo-repo@456789:ref:refs/heads/main |
@ は、GitHubのユーザー名とリポジトリ名に使えない文字なので、区切りとして使われています。
どのリポジトリが対象か
- 2026/07/15以降に作成したリポジトリは、不変形式が既定になっています。
- 2026/07/15より前に作成したリポジトリは、オプトインするまで従来形式のままです。オプトインは、組織またはリポジトリ単位で、OIDC設定のUIかREST APIから行います。
- 2026/07/15以降にリポジトリをリネームまたは移管した場合も、不変形式に切り替わります。
- GitHub Enterprise Serverは対象外です。
include_claim_keys で sub をカスタマイズしている場合でも、不変形式のリポジトリでは repo 部分に owner_id と repo_id が必ず含まれます。これらを外すことはできません。
信頼ポリシーへの影響
信頼ポリシーの sub 条件は、リポジトリが使っている形式に合わせる必要があります。
従来: repo:octo-org/octo-repo:ref:refs/heads/main
不変: repo:octo-org@123456/octo-repo@456789:ref:refs/heads/main
IDは次のコマンドで取得できます。
gh api repos/octo-org/octo-repo --jq '{repo_id: .id, owner_id: .owner.id}'
Terraformでの書き方
1. IDプロバイダー
# 既に作成してある場合はdataブロックを使用してください。
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
}
2. 信頼ポリシー(不変形式)
オーナーIDとリポジトリIDは、変数で受け取ります。
variable "github_owner" { type = string }
variable "github_owner_id" { type = string }
variable "github_repo" { type = string }
variable "github_repo_id" { type = string }
data "aws_iam_policy_document" "github_trust" {
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.github.arn]
}
condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:aud"
values = ["sts.amazonaws.com"]
}
condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:sub"
values = [
"repo:${var.github_owner}@${var.github_owner_id}/${var.github_repo}@${var.github_repo_id}:ref:refs/heads/main",
]
}
}
}
resource "aws_iam_role" "github_actions" {
name = "github-actions-deploy"
assume_role_policy = data.aws_iam_policy_document.github_trust.json
}
3. ブランチやenvironmentをまとめて許可する場合
StringLike とワイルドカードを使う場合も、IDを含めた形で書きます。
condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = [
"repo:${var.github_owner}@${var.github_owner_id}/${var.github_repo}@${var.github_repo_id}:*",
]
}
:* は、どのブランチ、環境、PRからでもAssumeRoleできるようにします。本番ロールでは範囲を絞ってください。
4. 移行期間は両方の形式を許可する
リポジトリを不変形式に切り替える前に信頼ポリシーを更新しておくと、切り替え時にAssumeRoleが失敗するのを避けられます。values に両方の形式を並べます。
condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:sub"
values = [
"repo:${var.github_owner}/${var.github_repo}:ref:refs/heads/main",
"repo:${var.github_owner}@${var.github_owner_id}/${var.github_repo}@${var.github_repo_id}:ref:refs/heads/main",
]
}
移行が終わったら、従来形式の行を削除します。
ハマりどころ
-
IDの取り違え:
owner_idは組織またはユーザーのID、repo_idはリポジトリのIDです。 -
リネームや移管: 2026/07/15以降にリネームや移管をすると、
subが不変形式に変わります。名前だけを書いた信頼ポリシーは通らなくなります。 -
ワイルドカード:
repo:octo-org/*のような書き方は、不変形式では一致しません。 -
環境のカスタマイズ:
include_claim_keysを使っていても、IDは外せません。
おわりに
不変形式では、名前の再利用によるなりすましを防げます。一方、信頼ポリシーはIDを含めて書き直す必要があります。新規リポジトリは不変形式が既定になっているので、TerraformのモジュールにオーナーIDとリポジトリIDを渡す設計にしておくと移行が楽です。