Cloud RunをEntra IDで認証|IAP×Workforce Identity Federation

認証元の差込口が 1 つだけある門のイラスト。アンバーの Entra ID のカードが差し込まれて雲の上の Cloud Run の箱まで道が開き、下の赤いカードは赤い棒で止められている。画像内の文字は IAP Workforce Identity Federation、One Identity Source Per Service、Entra ID、Cloud Run、Blocked 認証・IAM

はじめに

Cloud Run の IAP で認証元を Workforce Identity Federation に切り替えると、Entra ID のユーザーが Google アカウントを持たないまま、IAP で守った Cloud Run のアプリを開けます。IAP(Identity-Aware Proxy)は、アプリの手前で利用者を認証し、許可された人だけを通す Google Cloud の仕組みです。

本記事では、Entra ID で認証したユーザーに、IAP を有効にした Cloud Run を見せる構成を Terraform で組んだ手順と、途中でつまずいた点を紹介します。検証には Google が提供する Hello World のサンプルイメージを使いました。

前提となる Workforce Identity Pool と Entra ID の Provider は、Workforce Identity Federationとは?Workloadとの違いとEntra ID との連携手順で作成済みです。Google アカウントで認証する場合の手順は、「Cloud RunのIAPはロードバランサー不要|Terraformで直接設定する」を参照してください。

本記事のまとめ

  • IAP の認証元は 1 サービスに 1 つだけである。Google アカウントと Entra ID を併用したいなら、サービスを分ける。
  • IAP 用の OAuth client は、コンソールの「認証情報」画面に出ない別の種類のリソースである。
  • OAuth client の allowed_scopes に cloud-platform が無いと、サインイン画面の手前で 400 invalid_scope になる。
  • グループ単位で許可すると、人の追加は Entra 側だけで済む。IAP コンソールの「外部 ID を使用」は別の仕組みなので押さない。
Cloud Run のサンプルアプリが動いている画面。リビジョン名・サービス名・プロジェクト ID の 3 箇所が黒塗りになった作成メッセージが出ている
グループ単位の許可だけを残した状態で、Entra ID のユーザーから IAP 経由で Cloud Run のアプリが開けた画面。リビジョン名・サービス名・プロジェクト ID は伏せた

IAP の認証元(Workforce Identity Federation)は 1 サービスに 1 つだけ

IAP の認証元(identity_sources)は、1 つのサービスに 1 つしか設定できません。何も設定しなければ Google アカウント、WORKFORCE_IDENTITY_FEDERATION を設定すれば外部の IdP になります。

IAP の API リファレンスには、設定できる認証元は 1 つだけと書かれています。選べる値にも「Google アカウント」を表すものがありません。そのため私は、同じ Hello World を Google 認証版と Entra 認証版の 2 つのサービスに分けて検証しました。

この挙動は、Google Cloud に立てた Entra 認証版のサービスで確かめました。Google にログイン済みのブラウザでその URL を開くと、Google を選ぶ画面は出ず、そのまま Entra のサインイン画面に移ります。エラーではなく、設定どおりの動きです。

公式ドキュメントには、ほかにも制約があります。IAP を有効にしたアプリ 1 つに設定できる Workforce Identity Pool は 1 つで、その Pool の Provider も 1 つだけです。Pool、OAuth client、アプリは同じ組織に置く必要があります。

Microsoft のサインイン画面。メールアドレスの入力欄と「次へ」のボタンだけが出ていて、Google アカウントを選ぶ画面は出ていない
Google にログイン済みのブラウザで Entra 認証版の URL を開いたところ。Google の選択肢は出ず、そのまま Entra ID のサインイン画面になる

IAP 用の OAuth client を作る

IAP が Workforce Identity Pool へのサインインを始めるには、専用の OAuth client が要ります。この OAuth client は、コンソールの「API とサービス」→「認証情報」に出る OAuth クライアントとは別の種類のリソースです。

項目IAP の Workforce Identity Federation 用従来の OAuth クライアント
コンソールでの表示無し「認証情報」画面に出る
gcloudgcloud iam oauth-clientsgcloud iap oauth-clients
作成に要るロールroles/iam.oauthClientAdminroles/oauthconfig.editor

作成済みの client は、gcloud iam oauth-clients list でしか確認できませんでした。OAuth client は Pool の下ではなく、プロジェクトの下にある独立したリソースです。Pool との結び付けは、後で書く IAP の設定で行います。

私は OAuth client 本体を Terraform(google-beta の google_iam_oauth_client)で作り、client secret は gcloud で発行しました。client secret を Terraform で作ると、出力される値が state に平文で残るためです。

resource "google_iam_oauth_client" "iap_workforce" {
  provider = google-beta

  project         = var.project_id
  location        = "global"
  oauth_client_id = "iap-workforce-verification"
  display_name    = "IAP Workforce Verification" # 32 文字以内

  client_type         = "CONFIDENTIAL_CLIENT"
  allowed_grant_types = ["AUTHORIZATION_CODE_GRANT"]
  allowed_scopes = [
    "https://www.googleapis.com/auth/cloud-platform",
    "openid",
    "email",
  ]

  # CLIENT_ID は作成後に決まるため、作成後に gcloud で差し替える
  allowed_redirect_uris = ["https://iap.googleapis.com/v1/oauth/clientIds/PLACEHOLDER:handleRedirect"]

  lifecycle {
    ignore_changes = [allowed_redirect_uris]
  }
}

作成で 3 回つまずきました。

  • 権限: CI のサービスアカウントは roles/editor と roles/iap.admin を持っていたが、403 で失敗した。OAuth client を作る権限は roles/owner にも roles/editor にも含まれず、roles/iam.oauthClientAdmin を別に付ける必要がある。
  • 表示名の長さ: display_name は 32 文字までで、33 文字の名前で 400 エラーになった。この上限は provider のスキーマに書かれておらず、terraform validate でも検出できない。
  • リダイレクト URI: URI に含まれる client ID は、作成して初めて決まる。そのため仮の値で作り、gcloud で本来の値に差し替えてから、Terraform では変更を無視する設定にした。
Google Auth Platform の「クライアント」画面。OAuth 2.0 クライアント ID の一覧に 6 件並んでいるが、名前はすべて黒塗りで、IAP 用に作ったものは無い
コンソールの「Google Auth Platform」→「クライアント」の一覧。IAP 用に作った OAuth client はここに出てこない。プロジェクト ID と既存クライアントの名前は伏せた

IAP の設定を Terraform で書く

IAP の設定は google_iap_settings で書きます。認証元に Workforce Identity Federation を指定し、使う Pool と OAuth client を並べて書くことで結び付きます。

resource "google_iap_settings" "iap_verification_wif" {
  provider = google-beta

  # project 引数は無い。name に入れたプロジェクト番号で対象が決まる
  name = "projects/${var.project_number}/iap_web/cloud_run-${var.region}/services/${google_cloud_run_v2_service.wif.name}"

  access_settings {
    identity_sources = ["WORKFORCE_IDENTITY_FEDERATION"]

    workforce_identity_settings {
      workforce_pools = ["locations/global/workforcePools/${var.workforce_pool_id}"]

      oauth2 {
        client_id     = google_iam_oauth_client.iap_workforce.client_id
        client_secret = var.iap_workforce_oauth_client_secret
      }
    }
  }
}

書くときに気をつけた点は次のとおりです。

  • google_iap_settings には project 引数が無い。書くと Unsupported argument で失敗する。
  • name の cloud_run-リージョン という書き方は、調べた時点では provider のドキュメントに載っていなかった。apply して API に受け付けられることを確認している。
  • client_id には、OAuth client を作るときに指定した ID ではなく、作成後に払い出される値を使う。Terraform では google_iam_oauth_client の属性をそのまま参照した。
  • client_secret は write-only 引数に対応しておらず、state に平文で保存される。値は GitHub Secrets から環境変数で渡し、コードにも Git にも書いていない。

なお、調べた時点では、IAP 設定の API で Cloud Run を扱う部分は Preview でした。

サインインが 400 invalid_scope で失敗した

最初に Cloud Run の URL を開いたとき、Entra のサインイン画面に着く前に 400 エラーで止まりました。原因は、OAuth client の allowed_scopes に cloud-platform を入れていなかったことです。

私は「IAP に必要なのは利用者が誰かを知ることだけ」と考え、openid と email に絞っていました。しかし IAP はサインインのときに cloud-platform を要求します。前に挙げた公式ドキュメントの作成例でも cloud-platform が指定されていました。

allowed_scopes は「この OAuth client が要求してよい範囲」の宣言で、利用者の権限ではありません。利用者が実際に何をできるかは、IAM で付けたロールで決まります。cloud-platform を足しても、Entra のユーザーの権限は増えません。足す変更はその場での更新で済み、client ID も変わりませんでした。

Google の「400: invalid_scope」エラー画面。要求された scope として cloud-platform が表示され、URL バーにも scope のパラメータが見えている
allowed_scopes に cloud-platform が無い状態でサインインしたときのエラー。URL バーの client ID は伏せ、ブックマークバーは切り落とした

Entra のグループ単位でアクセスを許可する

最初は Entra のユーザー個人に権限を付けていましたが、グループ単位に変えました。グループに付けておけば、人を増やすときは Entra 側でメンバーを足すだけで済み、Google Cloud 側の変更は要りません。

そのために、Provider の属性マッピングに google.groups = assertion.groups を足しました。Entra 側のアプリ登録でグループのクレームを出す設定(group_membership_claims = ["SecurityGroup"])をしてあれば、これだけでグループが使えます。権限は、グループのオブジェクト ID を使った principal に付けます。

locals {
  entra_group = "principalSet://iam.googleapis.com/locations/global/workforcePools/${var.workforce_pool_id}/group/${var.entra_group_object_id}"
}

# IAP 経由でアプリを開く権限(サービス単位)
resource "google_iap_web_cloud_run_service_iam_member" "wif_group_access" {
  provider               = google-beta
  project                = var.project_id
  location               = google_cloud_run_v2_service.wif.location
  cloud_run_service_name = google_cloud_run_v2_service.wif.name
  role                   = "roles/iap.httpsResourceAccessor"
  member                 = local.entra_group
}

# コンソール(連携)で Cloud Run の一覧を見る権限(プロジェクト単位)
resource "google_project_iam_member" "wif_group_run_viewer" {
  project = var.project_id
  role    = "roles/run.viewer"
  member  = local.entra_group
}

2 つ目のロールは、コンソール(連携)で試して必要だと分かったものです。IAP の権限だけでは、アプリは開けても、コンソールの Cloud Run 一覧は「Cloud Run リソースを一覧表示する権限がありません。」になりました。一覧の権限(run.services.list)はプロジェクト単位のため、サービス単位の付与では満たせません。

Cloud Run のサービス一覧画面に「Cloud Run リソースを一覧表示する権限がありません。」の警告が出て、一覧が空になっている
Entra ID のユーザーでコンソール(連携)を開いたところ。IAP 経由でアプリは開けても、コンソールで一覧を表示するには別の権限が要る。プロジェクト ID は伏せた

変更後は、一度サインアウトしてから入り直す必要がありました。それまでのセッションのトークンにはグループの情報が載っていないためです。グループの情報が届いているかは、コンソールの Workforce Identity Pool の Provider 画面にある「Debug IdP token」で確認できます。

グループの数には注意が必要です。Microsoft のドキュメントでは、トークンに載るグループは JWT で 200 までで、超えるとグループのクレームがまるごと省かれます。ユーザーがこの数を超えるグループに入っていると、グループ単位の権限が何のエラーも出さずに効かなくなります。

apply の順番を誤ってアクセスを失った

この変更は、属性マッピング(Pool を管理する組織側の環境)と IAM(プロジェクト側の環境)の 2 つの環境にまたがります。私はプロジェクト側だけを先に apply してしまいました。

属性マッピングが無いのでグループの principal が作られず、新しいグループへの付与は誰にも当てはまりません。同じ変更で個人への付与を消していたため、検証ユーザーはすべてのアクセスを失いました。Terraform は環境をまたいだ整合性を確かめないので、apply 自体は成功します。前の段が適用されるまでは、既存の権限を消さないほうが安全です。

IAP コンソールの「外部 ID を使用」は押さない

IAP の設定を終えたあとも、IAP のコンソールには「承認には外部 ID を使用します」という案内が出続けます。これは Identity Platform(GCIP)の導線で、ここまで設定した Workforce Identity Federation とは別の仕組みです。

GCIP を有効にすると、IAP は IAM のポリシーを評価しなくなります。外部 ID の公式ドキュメントにも、IAM を承認に使えなくなると書かれています。押すと、ここまでに付けたグループ単位の権限が効かなくなります。

2 つは想定する利用者も違います。GCIP は外部の顧客向けの ID 基盤で、Workforce Identity Federation は社員やパートナー向けです。社内のユーザーに見せる目的なら、この案内は無視してかまいません。

IAP の設定画面に「承認には外部 ID を使用します」という案内と「開始」のリンクが出ている
Entra 認証版サービスの IAP コンソール。Workforce Identity Federation を設定済みでもこの案内は出続けるが、これは Identity Platform の導線なので押さない。サービス名は伏せた

まとめ

IAP の認証元を Workforce Identity Federation にすると、Entra ID のユーザーに Cloud Run のアプリを見せられます。Google アカウントと併用はできないので、必要ならサービスを分けます。

  • IAP 用の OAuth client はコンソールに出ない別のリソースである。作成には roles/iam.oauthClientAdmin が要り、allowed_scopes には cloud-platform を含める。
  • 権限は Entra のグループに付ける。アプリを開く権限とコンソールで一覧を見る権限は別で、一覧はプロジェクト単位で付ける。
  • 環境をまたぐ変更は、属性マッピングを先に適用する。IAP コンソールの「外部 ID を使用」は押さない。