
GitHub ActionsのOIDCでAWS認証が通らない時にsubクレームを確認して信頼ポリシーを直してみた
こんにちは、つくぼし(tsukuboshi0755)です!
GitHub ActionsからAWSへOIDCで認証する時、IAMロールの信頼ポリシーには組織名とリポジトリ名でsubの条件を書くのが定番でした。
ところが2026年7月15日より後に作成したリポジトリでは、この定番の書き方のままだとCIのAWS認証が通りません。
既存のリポジトリでは動いている設定なのに新しいリポジトリだけ落ちる時、何を疑えばよいか分からず止まってしまう事はないでしょうか?
そこで本記事では、その原因であるGitHubの仕様変更、immutable subject claimによる認証エラーを再現し、subクレームを確認して信頼ポリシーを直すまでの手順について紹介します!
immutable subject claimとは
従来のsubには、オーナー(組織またはユーザー)名とリポジトリ名がそのまま入っていました。
この形式には名前の再利用に伴う弱点があります。
リポジトリを削除・リネームした後に第三者が同じ名前を取得すると、そのリポジトリのワークフローが同一のsubを持つトークンを受け取れるため、AWS側のロールを引き受けられてしまいます。
この弱点を塞ぐため、新形式では名前に続けて不変の数値IDが入るようになりました。
主な変更点は以下の通りです。
| 項目 | 内容 |
|---|---|
| 新形式の構文 | repo:OWNER@OWNER-ID/REPO@REPO-ID:ref:refs/heads/BRANCH |
区切り文字に@を使う理由 |
GitHubのユーザー名とリポジトリ名に@が現れ得ないため |
| 自動適用の対象 | 2026年7月15日より後に作成されたリポジトリ。同日より後のリネーム・移管も新形式になる |
| 既存リポジトリ | 明示的にオプトインした場合のみ新形式になる |
| オプトインの方法 | Organization単位・リポジトリ単位のどちらでも、OIDC設定のUIかREST APIで切り替える |
このシナリオを防ぐために、2026 年 7 月 15 日以降に作成されたリポジトリでは、所有者 ID とリポジトリ ID の両方を含む不変の既定のサブジェクト形式が使用されるようになりました。
構文:
repo:OWNER@OWNER-ID/REPO@REPO-ID:ref:refs/heads/BRANCH変更できないサブジェクト要求を選択しない限り、2026 年 7 月 15 日より前に作成されたリポジトリは以前の形式を維持します。 OIDC 設定 UI または REST API を使用して、組織またはリポジトリ レベルでオプトインできます。
2026 年 7 月 15 日以降に作成されたリポジトリ、または変更できないサブジェクト要求にオプトインしたリポジトリの場合、
sub要求には、変更できない例に示すようにowner_idとrepo_idが含まれます。 リポジトリで使用されている形式に合わせて信頼ポリシーを更新します。
既存のリポジトリは旧形式のままなので、新しく作ったリポジトリだけが新形式のsubを受け取り、「特定のリポジトリだけ落ちる」という見え方になります。
なおsubから数値IDを外す事はできず、REST APIのinclude_claim_keysでクレームをカスタマイズしてもowner_idとrepo_idは残ります。
そのため対処は、信頼ポリシー側を新形式へ合わせる事になるでしょう。
ただし新形式に合わせた信頼ポリシーは、旧形式のsubを送る既存リポジトリとは一致しません。
同じテンプレートを既存リポジトリにも使っているなら、オプトインで新形式へ揃えるかを先に決めておくと良いでしょう。
前提条件
本記事のデモを再現するには、以下を用意してください。
- CloudFormationでIAMロールとOIDCプロバイダを作成できる権限を持つAWSアカウント
- 2026年7月15日より後に作成したGitHubリポジトリ(以降
octo-org/octo-repoとします) - 上記リポジトリのデフォルトブランチが
mainである事
なおOIDCプロバイダは同じURLのものを1つのAWSアカウントに複数作成できません。
既にtoken.actions.githubusercontent.comのプロバイダがあるアカウントでは、後述のテンプレートからGitHubOIDCProviderリソースを外し、既存プロバイダのARNを参照するよう書き換えてください。
本記事では以下のバージョンで検証しています。
| 項目 | バージョン |
|---|---|
| aws-actions/configure-aws-credentials | v6 |
| GitHub Actionsランナー | ubuntu-24.04 |
やってみた
まず従来通りの旧形式の信頼ポリシーでロールを作り、新しいリポジトリから認証が失敗する事を再現します。
その上でsubの実物を確認し、信頼ポリシーを新形式に直します。
旧形式の信頼ポリシーでIAMロールを作成する
OIDCプロバイダとIAMロールを、1つのCloudFormationテンプレートでまとめて作成します。
信頼ポリシーのsubは、これまで一般的だった組織名とリポジトリ名だけの旧形式で書いておきます。
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
GitHubOrg:
Type: String
Default: octo-org
RepositoryName:
Type: String
Default: octo-repo
Resources:
GitHubOIDCProvider:
Type: AWS::IAM::OIDCProvider
Properties:
Url: https://token.actions.githubusercontent.com
ClientIdList:
- sts.amazonaws.com
DemoRole:
Type: AWS::IAM::Role
Properties:
RoleName: iamrole-for-oidc-demo
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Federated: !Ref GitHubOIDCProvider
Action: sts:AssumeRoleWithWebIdentity
Condition:
StringEquals:
token.actions.githubusercontent.com:aud: sts.amazonaws.com
token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/${RepositoryName}:ref:refs/heads/main
Outputs:
RoleArn:
Value: !GetAtt DemoRole.Arn
ロールには許可ポリシーを付けていません。
動作確認で呼ぶsts get-caller-identityは権限を必要としないため、認証が通るかどうかだけを確かめるには信頼ポリシーだけで足ります。
以下のコマンドでデプロイします。
GitHubOrgとRepositoryNameは、ご自身の組織名とリポジトリ名に置き換えてください。
aws cloudformation deploy \
--template-file bootstrap.yaml \
--stack-name github-oidc-demo \
--parameter-overrides GitHubOrg=octo-org RepositoryName=octo-repo \
--capabilities CAPABILITY_NAMED_IAM
デプロイ後、IAMコンソールでロールの「信頼関係」タブを開くと、subが旧形式で登録されている事を確認できます。

赤枠のsubは組織名とリポジトリ名を/で区切っただけの値で、数値IDは含まれていません。
ワークフローを作成する
リポジトリに以下のワークフローを追加します。
今回は検証用として、subクレームを確認する診断ステップDebug OIDC claimsも入れています。
role-to-assumeには、スタックの出力RoleArnの値を指定してください。
name: OIDC demo
on:
workflow_dispatch:
permissions:
id-token: write
contents: read
jobs:
assume-role:
runs-on: ubuntu-24.04
steps:
- name: Configure AWS credentials
id: aws-credentials
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::<自アカウントID>:role/iamrole-for-oidc-demo
aws-region: ap-northeast-1
mask-aws-account-id: true
- name: Debug OIDC claims
if: failure() && steps.aws-credentials.outcome == 'failure' && runner.debug == '1'
run: |
TOKEN=$(curl -sS \
-H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=sts.amazonaws.com" \
| jq -r '.value')
PAYLOAD=$(echo "${TOKEN}" | cut -d. -f2 | tr '_-' '/+')
case $(( ${#PAYLOAD} % 4 )) in
2) PAYLOAD="${PAYLOAD}==" ;;
3) PAYLOAD="${PAYLOAD}=" ;;
esac
echo "${PAYLOAD}" | base64 -d | jq '{sub, aud, repository, ref}'
- name: Get caller identity
run: aws sts get-caller-identity
Debug OIDC claimsは、認証が失敗した時にsubの実物を確認するため、今回の検証用に入れた診断ステップです。
IDトークンは取得した時点でマスク対象になるため、ACTIONS_STEP_DEBUGを有効にするだけではsubがログに出てきません。
そこでACTIONS_ID_TOKEN_REQUEST_TOKENとACTIONS_ID_TOKEN_REQUEST_URLでトークンを取得し、ペイロード部分だけをデコードしています。
ログはリポジトリを読めるユーザーなら誰でも閲覧できるため、診断ステップにはログに残る情報を絞る工夫を2点入れています。
- JWTは有効期限内ならそのまま認証に使えるため、トークン全体は出さず、
cut -d. -f2で取り出したペイロードをjqで必要なクレームだけに絞る - 診断ステップは、認証が失敗し、かつデバッグ実行である時だけ動くよう
ifで条件を付ける(参照のため、認証のステップにはidを付ける)
なお診断ステップは実運用のワークフローに常設せず、認証が通らない時に一時的に追加し、原因が分かったら外してください。
最後のGet caller identityは、ロールを引き受けられたかを確かめるためのステップです。
その出力にはAWSアカウントIDが含まれるため、Configure AWS credentialsにmask-aws-account-id: trueを指定し、ログ上でマスクしています。
認証エラーを再現する
リポジトリの「Actions」タブからOIDC demoワークフローを選び、「Run workflow」で実行します。
するとConfigure AWS credentialsのステップが、以下のエラーでリトライを繰り返した末に失敗します。
Retry AssumeRole: attempt 1 of 12 failed: Could not assume role with OIDC: Not authorized to perform sts:AssumeRoleWithWebIdentity. Retrying after 31ms.

attempt 1 of 12の時点から同じ文言で拒否されており、ステップは失敗の表示で終わっています。
このエラー文言は、信頼ポリシーの条件が合わない場合にも、ロールに設定したIdPのARNが誤っている場合にも同じように返ってきます。
そのため、エラー文言だけでは原因を絞り込めません。
OIDC認証が通らない時に確認する箇所は、主に以下の3つです。
| 確認箇所 | 確認方法 |
|---|---|
ワークフローのpermissions |
id-token: writeが付いているか。付いていないとIDトークン自体を取得できない |
信頼ポリシーのaud条件 |
トークンのaud(configure-aws-credentialsの既定はsts.amazonaws.com)と一致するか |
信頼ポリシーのsub条件 |
トークンのsubと、StringEqualsなら完全一致、StringLikeならパターンで一致するか |
今回のワークフローはpermissionsを付けており、audもテンプレート通りです。
残るsubは、GitHub側で何が送られているかを見なければ一致しているか判断できません。
デバッグ実行でsubクレームを確認する
失敗した実行の画面右上にある「Re-run jobs」から「Re-run all jobs」を選び、「Enable debug logging」にチェックを入れて再実行します。

チェックを入れて再実行すると、各ステップのログに##[debug]で始まるデバッグ用の行が加わります。
デバッグ実行ではrunner.debugが1になるため、認証の失敗後にDebug OIDC claimsステップが動きます。
新形式に該当していれば、出力は以下の通りです。
{
"sub": "repo:octo-org@123456/octo-repo@456789:ref:refs/heads/main",
"aud": "sts.amazonaws.com",
"repository": "octo-org/octo-repo",
"ref": "refs/heads/main"
}

repositoryは組織名/リポジトリ名のままで、数値IDが加わっているのはsubだけです。
対して信頼ポリシーのStringEqualsに登録されているのは、組織名とリポジトリ名だけの旧形式です。
repo:octo-org/octo-repo:ref:refs/heads/main
トークンのsubには組織名とリポジトリ名のうしろに@付きの数値が入っているため、完全一致になりませんでした。
audとrefのブランチは信頼ポリシーの条件と一致しており、残る差分はsubだけだと確定できます。
信頼ポリシーを新形式に修正する
信頼ポリシーを新形式へ合わせるには、subの条件に組織IDとリポジトリIDを埋め込む必要があります。
手元の検証では、2つのIDで扱いを分けてパラメータ化しました。
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
GitHubOrg:
Type: String
Default: octo-org
GitHubOrgId:
Type: String
Description: 組織ID
Default: "123456"
AllowedPattern: ^[0-9]+$
RepositoryName:
Type: String
Default: octo-repo
RepositoryId:
Type: String
Description: リポジトリID。`*` を指定すると任意のリポジトリIDを受け付ける
Default: "*"
AllowedPattern: '^([0-9]+|\*)$'
Resources:
GitHubOIDCProvider:
Type: AWS::IAM::OIDCProvider
Properties:
Url: https://token.actions.githubusercontent.com
ClientIdList:
- sts.amazonaws.com
DemoRole:
Type: AWS::IAM::Role
Properties:
RoleName: iamrole-for-oidc-demo
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Federated: !Ref GitHubOIDCProvider
Action: sts:AssumeRoleWithWebIdentity
Condition:
StringEquals:
token.actions.githubusercontent.com:aud: sts.amazonaws.com
StringLike:
token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}@${GitHubOrgId}/${RepositoryName}@${RepositoryId}:ref:refs/heads/main
Outputs:
RoleArn:
Value: !GetAtt DemoRole.Arn
組織IDは一度調べれば済む値なので、パラメータのデフォルト値に実際の値を書き込む前提にしています。
一方でリポジトリIDは普段目にする機会が少なく、セットアップのたびに各自へ調べさせるのは現実的でないため、デフォルトを*にしました。
ただし*のままでは、同じ組織で同じ名前のリポジトリを作り直すと、IDが変わっても条件を通過します。
StringLikeはワイルドカードを含まない値を完全一致で照合するため、厳密に締めたい環境ではリポジトリIDに実際の数値を渡してください。
またStringLikeの*は/を含む任意の文字列に一致するため、12*のような値を埋めると別のIDまで通ってしまいます。
RepositoryIdにAllowedPatternを付け、受け付ける値を数値列か*単体に制限しているのはこのためです。
IDはGET /repos/{owner}/{repo}のレスポンスのidとowner.idで確認できますが、デモなら診断ステップのsubに出た値を使うのが手軽でしょう。
テンプレートを修正したら、組織IDを渡してスタックを更新してください。
aws cloudformation deploy \
--template-file bootstrap.yaml \
--stack-name github-oidc-demo \
--parameter-overrides GitHubOrg=octo-org GitHubOrgId=123456 RepositoryName=octo-repo \
--capabilities CAPABILITY_NAMED_IAM
更新後にロールの「信頼関係」タブを開くと、subの条件がStringLikeに移り、ID入りの新形式になっている事を確認できます。

組織IDは数値で固定され、リポジトリIDの位置だけが*になっています。
動作確認
信頼ポリシーを修正した後、「Run workflow」からワークフローを実行します。
今度はConfigure AWS credentialsが成功し、Get caller identityステップの出力は以下の通りになります。
{
"UserId": "AROAXXXXXXXXXXXXXXXXX:GitHubActions",
"Account": "***",
"Arn": "***"
}

mask-aws-account-idによって、AccountとアカウントIDを含むArnは値全体が***に置き換わっています。
ロールを引き受けられた根拠は、Configure AWS credentialsの成功と、UserIdがロールを表すAROAで始まる値になっている事です。
認証が成功しているため、Debug OIDC claimsステップはスキップされます。
通常の実行ではクレームがログに残らない事も、この画面で確認できます。
最後に
今回は、GitHub ActionsのOIDCでAWS認証が通らない原因であるimmutable subject claimによるエラーを再現し、subクレームを確認して信頼ポリシーを直すまでの手順について紹介しました。
新しいリポジトリだけ認証が通らない時は、テンプレートやワークフローではなく、GitHubが発行するsubの形式が変わっているのかもしれません。
IDトークンはログでマスクされるため、subの実物を見られるかどうかで原因の切り分けにかかる時間が大きく変わります。
同じように新しいリポジトリだけ認証が通らずに困っている方は、ぜひ本記事の診断ステップでsubの実物を確認するところから試してみてください!
以上、つくぼし(tsukuboshi0755)でした!









