はじめに
Workforce Identity Federation を使うと、Entra ID のユーザーが、Google アカウントを持たないまま Google Cloud コンソールにサインインできます。Entra ID は Microsoft が提供する ID 管理サービスで、Azure や Microsoft 365 のアカウントを管理しています。
本記事では、Entra ID を IdP として Workforce Identity Pool に接続し、実際にサインインするまでの手順を紹介します。IdP(Identity Provider)は、ユーザーの ID を管理してログインを受け持つサービスです。設定は Entra 側も Google Cloud 側も Terraform で管理しました。
Workforce Identity Federation の概念と Pool の作り方は、Workforce Identity Federationとは?Workloadとの違いで解説しています。本記事はその続きです。
本記事のまとめ
- Entra 側のリダイレクト URI に Pool ID と Provider ID が入るため、ID を先に決めてから両側を設定する。
- OIDC は認可コードフローを選ぶ。implicit フローにすると、後から使える機能が減る。
- client secret は Terraform の外で発行し、write-only 引数で渡すと、Azure と Google Cloud のどちらの state にも平文が残らない。
- subject に別の ID を入れてもサインインは成功してしまう。値の出どころまで確認する。

Entra ID と Workforce Identity Federation で連携する全体の流れ
作業は Entra 側と Google Cloud 側を行き来します。Entra 側のアプリ登録に Pool ID と Provider ID を含む URL を書くため、2 つの ID を最初に決めておくのが要点です。
- Google Cloud で Workforce Identity Pool を作り、Pool ID と Provider ID を決める
- Entra ID でアプリを登録し、リダイレクト URI を設定する
- Entra ID で client secret を発行する
- Google Cloud で Pool に OIDC の Provider を作る
- Google Cloud の IAM で Entra のユーザーにロールを付け、サインインする
OIDC(OpenID Connect)は、ログインした人が誰かを、IdP からアプリへ安全に伝えるための仕組みです。Workforce Identity Federation は OIDC と SAML 2.0 に対応しており、私は Entra 側の設定が少ない OIDC を選びました。

Entra ID でアプリを登録する
Entra 側で作るのは、アプリ登録とエンタープライズアプリケーションの 2 つです。リダイレクト URI はプラットフォームを「Web」にして登録します。SPA(シングルページアプリケーション)として登録すると、認可コードフローが通りません。
リダイレクト URI は、Entra でのログイン後に戻る先の URL です。形式は次のとおりで、Pool ID と Provider ID が入ります。
https://auth.cloud.google/signin-callback/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID私は Entra 側を azuread provider で Terraform 管理しました。主な定義は次のとおりです。
resource "azuread_application_registration" "gcp_wif" {
display_name = "gcp-workforce-identity-dev"
sign_in_audience = "AzureADMyOrg" # 自分のテナントのユーザーだけ
# グループ単位で権限を付けるときに使う
group_membership_claims = ["SecurityGroup"]
}
resource "azuread_application_redirect_uris" "gcp_wif" {
application_id = azuread_application_registration.gcp_wif.id
type = "Web"
redirect_uris = [local.gcp_redirect_uri]
}
# エンタープライズアプリケーション。ユーザーのサインインに必要
resource "azuread_service_principal" "gcp_wif" {
client_id = azuread_application_registration.gcp_wif.client_id
}アプリ登録には 2 つの書き方があります。1 つのリソースにまとめて書く azuread_application と、設定ごとに分かれたリソース(azuread_application_registration など)です。azuread provider のドキュメントには、分かれたリソースの一部が azuread_application と併用できないと書かれています。後から移すと state の付け替えが要るため、私は最初から分かれたリソースで書きました。
なお、Entra 管理センターの新しい画面では、アプリ登録の「認証」が「リダイレクト URI の構成」「Supported accounts」「設定」の 3 つのタブに分かれていました。暗黙的な許可のチェックは「設定」タブにあります。

OIDC は認可コードフローを選ぶ
Provider の OIDC には、認可コードフロー(CODE)と implicit フロー(ID_TOKEN)の 2 つがあります。私は最初 implicit フローで構築し、レビューを受けて認可コードフローに切り替えました。
認可コードフローでは、トークンがブラウザを通らず、IdP から Google Cloud へ直接渡ります。公式ドキュメントでも、こちらが最も安全な方式とされています。切り替えた理由は、Terraform の Provider リソースのドキュメントとスキーマで確かめた次の 2 点です。
- スキーマの説明に、implicit フローを避けるため
CODEを推奨すると書かれている。 ID_TOKENを選ぶと、クレームの扱いが「ID トークンのクレームだけ」に固定される。userinfo エンドポイントのクレームを合わせて使う設定(MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS)が使えなくなる。
認可コードフローには client secret が必要です。証明書やフェデレーション資格情報で代わりにできないかも調べましたが、Provider の oidc ブロックが受け付けるのは client secret だけでした。フェデレーション資格情報は、Entra が外部のトークンを受け取る側になる仕組みで、今回とは向きが逆です。
CORS の設定も要りませんでした。CORS は、ブラウザ上のスクリプトが別のドメインにアクセスしてよいかを決める仕組みです。この構成でブラウザが行うのは画面の遷移だけで、トークンのやり取りは Google のサーバーと Entra の間で行われます。実際に、CORS に関する設定を何もしないままサインインできました。
SAML 2.0 を選ぶ場合は、Terraform で書けない部分が残ります。調べた時点では、azuread provider に Federation Metadata XML を取得する手段が無く、署名証明書をアクティブにする設定もありませんでした。この 2 つは手作業になります。

client secret を state に残さずに渡す
client secret は、Terraform の state に平文を残さない形で渡しました。Entra 側は Terraform の外で発行します。Google Cloud 側は write-only 引数で受け取ります。write-only 引数は、Terraform に渡しても state や plan の出力に値が残らない引数です。
- Entra 側:
azuread_application_passwordで作ると、値が Azure 側の state に平文で残る。そのため Terraform では作らず、Entra 管理センターで 1 回だけ発行した。有効期限は 6 か月である。 - Google Cloud 側: Provider の
plain_text_woで受け取る。google provider 7.38.0 以降と Terraform 1.11 以降が必要で、私は provider を 5 系から 7 系に上げた。
apply は CI で実行するため、値は GitHub Secrets に登録し、環境変数 TF_VAR_... として Terraform に渡しています。apply 後に Provider を gcloud で確認すると、client secret は指紋(thumbprint)しか返りませんでした。出力は必要な項目だけを抜き出しています。
$ gcloud iam workforce-pools providers describe PROVIDER_ID \
--workforce-pool=POOL_ID --location=global
state: ACTIVE
attributeMapping: { google.subject: assertion.oid }
oidc.webSsoConfig: { responseType: CODE, assertionClaimsBehavior: MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS }
oidc.clientSecret.value: { thumbprint: ... }注意点は失効です。state に値が無いので、有効期限が切れても Terraform は何も警告しません。失効はサインインの失敗としてだけ現れます。次に更新するときは az ad app credential reset --append を使う予定です。--append を付けないと、使用中の secret がすぐに失効します。

Workforce Identity Pool に Entra ID の Provider を作る
Provider には、Entra の issuer、client ID、client secret、認可コードフローの設定を書きます。属性マッピングは、まず google.subject = assertion.oid の 1 つだけにしました。
resource "google_iam_workforce_pool_provider" "entra_id" {
workforce_pool_id = google_iam_workforce_pool.entra_poc.workforce_pool_id
location = "global"
provider_id = "entra-id"
attribute_mapping = {
"google.subject" = "assertion.oid"
}
oidc {
issuer_uri = "https://login.microsoftonline.com/${var.entra_tenant_id}/v2.0"
client_id = var.entra_client_id
client_secret {
value {
plain_text_wo = var.entra_client_secret
plain_text_wo_version = var.entra_client_secret_version
}
}
web_sso_config {
response_type = "CODE"
assertion_claims_behavior = "MERGE_USER_INFO_OVER_ID_TOKEN_CLAIMS"
}
}
}oid は、Entra のテナント内でユーザーごとに変わらないオブジェクト ID です。メールアドレスは変わることがあるので subject には使いません。表示名を出す google.display_name は、クレームが欠けるとサインイン自体が失敗するため、疎通を確認してから足すことにしました。
issuer の値は、認証なしで取得できる Entra の設定情報で裏を取りました。テナントのドメイン名から、Terraform で組み立てた issuer_uri と同じ値が返ります。
$ curl -s https://login.microsoftonline.com/TENANT_DOMAIN/v2.0/.well-known/openid-configuration
issuer = https://login.microsoftonline.com/TENANT_ID/v2.0属性マッピングには上限があります。前述の公式ドキュメントによると、合計 16 KB までで、独自の属性ルールは 50 個までです。グループを使う場合は、1 ユーザーが 400 を超えるグループに所属するとサインインに失敗します。Entra 側にも上限があり、Microsoft のドキュメントでは、トークンに載るグループは JWT で 200、SAML で 150 までとされています。
IAM でロールを付けてサインインする
最後に、Entra のユーザーに IAM でロールを付けます。サインインの確認が目的なので、付けたのはプロジェクトの一覧を見るだけの roles/browser 1 つです。
roles/browser -> principal://iam.googleapis.com/locations/global/workforcePools/POOL_ID/subject/USER_OBJECT_ID検証用のユーザーは Entra 管理センターで手作成しました。Terraform の azuread_user で作ると、初期パスワードが state に平文で残るためです。サインインは次の URL から行います。
https://auth.cloud.google/signin/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID?continueUrl=https://console.cloud.google/サインインすると、通常のコンソールではなく「コンソール(連携)」が開きました。アカウントの表示は Entra の oid と Pool 名で、IAM に書いた principal と一致します。roles/browser だけでプロジェクトを選べることも確認できました。
一方、画面下部には、プロジェクトが「組織なし」と表示されました。実際にはフォルダの下にあるプロジェクトです。roles/browser をプロジェクトにだけ付けていて組織の階層を読めないため、こう表示されたと見られます。
ユーザーのオブジェクト ID を取り違えやすい
principal に入れるのは、Entra の「ユーザー」画面にあるオブジェクト ID です。アプリ登録の概要画面にも「オブジェクト ID」があり、私は最初こちらを渡されていました。
間違えても apply は通り、サインインも成功します。Google Cloud 側は、subject の値が実在するユーザーかを確かめないためです。結果として「ログインできるのにプロジェクトが見えない」という、原因の分かりにくい状態になります。値をどの画面から取ったかまで確認してください。

まとめ
Entra ID のユーザーを、Google アカウントを作らずに Google Cloud コンソールへサインインさせることができました。設定は Entra 側と Google Cloud 側の両方を Terraform で管理しています。
- Pool ID と Provider ID を先に決め、Entra のリダイレクト URI(プラットフォームは Web)と一致させる。
- OIDC は認可コードフローにし、client secret は Terraform の外で発行して write-only 引数で渡す。失効は警告なしにサインインの失敗として現れるので、有効期限を管理する。
- subject には Entra のユーザーの oid を使い、どの画面から取った値かまで確認する。
なお、管理画面や provider の仕様、上限値は変わります。実際に設定するときは、最新を公式ドキュメントで確認してください。
次は、Entra ID のユーザーに IAP で Cloud Run のアプリを見せる構成を試します。グループ単位の権限もそこで扱います。

