GitHub Actions と Terraform Cloud に対応した Workload Identity 連携の構成
Google Cloud Japan Team
※この投稿は米国時間 2023 年 7 月 8 日に、Google Cloud blog に投稿されたものの抄訳です。
こちらのブログ投稿では、Workload Identity 連携のコンセプトとユースケースを基にした「キーなし認証」のセキュリティ上のメリットを説明しています。ここでは、エンタープライズ環境でよく利用されている CD / CI パイプラインとツールに関連して Workload Identity 連携をどのように使用できるかを詳しく説明します。
Workload Identity 連携は、Gitlab、GitHub Actions、Terraform Cloud などの外部プロバイダと統合できます。外部プロバイダが発行したトークンをさまざまな属性にマッピングする方法と、認証可能な ID を制限する条件の評価にこのトークンを使用する方法について説明します。
前提とする環境
Google Cloud プロジェクト
GitHub リポジトリ
GitHub Actions ワークフロー
Terraform Cloud アカウント / ワークスペース
シナリオ 1:
目的: 2 つの GitHub リポジトリとそれに対応する CI / CD パイプラインが、個別のサービス アカウントを使用し、最小権限のアクセス権によって、それぞれの Google Cloud リソースにアクセスする方法を示します。
このシナリオでは、カスタム属性「repository」を確認して、対応するサービス アカウントが特定の GitHub リポジトリで使用されていることを検証するために、Workload Identity プールを構成します。
手順
1. Workload Identity プールを作成する
ベスト プラクティスに従い、単一の専用プロジェクトから Workload Identity プールを作成および管理することをおすすめします。
2. Workload Identity プール プロバイダを作成する
以下に示す引数には、それぞれ次のような目的があります。
a. issuer-uri は GitHub を ID プロバイダとして指定し、Workload Identity が OIDC メタデータと JSON Web Key Set(JWK)エンドポイントを検出できるようにします。Google Cloud は、これらのエンドポイントを使用してトークンを検証します。
b. GitHub は、ワークフロー ジョブごとに一意のトークンを発行します。このトークンには、ワークフローの ID を示すクレームが含まれています。attribute-mapping を使用してトークンのクレームを変換することで、サービス アカウントへのアクセス権を付与する場合や属性条件を作成する場合に、そのクレームを principal / principalSet ID で使用できるようになります。
c. attribute-condition: 属性のマッピングが完了したら、認証の条件のアサーションを行います。今回は、GitHub 組織「sec-mik」の任意のリポジトリに対する、GitHub Actions ワークフローからの認証リクエストのみを許可します。この条件を作成するには、アサーションまたは以前にマッピングされた属性のいずれかを使用できます。
今回は、トークンのクレームに含まれているサブジェクト、リポジトリ、リポジトリ オーナー、ブランチ属性を GCP アサーションにマッピングします。トークンのサブジェクト クレームからブランチを抽出してカスタム属性「branch」にマッピングするために、CEL(Common Expression Language)式を使用します。
3. リポジトリごとにサービス アカウントを作成して適切な IAM 権限を付与する
このシナリオでは、Hello World アプリを Cloud Run にデプロイするアプリケーション チーム用に 1 つのリポジトリ / サービス アカウントを使用します。また、ネットワーキング チームが 1 つのリポジトリを所有します。
そのため、networking-sa サービス アカウントには「ネットワーキング」関連の IAM 権限が付与され、アプリケーション チームには Cloud Run と関連する IAM の権限が付与されます。
4. Workload Identity プールの IAM バインディングを追加する
ここでは、先ほどマッピングした属性を使用して principal / principalSet を作成し、それらを IAM バインディングで使用して、適切なサービス アカウントの一時的な認証情報を作成する権限を付与します。この場合、ネットワーキング サービス アカウント(「networking-sa」)を、「repository」属性にカプセル化されているネットワーキング GitHub リポジトリにマッピングします。
Google Cloud のベスト プラクティスに従い、リポジトリ名ではなくシステム生成の ID(repository_id など)を使用することをおすすめします。これは、システム生成の ID が不変であり、リポジトリ名の変更など、エンティティ名の変更操作が発生しても変更されないためです。
次に、同じプロセスを繰り返して、アプリケーション チームのサービス アカウントに「application-repo」リポジトリを関連付けます。この場合、カスタム属性ではなく、特定のリポジトリ名とブランチを表す「subject」アサーションに対してマッピングします。JWT の「sub」クレームは、メタデータを連結して作成されているため包括的で予測可能であり、ほとんどの場合に推奨されるマッピング オプションです。
IAM バインディングが適用されると、Google Cloud コンソールの Workload Identity プロバイダ ページに次のように表示されます。
5. Google Cloud の認証で Workload Identity プールが使用されるように GitHub Actions ワークフローを更新する
a. Workload Identity プール(WORKLOAD_IDENTITY_PROVIDER)は「projects/987654321/locations/global/workloadIdentityPools/GitHub-actions-pool/providers/GitHub-actions-oidc」として構成され、サービス アカウントのメール(SERVICE_ACCOUNT_EMAIL)は「application-sa@production-secmik.iam.gserviceaccount.com」として構成されています。
b. 認証が完了すると、ネットワーキングとアプリケーションのデプロイを対象とした 2 つのジョブがパイプラインで実行されます。
この GitHub アクションを application-repo から実行します。この場合、Cloud Run のデプロイは成功しますが、ネットワーキング コマンド(ファイアウォールの一覧表示)は失敗します。
6. Cloud Audit Logs を分析する
Workload Identity 連携に使用された Workload Identity プールとサービス アカウントを含むすべてのプロジェクトの監査ログについて、IAM と STS のデータアクセス ログを有効にする必要があります。これは、IAM 違反をすべて検出し、以下に示すように、GitHub と STS API の間でトークン交換がどのように行われるのかを確認するためです。
最初に「auth」アクションが STS API を使用して、一時的な連携アクセス トークンを取得します。このトークン交換は、属性条件の不一致または構成エラーが原因で失敗する場合があります。
トークン交換のログを以下に示します。
上記の画像では、次のパラメータに注目してください。
protoPayload.authenticationInfo.principalSubject: GitHub JWT トークンのサブジェクト(repo:sec-mik/networking-repo:ref:refs/heads/main)
protoPayload.metadata.mapped_principal: IAM 構文を使用してプリンシパルを指定するトークンのサブジェクト(principal://iam.googleapis.com/projects/987654321/locations/global/workloadIdentityPools/GitHub-actions-pool/subject/repo:sec-mik/networking-repo:ref:refs/heads/main)
次に「auth」アクションは IAM Credentials API を使用して、サービス アカウントの一時的な認証情報を取得します。workloadIdentityUser IAM ロールが存在しない、またはトークンを作成しようとしたプリンシパルが正しくないために、失敗する可能性があります。
サービス アカウントの一時的な認証情報作成のログを以下に示します。
上記の画像では、次のパラメータに注目してください。
protoPayload.authenticationInfo.principalSubject: 連携トークンのサブジェクト(principal://iam.googleapis.com/projects/987654321/locations/global/workloadIdentityPools/GitHub-action-pool/subject/repo:sec-mik/application-repo:ref:refs/heads/main)
metadata.identityDelegateChain: 一時的な認証情報が生成されたサービス アカウント(example-app-sa@production-secmik.iam.gserviceaccount.com など)
詳しくは、ログの例をご覧ください。
まとめ
ここでは、GitHub OIDC トークンを使用して Google Cloud の認証を行うプリンシパルとして GitHub リポジトリがマッピングされ、その後 Google Cloud の認証情報と交換される仕組みを見てきました。認証が完了すると、対応するサービス アカウントの IAM バインディングによって認証チェックが実行され、これに応じてアクセス権が付与されました。
シナリオ 2
目的: 2 つの Terraform Cloud ワークスペースが、別々でありながら対応している 2 つのサービス アカウントをプロビジョニングで使用できる仕組みを示します。
このシナリオでは、IaC 分野で人気が高く、オーケストレーターとしてよく使われているツールである Hashicorp Terraform Cloud について詳しく説明します。また、Workload Identity 連携を同様の方法で使用できることを説明します。
ここで説明する内容の概要は、以下のとおりです。
マッピング(GitHub リポジトリ → Terraform Cloud ワークスペース → Google Cloud サービス アカウント → Google Cloud プロジェクト)
app-repo-dev → app-ws-dev → app-sa-dev → dev-secmik
app-repo-prod → app-ws-prod → app-sa-prod → production-secmik
手順
1. 開発環境と本番環境の Workload Identity プールを作成する
このシナリオでは、Google Cloud のベスト プラクティスに従い、個別の Workload Identity プールを作成します。このベスト プラクティスでは、サブジェクトの競合を防ぐために、外部 ID プロバイダと Workload Identity プールを 1 対 1 でマッピングすることが推奨されています。サブジェクトの形式は「organization:my-org:project:Default Project:workspace:my-workspace:run_phase:apply」ですが、このシナリオでは、開発環境と本番環境でワークスペースの参照が異なります。
2. Workload Identity プール プロバイダを作成する
次のステップでは、Workload Identity プール内でプロバイダを構成します。ただしその前に、Terraform JWT トークンを構成するさまざまなクレームを確認しておくことが重要です。JWT トークンの重要なクレームのいくつかを以下に示します。
iss : トークンの発行元。
terraform_full_workspace : 完全なワークスペース名。
terraform_project_id : ワークスペースが関連付けられているプロジェクトの ID。
terraform_workspace_id : ワークスペースの ID。
開発環境および本番環境の Workload Identity プール構成では、Terraform Cloud 組織とプロジェクト内の(開発環境 / 本番環境に対応する)特定のワークスペースの ID トークンへのアクセスが、CEL 条件によって制限されます。
3. Google Cloud サービス アカウントを作成する
個別のワークスペースで使用される 2 つのサービス アカウントを作成します。
メイン ワークスペース: "app-sa-prod"
開発用ワークスペース: "app-sa-dev"
4. Workload Identity プールの IAM バインディングを追加する
先ほど作成した Workload Identity プール プロバイダの IAM バインディングを追加します。このシナリオでは、IAM はワークスペース ID の属性を検証した後で、サービス アカウントの権限を借用するためのアクセスを認可します。ワークスペース ID は変更不可です。そのため、ワークスペースが削除され、その後同じ名前で再作成された場合でも、Google のベスト プラクティスのとおり、アクセス権が誤って付与されることはありません。
開発環境のサービス アカウント
本番環境のサービス アカウント
IAM バインディングが適用されると、Google Cloud コンソールの Workload Identity プロバイダ ページに次のように表示されます。
5. Terraform Cloud の構成
以下に示すように、Workload Identity プール関連の構成をワークスペース変数として指定します。変数を sensitive として分類すれば、その変数は UI やログに表示されません。詳しくは、HashiCorp で公開されているドキュメントをご覧ください。
TFC_GCP_PROVIDER_AUTH : Terraform Cloud は GCP の認証に動的認証情報を使用します。
TFC_GCP_RUN_SERVICE_ACCOUNT_EMAIL: Terraform Cloud が Google Cloud の認証に使用するサービス アカウントのメールアドレスです。
TFC_GCP_WORKLOAD_PROVIDER_NAME: Workload Identity プロバイダの正規名です。形式: projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_NAME/providers/PROVIDER_NAME
条件チェックが原因で認証情報の交換が失敗すると、次のようなエラーが表示されます。
権限が原因でトークンの生成が失敗すると、次のようなエラーが表示されます。
デプロイが成功すると、次のように表示されます。
6. Workload Identity 連携のサービス アカウントを監査する
Workload Identity 連携を使用するようにプロビジョニングされているすべてのサービス アカウントを組織全体で確認するには、次の手順を使用します。
Policy Analyzer で組織を選択します。
パラメータとして [Workload Identity ユーザー] ロールを選択します。
チェックボックスは選択しないでください。
[ANALYZE] をクリックします。
Workload Identity 連携を使用するようにプロビジョニングされているすべてのサービス アカウントが、その属性条件を含めて表示されます。
今すぐ使用を開始する
Workload Identity 連携と CI / CD パイプラインの使用を今すぐ開始するには、デプロイメント パイプラインとの Workload Identity 連携を構成するをご覧ください。
- クラウド インフラストラクチャ コンサルタント Sanmay Mishra