はじめに
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が無いと、サインイン画面の手前で 400invalid_scopeになる。 - グループ単位で許可すると、人の追加は Entra 側だけで済む。IAP コンソールの「外部 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、アプリは同じ組織に置く必要があります。

IAP 用の OAuth client を作る
IAP が Workforce Identity Pool へのサインインを始めるには、専用の OAuth client が要ります。この OAuth client は、コンソールの「API とサービス」→「認証情報」に出る OAuth クライアントとは別の種類のリソースです。
| 項目 | IAP の Workforce Identity Federation 用 | 従来の OAuth クライアント |
|---|---|---|
| コンソールでの表示 | 無し | 「認証情報」画面に出る |
| gcloud | gcloud iam oauth-clients | gcloud iap oauth-clients |
| 作成に要るロール | roles/iam.oauthClientAdmin | roles/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 では変更を無視する設定にした。

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 を入れていなかったことです。
400: invalid_scope
Some requested scopes were invalid.
Requested:

https://www.googleapis.com/auth/cloud-platform
Invalid:

https://www.googleapis.com/auth/cloud-platform私は「IAP に必要なのは利用者が誰かを知ることだけ」と考え、openid と email に絞っていました。しかし IAP はサインインのときに cloud-platform を要求します。前に挙げた公式ドキュメントの作成例でも cloud-platform が指定されていました。
allowed_scopes は「この OAuth client が要求してよい範囲」の宣言で、利用者の権限ではありません。利用者が実際に何をできるかは、IAM で付けたロールで決まります。cloud-platform を足しても、Entra のユーザーの権限は増えません。足す変更はその場での更新で済み、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)はプロジェクト単位のため、サービス単位の付与では満たせません。

変更後は、一度サインアウトしてから入り直す必要がありました。それまでのセッションのトークンにはグループの情報が載っていないためです。グループの情報が届いているかは、コンソールの 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 の認証元を Workforce Identity Federation にすると、Entra ID のユーザーに Cloud Run のアプリを見せられます。Google アカウントと併用はできないので、必要ならサービスを分けます。
- IAP 用の OAuth client はコンソールに出ない別のリソースである。作成には
roles/iam.oauthClientAdminが要り、allowed_scopesにはcloud-platformを含める。 - 権限は Entra のグループに付ける。アプリを開く権限とコンソールで一覧を見る権限は別で、一覧はプロジェクト単位で付ける。
- 環境をまたぐ変更は、属性マッピングを先に適用する。IAP コンソールの「外部 ID を使用」は押さない。

