もう AWS Access Key を使うのはやめよう
この記事では、なぜ AWS Access Key をできるだけ早く廃止すべきなのか、そしてどのように OIDC + IAM Role で置き換えるのかを解説する。
対象範囲は GitHub Actions の CI/CD、EC2/ECS 上のサービス、そしてローカル開発環境までカバーする。
Access Key の何が問題なのか
AWS の Access Key は、Access Key ID と Secret Access Key で構成される長期的に有効な認証情報(credentials)だ。
一度作成すると、手動で削除または無効化しない限り永遠に有効であり続ける。
AWS のマネジメントコンソールで Access Key を作成しようとすると、AWS が可能な限り作成を阻止しようとしてくることにも気づくだろう。
Access Key がひとたび漏洩すると、攻撃者は気づかれる前にその認証情報を使ってあらゆる操作を実行できる。例えば、勝手に EC2 を起動してマイニングを行ったり、S3 内の顧客データをダウンロードしたり、RDS のスナップショットを削除したりといったことだ。
AWS 自身も IAM のベストプラクティス の中で直接こう明記している:
Where possible, we recommend relying on temporary credentials instead of creating long-term credentials such as access keys. (可能な限り、アクセスキーのような長期的な認証情報を作成するのではなく、一時的な認証情報を使用することをお勧めします。)
漏洩は思っているより簡単に起きる
Access Key が漏洩する経路は非常に多い:
- Git のコミット履歴:誤ってコミットしてしまった
.envや設定ファイル。後から削除したとしても、git logには残り続ける - CI/CD のログ:デバッグ時に環境変数を
echoしてしまったり、何らかのライブラリがエラースタックトレースに完全な環境変数を出力してしまったりする - 従業員の退職:特定の IAM User にキーが紐づいている場合、退職時に revoke(無効化)を忘れるリスクがある
- アカウントの共有:チームで同じキーを共有していると、問題が発生した際に誰が実行したのかすら追跡できない
GitHub には secret scanning 機能があり、AWS にも aws:SecretAccessKey の自動検知がある。しかし、これらはすべて事後対応に過ぎず、根本的な解決策ではない。
管理コスト
漏洩が起きなかったとしても、Access Key の維持管理そのものにコストがかかる:
- 定期的なローテーション:AWS は少なくとも 90 日ごとのローテーションを推奨している。ローテーションのたびに、そのキーを使用しているすべての場所(GitHub Secrets、その他の CI システム、ローカル開発環境など)を更新しなければならない
- 権限の監査:そのキーに紐づく IAM Policy が過剰な権限を持っていないか、定期的に確認する必要がある
- 利用状況の追跡:誰が使っているのか?どのワークフローで使われているのか?まだ使われているのか?作成されて半年間誰も触っていないキーを削除していいのか?
これらは 3〜5 人のチームでは大した手間に見えないかもしれないが、チームが成長しプロジェクトが増えるにつれて、Access Key の管理は継続的な重荷になっていく。「まあ、そんなに簡単には漏洩しないだろう」という油断が、いつか手痛いしっぺ返しを食らわせることになる。
OIDC + IAM Role で Access Key を置き換える
GitHub Actions は OIDC(OpenID Connect)を通じて AWS に対し自身の身元を証明する。AWS はそれを検証した上で、数分で失効する一時的な認証情報を発行する。このプロセス全体において、いかなるシークレットも保存する必要がない。
本記事では GitHub Actions を例に説明するが、GitLab CI や CircleCI などのプラットフォームでも同様の OIDC 統合メカニズムが用意されている。
OIDC の仕組み
OIDC は JWT に基づく認証プロトコルだ。GitHub Actions における利用フローは以下のようになる:
GitHub Actions Runner
│
│ 1. 向 GitHub 的 OIDC Provider 請求 JWT token
│ (token 裡包含 repo 名稱、branch、workflow 等資訊)
│
▼
AWS STS (Security Token Service,負責發放臨時憑證的服務)
│
│ 2. 驗證 JWT token 的簽章(透過 GitHub 的公開金鑰)
│ 3. 檢查 token 的 claims 是否符合 IAM Role 的信任條件
│ 4. 回傳臨時憑證(Access Key + Secret Key + Session Token)
│
▼
GitHub Actions Runner
│
│ 5. 用臨時憑證執行 AWS 操作
│ (憑證在指定時間後自動過期)
│
▼
完成,憑證失效
ワークフローが実行されるたびに新しいトークンが取得され、有効期限が切れると自動的に無効化される。ローテーションの必要はなく、Git の履歴から掘り出して再利用することも不可能だ。
もうひとつの重要な違いは信頼モデルにある。
Access Key の安全性は、シークレット自体が漏洩しないことに完全に依存している。一度漏洩してしまうと、正当な利用か不正な利用かを区別する手段がない。
一方 OIDC は JWT の署名を用いて身元を検証する。AWS は GitHub の JWKS エンドポイントを通じてトークンの真正性を独自に検証できるため、シークレットを共有する必要がまったくない。
Access Key との違い
| Access Key | OIDC + IAM Role | |
|---|---|---|
| クレデンシャルの有効期間 | 永久に有効 | 数分〜数時間 |
| 漏洩リスク | 漏洩後は無制限に利用可能 | 期限切れで即座に失効 |
| シークレット管理 | GitHub Secrets への保存が必要 | シークレット一切不要 |
| ローテーション | 手動、最低 90 日ごと | 自動、実行ごとに新規発行 |
| 監査 | IAM User までしか追跡できない | 具体的なリポジトリやワークフローまで追跡可能 |
| 退職時の対応 | revoke を忘れないように注意が必要 | 不要(そもそも長期クレデンシャルが存在しない) |
セキュリティリスクと管理コストの両面において、IAM Role のほうが明らかに優れた選択肢だ。AWS のコンソール上でも、状況に応じた適切な対処法が提示されている:
- CLI:AWS CLI v2 または CloudShell を使用する。ローカル環境に永久クレデンシャルを保存するのを避けるため、
aws sso loginの使用を推奨 - Local Code:IDE に AWS Toolkit を統合し、既存のコンソール認証情報または IAM アイデンティティセンターを通じて認証を行う
- AWS 上のコンピュートリソース:IAM Role を使用する
- サードパーティサービス:長期的な認証情報の代わりに一時的な認証情報を使用する
- AWS 外で稼働するアプリケーション:IAM Roles Anywhere を使用して一時的な認証情報を生成する
- ただし、IAM Roles Anywhere は認証局(AWS Private CA など)と連携させる必要があり、小規模チームにとっては PKI の設定・運用コストが高く、Access Key の管理より楽になるとは限らない
設定手順
1. AWS に OIDC Provider を登録する
IAM コンソール > Identity providers(ID プロバイダー)> Add provider(プロバイダーを追加)へ進む:
- Provider type: OpenID Connect
- Provider URL:
https://token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
あるいは AWS CLI を使用する:
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com
これは 1 つの AWS アカウントにつき 1 回登録するだけでよく、利用するリポジトリがいくつあっても同じプロバイダーを使い回せる。
2. IAM Role を作成する
IAM Role を作成し、Trust Policy(信頼ポリシー)に GitHub OIDC Provider を信頼するよう設定する:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID: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:YOUR_ORG/YOUR_REPO:*"
}
}
}
]
}
注意すべき点がいくつかある:
ACCOUNT_IDを自身の AWS アカウント ID に置き換えるYOUR_ORG/YOUR_REPOを自身の GitHub リポジトリのフルネームに置き換えるsubの値はより厳密に制限することも可能。例えばmainブランチのみを許可する場合:repo:org/repo:ref:refs/heads/main- 複数のリポジトリを許可したい場合は、ワイルドカードを使って
repo:org/*:*と指定できる。ただし、その org 配下のどのリポジトリからでもこのロールを引き受け(assume)られるようになる点には注意が必要だ
Permission Policy(許可ポリシー)は必要に応じて設定する。ECS へのデプロイを例にすると、以下のような権限が必要になる:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecs:UpdateService",
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"ecs:RegisterTaskDefinition",
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"iam:PassRole"
],
"Resource": "*"
}
]
}
実際には Resource に * を使うべきではなく、具体的な ECS サービスや ECR リポジトリの ARN に絞り込むべきだ。上記は必要な Action を示すための例に過ぎない。
3. GitHub Actions を設定する
ワークフローに permissions と aws-actions/configure-aws-credentials の 2 つを追加する。
name: Deploy
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::ACCOUNT_ID:role/github-actions-deploy
aws-region: ap-northeast-1
role-duration-seconds: 900
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/my-app:$IMAGE_TAG .
docker push $ECR_REGISTRY/my-app:$IMAGE_TAG
- name: Deploy to ECS
run: |
aws ecs update-service \
--cluster my-cluster \
--service my-service \
--force-new-deployment
忘れがちなポイント:
permissions は必ず設定すること。 id-token: write によって、ランナーが GitHub OIDC Provider にトークンを要求できるようになる。この行がないとランナーはトークンを取得できず、エラーで即失敗する。また、一度 permissions を明示的に設定すると、記述されていない権限はすべて none に設定されるため、contents: read も一緒に指定しないと actions/checkout がリポジトリを読み取る権限を失ってしまう。
role-duration-seconds のデフォルトは 3600 秒(1 時間)。 ワークフローの実行にそこまで時間がかからないなら、短めに設定しよう。認証情報の有効期間は短いほど安全だ。
role-to-assume を GitHub Secrets に保存する必要はない。 この値は IAM Role の ARN であり、機密情報ではないため、ワークフローファイルにそのまま平文で書いて構わない。
GitHub Actions だけじゃない:EC2 や ECS も同様
同じ原則は、AWS 上で動くすべてのワークロードに当てはまる。
EC2 上のアプリケーションで Access Key を使うべきではない。Instance Profile を使うべきだ。Instance Profile は EC2 インスタンスに紐づけられた IAM Role のことで、インスタンス内の AWS SDK はメタデータサービスを通じて自動的に一時的な認証情報を取得する。
ECS のタスクでも Access Key を使うべきではなく、Task Role を使うべきだ。タスク定義で taskRoleArn を指定すれば、コンテナ内の AWS SDK が自動的に対応する一時認証情報を取得してくれる。
{
"family": "my-app",
"taskRoleArn": "arn:aws:iam::ACCOUNT_ID:role/my-app-task-role",
"executionRoleArn": "arn:aws:iam::ACCOUNT_ID:role/ecsTaskExecutionRole",
"containerDefinitions": [...]
}
taskRoleArn と executionRoleArn は別物である点に注意しよう:
- Task Role(
taskRoleArn):アプリケーションが実行時に AWS リソースにアクセスするために使用するもの(例:S3 の読み取り、DynamoDB への書き込み) - Execution Role(
executionRoleArn):ECS エージェントがコンテナイメージをプルしたり、CloudWatch Logs にログを出力したりするために使用するもの
両者の権限は別々に設定すべきであり、共通化して使い回してはならない。
ローカル開発でも Access Key は使うな:aws sso login
CI/CD では OIDC を導入しているのに、ローカル開発では依然として ~/.aws/credentials に Access Key を置いたままにしている人を多く見かける。
ローカルに置いたキーのリスクは CI とは少し異なるが、決して軽視できない。ノート PC の紛失や盗難、ディスクの未暗号化、複数人での開発マシンの共有など、いずれもそのキーが第三者の手に渡るリスクになり得る。
AWS CLI は IAM Identity Center(旧 AWS SSO)経由のログインをサポートしている。手順は Web サイトで Google OAuth を使ってログインするのと同様だ:
aws configure sso
設定が完了すれば、使いたいときに以下を実行するだけで済む:
aws sso login --profile my-profile
ブラウザがポップアップして認証を完了させれば、CLI が使えるようになる。取得できるのは同様に一時的な認証情報であり、期限が切れれば再ログインするだけなので、ローカルに長期的なキーを保存しておく必要は一切ない。
~/.aws/config はおおよそ次のようになる:
[profile my-profile]
sso_session = my-sso
sso_account_id = 123456789012
sso_role_name = AdministratorAccess
region = ap-northeast-1
[sso-session my-sso]
sso_start_url = https://your-org.awsapps.com/start
sso_region = ap-northeast-1
sso_registration_scopes = sso:account:access
チームでまだ IAM Identity Center を有効化していない場合は、AWS Organizations レベルで有効化する必要がある。設定手順自体は難しくないが、Organization の管理者権限が必要となる。
一度セットアップしてしまえば、チームメンバーは会社の IdP(Google Workspace、Okta、Azure AD など)を使って直接 AWS にログインできるようになり、別途 IAM User を作成する必要もなくなる。
まとめ
Access Key を使うのはやめよう。
CI/CD、EC2/ECS で稼働するサービス、あるいはローカル開発であっても、それぞれ適切な代替手段が存在する:
- GitHub Actions → OIDC + IAM Role
- EC2 → Instance Profile
- ECS → Task Role
- ローカル開発 →
aws sso login
初期設定さえ済んでしまえば、日常的なメンテナンスの負担は Access Key の管理よりもはるかに軽くなる。プロジェクトの進化に伴って Trust Policy や Permission Policy を調整する必要はあるが、少なくともクレデンシャルの漏洩やローテーションの悩みに頭を抱えることはもうなくなるはずだ。
もし君の GitHub Actions にまだ AWS_ACCESS_KEY_ID が残っていたり、~/.aws/credentials にキーが眠っていたりするなら、今こそそれらを一掃する絶好の機会だ。
関連記事
- 測定が目標になるとき:窓税からPull Request数まで かつて僕は小さなツールを自作し、四半期で自分がどれだけPRに貢献したか、レビューコメントをどれだけ残したか、チケットをどれだけ消化したかを集計して、上司にアウトプットを証明しようとしたことがある。上司は淡々と、評価はアウトプットだけで見るものではないと言った。数年後、僕はようやく理解した――測定が目標になるとき、それはもはや良い測定ではなくなるのだ。英国の窓税、ハノイのネズミ駆除の報奨金から、現代のPR数による開発者評価に至るまで、そのメカニズムはまったく同じだ。
- Cloudflare Images を画像ストレージ・変換ソリューションとして使う ウェブページに画像を1枚置くのはフロントエンドにとって最も簡単なことだが、リサイズや各種フォーマットの生成、さらにはトラフィックの負荷に耐えることまで完璧にやろうとすると、実際には一つの包括的なソリューションが必要になる。僕はその後、すべて Cloudflare Images に任せるようになり、オリジナル画像1枚だけを渡すようにしている。
- データベース主キー:AUTO_INCREMENT、UUID、そしてUUIDv7 バックエンド開発で度々直面する主キーの決定。auto incrementを使うべきか、それともUUIDか?衝突への懸念は?UUIDv7とcreated_at + インデックスの性能差はどれほどか?実際に2,000万件のデータで検証したベンチマークと設計上の意思決定を解説する。
- Zeaburを使ってみた感想とレビュー 個人開発者がサービスをデプロイする際、一般的にはVercelなどのプラットフォームを選ぶことが多いが、データベース接続などもう少し高度な要件が必要になるとVercelはそこまで便利ではなくなる。一方で一般的なクラウドプロバイダーの価格は個人開発にとってはかなり高額だ。この記事ではZeaburを使ってみた感想をシェアし、おすすめしたいと思う!