CodeCommit派だった私がGitHub Actions移行を頼まれて、権限設計で迷った話

これまでの構成

私はAWSのBackendシステムの基盤開発を担当することが多く、これまでは次の構成が標準でした。

  • Git: AWS CodeCommit
  • IaC: AWS CDK(TypeScript)
  • CI/CD: CDK Pipelines(AWS CodePipeline)
  • CICDアカウントと構築対象環境アカウントを分けたクロスアカウント構成

CodePipelineが1本あれば、Source→Build→Self-Mutate→各環境へのDeployまでが全部CDKコードの中で完結する。この構成に慣れきっていました。

何につまずいたか

あるとき「GitHubを使うことになったので、GitHub ActionsでCI/CDを組んでほしい」という依頼を受けました。正直、GitHub Actionsのメリットを深くは理解していませんでした。

構成が大きく変わることは想像がついていました。

  • CICDアカウントは、常設のCodePipelineを置く必要がなくなるので、身軽になりそう
  • Pipeline/Stageに相当する組み立ては、GitHub Actionsのワークフロー(YAML)で書けばよさそう

ここまでは調べる前からなんとなく分かっていたのですが、一番分からなかったのが**「GitHub ActionsからCDKのsynth/diff/deployを実行するとき、AWS側の権限をどう持たせるのか」**でした。CodePipelineなら、サービスロールがすべてを引き受けてくれていたので、これまで意識する必要がなかった部分です。

調べて分かったこと①: クロスアカウントの土台は変わらない

調べていて一番の収穫だったのは、クロスアカウントデプロイの根幹部分は、CodeCommit+CDK PipelineでもGitHub Actionsでも同じだと分かったことです。公式ドキュメントを読み込んだうえでの、私自身の理解です。

CDKにはcdk bootstrapというコマンドがあり、--trustオプションでどのAWSアカウントからのクロスアカウントデプロイを信頼するかを指定します。これを実行すると、ターゲットアカウント側に次のようなIAMロール(bootstrap roles)が自動で作られます。

  • deploy-role: CloudFormationの変更セット作成・実行を指示する
  • cfn-exec-role: 実際にリソースを作成・更新するCloudFormationの実行ロール
  • file-publishing-role / image-publishing-role: LambdaコードやコンテナイメージをS3/ECRへ公開する
  • lookup-role: cdk diffなどで環境情報を読み取る(読み取り専用)

CodePipelineは、このbootstrap rolesをsts:AssumeRoleすることでクロスアカウントデプロイを実現していました。そして、これはGitHub Actionsに乗り換えても変わりません。変わるのは、そこにたどり着くまでの「最初の入口」だけでした。

調べて分かったこと②: 変わるのは「入口」だけ

CodePipelineの場合、入口はCodePipelineのサービスロールでした。GitHub Actionsの場合、この入口がOIDC(OpenID Connect)で認証するIAMロールに置き換わります。

流れはこうです。

  1. CICDアカウントに、GitHubのOIDC発行者(https://token.actions.githubusercontent.com)を信頼するIDプロバイダを1つ作る
  2. そのIDプロバイダを信頼する「Hub Role」を1つ作る。信頼ポリシーのsub条件で、対象リポジトリ・Environmentを絞り込む
  3. Hub Roleに、各環境アカウントのbootstrap roles(deploy-roleなど)へsts:AssumeRoleする権限を持たせる
  4. GitHub Actions側は、configure-aws-credentialsアクションを2回呼ぶ。1回目でHub Roleを引き受け、2回目はrole-chaining: trueを付けてターゲットのdeploy-roleを引き受ける

CodePipelineの「サービスロールが黙って全部やってくれていた」部分を、自分で組み立て直す感覚に近いです。

最小構成でやってみる

実際に手を動かすなら、次の順番が最短ルートだと思います。

  • CICDアカウントにGitHubのOIDC IDプロバイダを1つ作る(Audience: sts.amazonaws.com)
  • Hub Roleを作り、信頼ポリシーのsub条件をrepo:<ORG>/<REPO>:environment:devのように環境ごとに絞る(environment:*のようなワイルドカードにしない)
  • Hub Roleの許可ポリシーに、対象環境アカウントのdeploy-role等へのAssumeRoleを追加する
  • 環境アカウント側でcdk bootstrap --trust <CICDアカウントID>を実行し、信頼関係を張る
  • ワークフローYAMLにpermissions: id-token: writeを付け、configure-aws-credentialsを2段(Hub Role→ターゲットのdeploy-role、2回目はrole-chaining: true)で呼ぶ

ここまでできれば、cdk synth / cdk diff / cdk deployはGitHub Actions上の通常のシェルコマンドとして実行できます。

AIレビューで指摘されて直したこと

技術的な仕組みが分かった後、実際にワークフローのサンプルを書いて記事にまとめる過程で、Claude Code(AI)のレビューエージェントにセキュリティ観点でチェックしてもらいました。ここで一番刺さった指摘が、本番デプロイにcdk deploy --require-approval neverを使っていたことです。

自分では「GitHub Environmentの承認ゲートがあるから大丈夫」と思っていたのですが、指摘を受けて次のことに気づきました。

  • Environmentの承認は「ワークフローの実行を止める」だけで、cdk deployコマンド自体は承認内容を見ているわけではない
  • --require-approval neverにしていると、承認者は変更の中身(IAMポリシーの拡張やセキュリティグループの開放など)を見ないまま承認してしまう

直した結果、次のようにしました。

  • 承認ゲートの手前に、environmentを指定しないdiff-prodジョブを追加してcdk diffの結果をジョブサマリへ出す
  • 本番デプロイのコマンドは--require-approval broadeningにし、権限が広がる変更が含まれる場合はCLI側でも止まるようにする

GitHub Actionsのランナーは対話端末を持たないので、--require-approval broadeningに該当する変更があるとその場でエラー終了します。最初は「CIで承認プロンプトなんて出せないのでは」と思ったのですが、これはむしろ「危ない変更は自動で止まる」というフェイルセーフとして機能する、と考え直しました。

CodeCommit+CDK Pipelineとの違い、簡単にまとめると

観点CodeCommit + CDK PipelineGitHub Actions
権限の入口CodePipelineのサービスロールOIDCを信頼するHub IAMロール
クロスアカウントの土台cdk bootstrap --trust/bootstrap roles同じ(ここは変わらない)
Pipeline/Stageの組み立てCDKコード(addStage()など)GitHub Actionsのワークフローyaml
承認ゲートCodePipelineの手動承認アクションGitHub Environmentのrequired reviewers
自己書き換え(Self-Mutation)CDK Pipelinesが標準対応標準機能はない(別途cdk-pipelines-githubという選択肢もある)

筆者の判断・次に検証したいこと

今回分かったのは、あくまで「1つのHub Roleから、dev/prodの2アカウントへロールチェインする」という比較的小さな構成での話です。アカウント数がもっと多い組織(10、20アカウント規模)では、Hub Roleの許可ポリシーへのARN列挙がどこまで運用の負担になるかは、まだ実際には検証できていません。ここは推測の域を出ないので、次に大規模構成を任されたときに、実際どうなるか確かめてみたいと思っています。

また、cdk-pipelines-githubのように「CDK Pipelinesの書き方のままGitHub Actionsのワークフローを生成する」構築ライブラリも存在することが分かりました。今回は素のGitHub Actionsワークフローで組みましたが、既存のCDK Pipelinesコード資産が大きいチームでは、こちらを検討する価値がありそうです。

ただし1つ調べていて気づいたのは、このライブラリを使っても「CICDアカウント」に相当するものが不要になるわけではない、ということです。公式サンプルを見る限り、OIDC用のGitHubActionRoleはパイプライン全体で1箇所にしか置かない設計で、内部的には従来のCDK Pipelines(CodePipelineベース)と同じbootstrap --trustの仕組みでクロスアカウントデプロイしています。つまり「常設のCodePipeline/CodeBuildが要らなくなる」のは変わらないのですが、「クロスアカウントの入口になる1アカウント」自体は、今回の記事のHub Roleとまったく同じ考え方で必要になります。ここは実際に試したわけではなく公式ドキュメントを読んだ範囲の理解なので、機会があれば実際に組んでみて別の記事にまとめたいと思います。