GitHub Actions の AWS キーは「保存しない」設計にする — Secrets 漏洩リスクと OIDC 移行
はじめに
GitHub Actions で AWS にデプロイするとき、こんな設定をしていませんか?
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
この設定自体は動きます。
しかし 「AWS キーを GitHub Secrets に保存する」設計には構造的なリスク があります。
本記事では、このリスクがどのように現実の侵害につながるか、
そして OIDC(OpenID Connect)を使ってどう根本解決できるかを解説します。
CI/CD はクラウドへの入口になっている
GitHub Token(PAT や SSH 鍵)が漏洩したとき、被害は GitHub にとどまりません。
GitHub Token → GitHub Secrets → AWS キー という連鎖が成立するため、
GitHub への不正アクセスがクラウド侵害に直結します。
CI/CD パイプラインは便利な自動化の仕組みである一方、
クラウドへの入口としてのリスクも持っています。
Secrets 方式のリスク
本稿における「Secrets 方式」とは、GitHub Actions から AWS にアクセスするために、
IAM ユーザーの長期アクセスキー(AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY)を GitHub Secrets に登録しておく方式です。
なぜ workflow の改ざんが必要になるのか
GitHub Secrets の値は設定画面から 閲覧できない設計 になっています(名前のみ表示)。
そのため、不正なアクセスを試みる場合でも Secrets の値を直接読み出すことはできません。
代わりに使われる手口が間接的な方法です。
「workflow を改ざんして実行させ、実行時に注入された環境変数を外部へ送信する」
workflow ファイル(.github/workflows/*.yml)を書き換え、次回の実行時に Secrets の値を外部エンドポイントへ送るコードを仕込みます。
メンテナーアカウントの侵害ルート
workflow の改ざんにはリポジトリへの write 権限が必要です。
その入口となるのがメンテナーアカウントの侵害です。
| 侵害ルート | 具体例 |
|---|---|
| PAT(Personal Access Token)窃取 | フィッシング、マルウェア、リポジトリへの直書き |
| SSH 鍵の窃取 | 開発端末への侵入 |
| GitHub App の不正利用 | 過剰な権限を持つ App を経由 |
| OAuth Token の窃取 | 悪意ある OAuth アプリへの誤認可 |
キー漏洩までの流れ
構造的な問題
Secrets 方式の根本的な問題は「長期アクセスキーをどこかに保存する設計」であることです。
GitHub Secrets
┌────────────────────────────┐
│ AWS_ACCESS_KEY_ID │ ← 無期限の長期キーが保存されている
│ AWS_SECRET_ACCESS_KEY │
└────────────────────────────┘
↓ workflow が参照
↓ 改ざんされた workflow も同様に参照できる
どれだけ安全に保存しても、「保存している」という事実がリスクとなります。
漏洩したキーはローテーションするまで有効であり、気づかずに悪用され続けます。
OIDC 方式:「保存しない」設計へ
OIDC(OpenID Connect)を使うと、長期アクセスキーを一切発行・保存しない設計 が実現できます。
仕組み
GitHub Actions が実行されるたびに JWT が自動発行され、それを AWS STS に渡して短期の認証情報を得ます。
GitHub Secrets に AWS キーを登録する必要がありません。
workflow の変更点
Secrets 方式との差分はシンプルです。
# Secrets 方式(変更前)
- uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
# OIDC 方式(変更後)
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.OIDC_ROLE_ARN }}
aws-region: ap-northeast-1
role-session-name: GitHubActions-OIDC
OIDC トークンの発行には permissions: id-token: write が必要です。
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # OIDC トークン発行に必要
contents: read
Trust Policy:リポジトリ・ブランチ単位でアクセスを制限
OIDC 方式の重要なポイントが Trust Policy です。
IAM Role の「誰がこの Role を引き受けられるか」を定義し、リポジトリとブランチを限定できます。
// Trust Policy の Condition 部分(抜粋)
{
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub":
"repo:your-org/your-repo:ref:refs/heads/main"
}
}
}
sub(subject)クレームにはリポジトリ名とブランチ名が含まれており、
特定のリポジトリ・特定のブランチからのみ Role の引き受けを許可できます。
Secrets 方式では「AWS キーを持っていれば誰でもアクセス可能」でしたが、
OIDC 方式では GitHub の信頼チェーンが加わります。
CloudTrail で見る違い
両方式は CloudTrail に残るイベントも異なります。
| 方式 | 記録されるイベント | 追跡の粒度 |
|---|---|---|
| Secrets 方式 | GetCallerIdentity |
IAM ユーザー単位 |
| OIDC 方式 |
AssumeRoleWithWebIdentity + GetCallerIdentity
|
リポジトリ・ブランチ単位 |
OIDC 方式では AssumeRoleWithWebIdentity イベントの userIdentity.userName に以下の形式でリポジトリ情報が記録されます。
{
"eventName": "AssumeRoleWithWebIdentity",
"userIdentity": {
"type": "WebIdentityUser",
"userName": "repo:your-org/your-repo:ref:refs/heads/main",
"identityProvider": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
}
}
不正利用が発生した場合、「どのリポジトリの、どのブランチから」アクセスされたかが CloudTrail で追跡できます。
Secrets 方式 vs OIDC 方式:比較まとめ
| 比較項目 | Secrets 方式 | OIDC 方式 |
|---|---|---|
| 認証情報の種類 | 長期アクセスキー(無期限) | 短期トークン(最大1時間) |
| 保存場所 | GitHub Secrets | 保存しない |
| 漏洩時の影響 | キーが有効な限り悪用可能 | 短時間で自動失効 |
| ローテーション | 手動で必要 | 自動(毎回発行) |
| アクセス制御 | IAM Policy のみ | IAM Policy + Trust Policy |
| CloudTrail での追跡 | IAM ユーザー単位 | リポジトリ・ブランチ単位 |
| 設計思想 | 秘密を保存する | 秘密を保存しない |
対策
| 対策 | 内容 |
|---|---|
| OIDC 方式への移行 | 長期 AWS キーを発行しない設計に切り替える(本記事のメインテーマ) |
| メンテナーアカウントの保護 | MFA の有効化、PAT のスコープ・有効期限を最小化する |
| CloudTrail の監視 | 異常なアクセスを EventBridge → Lambda → Slack 等で検知する(AWS デモ記事参照) |
| キーの即時ローテーション | 不審を感じたら即座に無効化・ローテーションする |
補足: workflow の最小権限について
permissions: contents: readのように GITHUB_TOKEN の権限を絞ることは、最小権限の観点から重要です。
ただしメンテナーアカウントが侵害された場合、workflow ファイル自体を書き換えられるためpermissionsブロックも変更できます。
この設定はサードパーティ action が侵害された場合や、workflow 実行時の脆弱性による二次被害の範囲を限定する目的で有効です。
デモリポジトリ
本記事のデモを実際に動かして確認できるリポジトリを公開しています。
- Terraform で IAM Role(OIDC 用)・IAM ユーザー(Secrets 検証用)を一括作成
- workflow サンプル(
.yml.example)で両方式の動作を確認 - CloudTrail のイベント確認方法・クリーンアップ手順を収録
また、AWS キーが漏洩した後のリスク(S3 データ窃取・CloudTrail 検知)を扱う関連デモも公開しています。
Startup Security Kit について
今回のデモで確認したリスクは、認証情報の漏洩を起点としたサプライチェーンリスクの一部です。
このリスクの全体像(攻撃チェーン・よくある問題・設計レベルの対策)は、
Startup Security Kit の credential-compromise-supply-chain.md にまとめています。
おわりに
「GitHub Secrets は安全な保管場所」という認識は半分正しく、半分誤っています。
Secrets の値は画面から読めませんが、workflow を通じて間接的にアクセスできるという事実は変わりません。
OIDC 方式への移行は workflow の数行の変更と IAM Role の設定だけで実現できます。
「秘密を安全に保存する」から 「秘密を保存しない」設計へ、ぜひ移行を検討してみてはいかがでしょうか。
Discussion