· 11分で読了

もう 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 KeyOIDC + 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 を設定する

ワークフローに permissionsaws-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": [...]
}

taskRoleArnexecutionRoleArn は別物である点に注意しよう:

  • Task RoletaskRoleArn):アプリケーションが実行時に AWS リソースにアクセスするために使用するもの(例:S3 の読み取り、DynamoDB への書き込み)
  • Execution RoleexecutionRoleArn):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 にキーが眠っていたりするなら、今こそそれらを一掃する絶好の機会だ。

関連記事

他のトピックを探索