GitHub ActionsでAWSなどへデプロイするとき、シークレットをどう渡すかで迷う人は多いです。
IAMユーザーのAccess KeyをGitHub Secretsに登録する。workflowからそのシークレットを読み出して使う、という流れが定番です。
一方で、GitHub ActionsはOIDC(OpenID Connect)に対応しており、job実行のたびに短命なJWTを発行します。AWS が JWT を検証したうえで短命なシークレットを渡す方式も取れます。長期シークレットをリポジトリに預けずに済むので、デプロイのセキュリティを上げたいときに検討する価値があります。
以下では、用語、従来方式の弱点、OIDCのメリット、認証フロー、必要な設定を順に整理します。全体像は GitHub 公式の解説 も参考になります。
用語
GitHub OIDC
GitHub Actionsのjobが走ると、GitHubが短命なJWT(JSON Web Token)を発行します。GitHubが認証基盤(Identity Provider)の役割を担い、job実行のたびに「今このjobが動いている」という情報をJWTに載せて渡します。
長期のAccess KeyをSecretsに置く必要はなく、jobのたびに新しいJWTが使われます。
Trust Policy
AWS IAMロールの設定の一つで、「誰がこのロールを引き受け(AssumeRole)られるか」を決めます。GitHub OIDC連携では、Trust PolicyがJWTの内容(どのリポジトリから、どのブランチで、など)をチェックするゲートになります。
条件を満たした JWT だけがロールを引き受けられ、短命なシークレットが渡されます。
Permission Policy
AWS IAMロールのもう一つの設定で、「ロールを引き受けたあと、どの AWS 操作が許されるか」を決めます。Trust Policy が入口(AssumeRole)を制御するのに対し、Permission Policy は引き受け後の S3 や IAM など API 呼び出しの範囲を決めます。
Trust Policy と混同しやすいですが、別の設定です。本番ロールに S3 書き込み、プレビューロールに読み取りだけ、といった AWS 操作の権限 は Permission Policy で分けます。
どちらも最終的には AWS のシークレット
GitHub Actions から AWS API を呼ぶとき、実際にリクエストへ載せるのは AWS から発行されたシークレット(Access Key ID と Secret Access Key のペア)です。
長期シークレット方式では、IAM ユーザー作成時に得たシークレットを GitHub Secrets に置き、workflow がそのまま使います。OIDC 方式では、GitHub が JWT を発行し、AWS が Trust Policy で JWT を検証したうえで、短命なシークレットを新たに発行します。
API を呼ぶ段階で AWS がやっているのは、シークレットの検証だけです。長期か短命かは意識せず、有効なシークレットかどうかを見ています。方式の違いは、シークレットをどこに置き、どれくらいの寿命で発行するかにあります。
JWT は API 呼び出し用のシークレットではなく、AWS に身元を示して短命なシークレットをもらうための証明です。
従来のやり方:長期シークレットを GitHub に登録する
IAMユーザーのAccess Keyを発行し、GitHub Secretsに登録してworkflowから使う、という流れがよくあります。
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
手軽に動かせますが、一度 Secrets に置いたシークレットは、無効化するまでずっと使えます。
ざっくりした流れ
flowchart TB
subgraph pre ["① 事前準備(1回だけ)"]
direction LR
iam["IAM ユーザー"] --> key["Access Key を発行"]
end
secrets[("GitHub Secrets<br/>長期シークレットを常時保管")]
subgraph run ["② job 実行時"]
direction LR
wf["GitHub Actions workflow"] --> aws["AWS API"]
end
subgraph issue ["③ 問題になりやすい点"]
direction LR
local["ローカル PC 等"] --> reuse["同じ Access Key で<br/>workflow 外からも API 呼び出し"]
end
key --> secrets
secrets -->|"毎回読み出す"| wf
secrets -.->|"シークレットをコピーして使い回し"| local
reuse -.-> aws
pre --> secrets
図: Access Key は Secrets に常時保管される。workflow 外のローカル等からも同じシークレットが使える。
同じAccess Keyを、workflow以外(ローカル開発や別ツール)でも使い回せてしまう点が、運用上の盲点になりやすいです。
弱点
| 観点 | 起きやすいこと |
|---|---|
| 有効期限 | 無効化・ローテーションするまで永続 |
| ローテーション | AWS・Secrets・他環境を手動で揃える必要がある |
| 権限設計 | 1つのシークレットを本番・PR・複数リポジトリで使い回しがち |
| 監査 | CloudTrailはIAMユーザー単位。GitHubのjobとは別系統 |
長期シークレットそのもののリスク
Access Keyは有効な間、何度でもAPIを呼べます。漏えいしても無効化するまで被害が続く、というリスクがあります。
キーローテーションの面倒さ
推奨される定期ローテーションは、AWS・GitHub Secrets・ローカル環境など複数箇所を揃える作業になります。更新漏れがどこか一つでも残りやすいです。
権限設計が粗くなりがち
デプロイ用IAMユーザー1つに広い権限を付け、本番とPRプレビューで同じシークレットを使う、といった形になりがちです。用途ごとに必要最小限へ絞りにくくなります。
AWS側のログだけでは、どのjobから来た操作かわからない
CloudTrailにはIAMユーザーとしてのAPI呼び出しは残りますが、認証主体は常にそのAccess Keyです。GitHub Actionsのどのworkflow・jobか、ローカルからの操作かは、AWS側のログだけでは区別できません。
比較:長期シークレット vs OIDC

| 観点 | 長期シークレット(Secrets) | OIDC |
|---|---|---|
| GitHubに置くもの | Access Key(長期) | AWS シークレットは不要(ロールARNのみ) |
| 有効期限 | 無効化まで永続 | job単位で自動失効 |
| ローテーション | 手動(AWS + Secrets + 他) | 短命シークレットは自動発行 |
| 権限の切り方 | IAMユーザー単位 | repository / ref / environment で分岐 |
| 監査ログ | IAMユーザーとして記録 | ロールセッション + claim / tags |
OIDCを使うメリット
前節の弱点に対して、OIDC では次のように変わります。
ざっくりした流れ
flowchart TB
subgraph pre ["① 事前準備(1回だけ)"]
direction LR
oidc["OIDC プロバイダ<br/>(AWS に登録)"]
role["IAM ロール<br/>+ Trust Policy"]
oidc --- role
end
subgraph run ["② job 実行時"]
direction TB
wf["GitHub Actions workflow"]
idp["GitHub OIDC<br/>(JWT 発行)"]
gate{{"Trust Policy<br/>claim を検証"}}
aws["AWS API"]
wf -->|"JWT を要求"| idp
idp -->|"短命 JWT"| wf
wf -->|"ロールを引き受け"| gate
gate -->|"短命シークレット"| wf
wf -->|"API 呼び出し"| aws
end
subgraph diff ["③ 長期シークレット方式との違い"]
direction LR
nokey["Secrets に<br/>AWS シークレットは不要"]
temp["job 終了で<br/>シークレットは失効"]
end
pre --> run
role -.->|"検証ルール"| gate
run --> diff
図: job ごとに JWT を発行し、Trust Policy 通過後にだけ短命シークレットが渡る。
GitHubに長期シークレットを置かず、job実行のたびに短命なシークレットだけをもらうのが、長期シークレット方式との大きな違いです。
| 従来の弱点 | OIDCでの変化 |
|---|---|
| 長期シークレットのリスク | SecretsにAccess Keyを置かない |
| ローテーションの手間 | 短命シークレットはjobごとに自動発行・失効 |
| 権限設計の粗さ | Trust Policyでrepo / ref / environmentを条件にできる |
| 監査の紐づけ | CloudTrailにロールセッション + claimを載せやすい |
長期シークレットをGitHubに置かなくてよい
AWS側でGitHubを信頼するOIDCプロバイダを設定し、workflowは実行のたびに短命なJWTを取得してロールを引き受けます。漏えいの対象になる「リポジトリに眠っている長期シークレット」がなくなります。
キーローテーションの手間が減る
GitHub側で AWS シークレットを回し続ける必要はありません。メンテの中心は、Trust PolicyとIAMロールの設計に移ります。
リポジトリ・ブランチなどで権限を切れる
IAMロールに付ける Permission Policy だけでは、「このリポジトリの main ブランチから来た job だけ」といった条件は書きにくいです。OIDC では JWT に repository や ref が自動で入るので、Trust Policy で repo / branch を条件にできます。
本番用と PR プレビュー用でロールを分け、Trust Policy で main 専用・PR 専用と入口を切り分けられます。repo / branch の条件は Trust Policy に書き、workflow は引き受けるロール ARN を指定するだけです。
AWS側にも、GitHub Actions由来の文脈を載せやすい
AssumeRoleWithWebIdentityで引き受けたセッションはCloudTrailに残り、claimをsession nameやsession tagsに載せる設定も取れます。どのリポジトリ・ブランチ・workflowからの操作かを、AWS側のログにも残しやすくなります。
OIDCの認証フロー
OIDC の流れは 事前準備(1回) と job 実行時(毎回) の2段階に分かれます。
【事前準備】AWS 側(Trust Policy)
「GitHub が発行した JWT で、
my-org/my-repoのmainブランチから来たやつだったら、この IAM ロールを貸していいよ」
まだ job は動いておらず、身元の条件だけをあらかじめ決めておく段階です。
【job 実行時】GitHub Actions → AWS
workflow:「JWT 持ってきました。
my-org/my-repoのmainから来た job です。ロールを引き受けさせてください」
GitHub OIDC が job ごとに JWT(身分証)を発行し、workflow がそれを AWS に見せに行きます。
【job 実行時】AWS 側
AWS:「JWT の
subを見るね…… Trust Policy の Condition と一致してる。OK、短命シークレットを渡すよ」
AWS は JWT の中身(sub や repository, ref など)と、事前準備で書いた Condition を照合します。一致すれば短命なシークレットが返り、S3 デプロイなどが実行できます。不一致の場合はここで拒否されます。
JWT(GitHub 発行)と短命シークレット(AWS 発行)は別物です。前者は「こういう job です」の申告、後者が AWS API を呼ぶためのシークレットです。
事前準備
workflow が動く前に、AWS 側と GitHub 側で信頼関係とロールを整えます。やることは次の表のとおりです。
| どこ | 何をする |
|---|---|
| AWS | GitHub の OIDC プロバイダ(token.actions.githubusercontent.com)を登録する |
| AWS | デプロイ用 IAM ロールを作り、Trust Policy で「どの repo / branch から来た JWT なら OK か」を書く |
| AWS | 同じロールに Permission Policy を付け、S3 書き込みなど 引き受けたあと何ができるか を決める |
| GitHub | workflow に permissions: id-token: write を付け、JWT を取得できるようにする |
| GitHub | configure-aws-credentials 等で 引き受けるロール ARN を指定する |
この段階では API は呼ばれません。GitHub が AWS を信頼する、AWS が GitHub 発行の JWT を信頼する、という 土台を作る作業 です。
job 実行時
workflow の job が走るたびに、次の流れで JWT が発行され、短命シークレットに交換されます。
sequenceDiagram
participant GH as GitHub Actions
participant IdP as GitHub OIDC Provider
participant AWS as AWS (IAM)
GH->>IdP: ① JWT を要求
IdP-->>GH: ② JWT を返す(sub, ref, repository など)
GH->>AWS: ③ JWT を提示して AssumeRoleWithWebIdentity
AWS->>AWS: ④ Trust Policy で claim を検証
AWS-->>GH: ⑤ 短命シークレットを返す
GH->>AWS: ⑥ 短命シークレットで S3 デプロイ等を実行
| 段階 | 誰が | 何が起きる |
|---|---|---|
| ① JWT 要求 | workflow → GitHub OIDC | job の文脈(repo, branch, workflow 等)を載せた JWT を発行してもらう |
| ② JWT 受け取り | GitHub OIDC → workflow | JWT(sub, ref, repository, workflow など)が job に渡る |
| ③ ロール引き受け | workflow → AWS | JWT と引き受ける IAM ロール ARN を AWS に送る |
| ④ 検証 | AWS IAM | Trust Policy と JWT の claim を照合。条件不一致ならここで拒否(シークレットはまだ返らない) |
| ⑤ シークレット発行 | AWS → workflow | 短命な Access Key / Session Token が job に返る |
| ⑥ API 実行 | workflow → AWS | 短命シークレットでデプロイ等を実行。job 終了後は失効 |
②の JWT(GitHub 発行)と ⑤ の短命シークレット(AWS 発行)は別物です。JWT は身元証明、短命シークレットが AWS API を呼ぶための資格情報です。
長期シークレット方式との違いは、⑥ の前に毎回 ①〜⑤ が挟まる 点です。Secrets からシークレットを読むのではなく、job ごとに JWT → 短命シークレット、という経路になります。
必要な設定
どこに何を書くかは、次の3つに分かれます。
| 設定 | どこ | 何を決める |
|---|---|---|
| Trust Policy | AWS(IAMロール) | どの repo / branch / environment から来た JWT なら、このロールを引き受けていいか |
| workflow | GitHub(.github/workflows/*.yml) | どの job が、どの IAM ロールを引き受けるか |
| Permission Policy | AWS(IAMロール) | ロールを引き受けたあと、S3 など AWS 上で何ができるか |
AWS:Trust Policy
IAM ロールの「信頼関係」タブに書く JSON が Trust Policy です。本番ロールなら main ブランチからだけ、という条件を書きます。
{
"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:my-org/my-repo:ref:refs/heads/main"
}
}
}
sub の形式は repo:<owner>/<repo>:ref:refs/heads/<branch> です。PR プレビュー用ロールなら refs/pull/* など、ロールごとに Trust Policy を分けます。GitHub Environment を使う場合は repo:my-org/my-repo:environment:production 形式になる。
GitHub:workflow
Trust Policy で入口を決めたうえで、workflow 側で引き受ける IAM ロール ARN を指定します。
jobs:
deploy-prod:
if: github.ref == 'refs/heads/main'
environment: production
permissions:
id-token: write # JWT 取得に必要
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-prod
aws-region: ap-northeast-1
deploy-preview:
if: github.event_name == 'pull_request'
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-preview
aws-region: ap-northeast-1
AWS:Permission Policy
IAM ロールの「アクセス権限」タブに付けるポリシーです。用語の Permission Policy のとおり、引き受け後に許す AWS 操作をここで決めます。本番・プレビューでロールを分けた場合は、それぞれに必要な Permission Policy を付けます。
まとめ
GitHub Actionsからクラウドへデプロイするとき、Access KeyをSecretsに置く方式は手軽ですが、長期シークレットの管理・ローテーション・権限の切り方に弱みがあります。
OIDCを使うと、GitHubに長期シークレットを預けずに済みます。jobごとのJWTとTrust Policyで認可を組み立て、AWS APIは短命シークレットで呼び出せます。