GitHub Actions 講座 #7 AWS デプロイと OIDC — アクセスキーなしの資格情報

読了 5分

前回でイメージを作ったので、次は AWS へのデプロイですが、その前に越えるべき関門が資格情報です。以前の方式は、IAM ユーザーのアクセスキーを発行して secrets に入れることでした。動きはしますが、有効期限のない強力なキーが GitHub に常時保管される構造なので、流出したときの被害が大きく、定期的なローテーションは人間が忘れます。現在の標準はキーをそもそも保管しない OIDC 連携で、Terraform 実践講座 #7 で AWS 側の準備はすでに済ませてあります。今回はワークフロー側を完成させて、両側をつなぎます。

動作原理 — 90 秒の説明 #

OIDC 連携の流れは、トークン交換 1 回です。

トークン交換の流れ
1. ワークフローの実行が開始
2. GitHub がこの実行の身元トークン(OIDC トークン)を発行
   : 「このトークンは my-org/myapp リポジトリの main ブランチの実行である」という署名付きの証明
3. ワークフローがトークンを AWS STS に提出
4. AWS が署名を検証し、信頼ポリシーの条件と照合
5. 条件が合えば短期の資格情報(1 時間など)を発行
6. ワークフローがその資格情報で AWS の作業を実行し、期限が切れたら終わり

核心は、保存される秘密が存在しないことです。トークンは実行のたびに新しく発行されて寿命が短く、AWS は「GitHub が署名した、自分のリポジトリの、指定条件に合う実行」にだけ資格情報を渡します。流出するキーがないので、ローテーションするキーもありません。

AWS 側 — 信頼構成の復習 #

AWS 側に必要なものは 2 つで、Terraform 実践 #7 でコードとして作りました。GitHub の OIDC provider の登録と、信頼ポリシーに条件を付けた IAM ロールです。条件の核心だけもう一度見ます。

信頼ポリシーのsub条件
"token.actions.githubusercontent.com:sub": "repo:my-org/myapp:*"

sub クレームはトークンの出どころ (リポジトリ・ブランチ・environment) を載せるフィールドで、この条件が自分のリポジトリの実行だけにロールを引き受けさせるロックです。さらに絞ることもできます。repo:my-org/myapp:ref:refs/heads/main で main ブランチの実行だけ、repo:my-org/myapp:environment:production で特定の environment の実行だけを許可する形です。#3 の environment 保護ルールと組み合わせれば、「承認を通過した production ジョブだけがこのロールを引き受ける」という二重ロックになります。

ワークフロー側 — 2 つの部品 #

ワークフローに必要なのは、権限の宣言とアクション 1 つです。

.github/workflows/deploy.yml
permissions:
  id-token: write      # OIDC トークンの発行を許可
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ vars.DEPLOY_ROLE_ARN }}
          aws-region: ap-northeast-2

      - run: aws s3 sync ./dist "s3://${{ vars.SITE_BUCKET }}" --delete
      - run: >
          aws cloudfront create-invalidation
          --distribution-id "${{ vars.CF_DIST_ID }}" --paths "/*"

permissions の id-token: write が、このジョブに OIDC トークンの発行を許可するスイッチです。この宣言がないとトークン発行自体が拒否されるため、OIDC が「権限がない」と言って失敗したら、最初に確認すべき行です。configure-aws-credentials アクションが先ほどのトークン交換の流れ全体を実行してくれるので、以降のステップの AWS CLI はログイン済みの状態で動きます。ロールの ARN は秘密ではないので、#3 の基準どおり vars に置きます。

例には静的サイトのデプロイ (S3 同期 + CloudFront の無効化) を選びました。前回のイメージを ECS に反映するデプロイなら、最後の 2 ステップがタスク定義の更新とサービスの更新に変わるだけで、資格情報の構造は完全に同じです。Terraform 運用講座 #1 の plan・apply ワークフローも同じ構造の上にあるので、この連携 1 つでデプロイ・インフラパイプライン全体の資格情報が統一されたことになります。

よくある失敗 2 つ #

  • “Not authorized to perform sts:AssumeRoleWithWebIdentity”: 信頼ポリシーの sub 条件と実際の実行の出どころが合っていないケースがほとんどです。ブランチ制限を掛けたのに別のブランチから実行した、あるいは environment 条件を付けたのにジョブに environment の指定がない、といった場合です。条件を絞るほど、この照合を正確に合わせる必要があります。
  • トークン発行の拒否: id-token: write の抜けです。特にワークフローレベルで permissions を宣言するとデフォルト権限がすべてリセットされるため、ほかの権限 (contents: read など) も一緒に明示しないと checkout が壊れます。

まとめ #

今回扱った内容です。

  • アクセスキーの保管方式は、無期限キーの常時保管という構造的リスクです。OIDC は実行ごとに短期トークンを交換し、保存される秘密をなくします
  • AWS の信頼ポリシーの sub 条件がロックの核心で、ブランチ・environment 単位まで絞れます
  • ワークフロー側は id-token write 権限と configure-aws-credentials アクションの 2 つの部品です
  • AssumeRole の拒否は sub 条件の不一致、トークン発行の拒否は id-token 権限の抜けが定番の原因です
  • 静的サイト、ECS、Terraform まで、すべての AWS パイプラインが同じ資格情報の構造を共有します

次回(#8 セルフホステッドランナー)では、GitHub ホステッドランナーでは対応できない状況、つまり内部ネットワークへのアクセス、コスト、特殊なハードウェアのためにランナーを自前で運用する方法を扱います。

X