はじめに
Google Cloud環境でのログイン機能を実装するとき、何を使っていますか?
「すでに社内で Okta や Keycloak、Microsoft Entra ID などの IdP を運用しているのに、Google Cloud 用に別のユーザー管理をしたくない…」と悩んだことはないでしょうか。
今回は、そんな「社内 IdP をアプリケーションのログインとしてそのまま使いたい」という方に向けた内容となります。
既存の社内 IdP(今回は Keycloak)と Identity-Aware Proxy (IAP)、Workforce Identity Federation (WIF) を組み合わせ、社内アカウントによるアプリケーションログインを実装しました。
なぜ Identity Platform ではなく Workforce Identity Federation (WIF) か
Google Cloud で外部 IdP と連携する際、Identity Platform という選択肢もあります。
しかし、すでに社内 IdP でユーザーやグループの管理が統合されている場合、Identity Platform の採用には以下の課題があります。
- シャドウアカウントの発生(二重管理の懸念): Identity Platform では、ログイン時に Google Cloud 側にユーザーのレコードが作成されてしまいます。退職者のレコードがクラウド上に残り続けるなど、ガバナンス上の管理対象が増えてしまいます。
- 権限管理の二度手間: Identity Platform 単体では、社内 IdP 側の「グループ情報」を使った柔軟なアクセス制御(IAP との連携)を構築するのが難しく、アプリ側や Google 側で別途権限を管理する手間が生じがちです。
Workforce Identity Federation (WIF) を使えば、Google Cloud 側にユーザー情報を一切作らず、社内 IdP を直接信頼する「ステートレス」な構成が取れます。
ユーザーの追加・削除はもちろん、IAP のアクセス権限(ポリシー)も社内 IdP のグループ情報に直接マッピングできるため、Google Cloud 側の運用負荷を最小限に抑え、すべての権限管理を社内 IdP に集約できます。
アーキテクチャ
主要コンポーネント
| コンポーネント | 機能 | 役割 |
|---|---|---|
| IAP | Google Cloud リソースへのアクセスを保護するリバースプロキシ。認証済みユーザーのみ通過させる | アプリへのアクセス制御。未認証ユーザーを認証フローへ誘導する |
| WIF | 外部 IdP のアイデンティティを Google Cloud の ID に変換するフェデレーション基盤 | 社内 IdP の SAML アサーションを Google トークンに変換する |
| OAuth Client | OAuth 2.0 の認可フローでクライアントを識別するための認証情報(Client ID / Secret) | IAP が WIF と連携するための認証情報 |
| 社内 IdP | ユーザー認証・認可を一元管理する IdP / SSO ソリューション | SAML SP(WIF)からの AuthnRequest を処理する |
今回は社内 IdP として Keycloak(SAML) を使用します。
全体構成
認証フロー
WIF 設定手順
WIF の設定は Organization レベルの権限(roles/iam.workforcePoolAdmin)が必要です。
前提: 社内 IdP が起動しており、SAML メタデータ XML ファイルが取得できる状態であること。
必要な情報
| 項目 | 説明 | 例 |
|---|---|---|
| Organization ID | Google Cloud Organization ID(数字のみ) | 123456789012 |
| IdP メタデータ | 社内 IdP の SAML メタデータ XML ファイル | IdP から取得した .xml ファイル |
Step 1: Workforce Identity プールの作成
Google Cloud Console で操作します。
-
[IAM と管理] > [Workforce Identity の連携] を開く
-
[プールを作成] をクリック
-
以下を入力する:
-
名前:
myapp WIF Pool -
プール ID:
myapppool -
説明:
myapp用 Workforce Identity Federation Pool(任意)
-
名前:
-
[次へ] をクリック
Step 2: SAML プロバイダの追加
プール作成後、続けてプロバイダを追加します。
今回は社内 IdP として Keycloak(SAML) を使用します。
-
IdP のベンダー [Keycloak] を選択する
-
プロトコルとして [SAML] を選択する
-
以下を入力する:
-
[続行] をクリックし、URL2つをメモしておく(後で使います)
-
[続行] をクリックし、属性マッピングを設定する:
-
google.subject←assertion.subject -
attribute.email←assertion.attributes.email[0]
属性マッピングのポイント:
-
-
[プロバイダを追加] をクリック
Step 3: 社内 IdP 側に SAML クライアントを登録
WIF の SAML プロバイダ作成後、社内 IdP 側にも WIF を SAML SP(サービスプロバイダ)として登録する必要があります。
OAuth 追加手順
IAP が WIF を使って認証するための OAuth Client を作成します。
注意: ここで作成する OAuth Client は、WIF(IAP)専用のものです。
gcloud iam oauth-clientsで管理される WIF 用 OAuth Client であり、通常の Google OAuth 2.0 クライアント(APIs & Services > Credentialsで作成するもの)とは別物です。IAP の認証フローでのみ使用します。
必要な情報
| 変数名 | 説明 |
|---|---|
PROJECT_ID |
Google Cloud プロジェクト ID |
Step 1: プロジェクト設定
gcloud config set project "<PROJECT_ID>"
Step 2: OAuth Client の作成
Client ID は Google Cloud が払い出すため、まずダミーの Redirect URI で作成します。
gcloud iam oauth-clients create "myapp-iap-oauth-client" \\
--project="<PROJECT_ID>" \\
--location="global" \\
--client-type="confidential-client" \\
--allowed-grant-types="authorization-code-grant,refresh-token-grant" \\
--allowed-scopes="<https://www.googleapis.com/auth/cloud-platform,openid>" \\
--allowed-redirect-uris="<https://iap.googleapis.com/v1/oauth/clientIds/placeholder:handleRedirect>" \\
--display-name="myapp IAP OAuth Client" \\
--description="myapp IAP用 OAuth Client"
Step 3: Client ID の取得
OAUTH_CLIENT_ID=$(gcloud iam oauth-clients describe \\
"projects/<PROJECT_ID>/locations/global/oauthClients/myapp-iap-oauth-client" \\
--format="value(clientId)")
echo "OAuth Client ID: ${OAUTH_CLIENT_ID}"
出力された値を OAUTH_CLIENT_ID として記録します。
Step 4: Redirect URI の更新
gcloud iam oauth-clients update \\
"projects/<PROJECT_ID>/locations/global/oauthClients/myapp-iap-oauth-client" \\
--allowed-redirect-uris="<https://iap.googleapis.com/v1/oauth/clientIds/${OAUTH_CLIENT_ID}:handleRedirect>"
Step 5: Credentials の作成
gcloud iam oauth-clients credentials create "myapp-iap-oauth-credential" \\
--oauth-client="myapp-iap-oauth-client" \\
--location="global" \\
--display-name="myapp IAP OAuth Credential"
出力の clientSecret フィールドを OAUTH_CLIENT_SECRET として記録します。後から確認したい場合は以下で取得できます:
gcloud iam oauth-clients credentials describe "myapp-iap-oauth-credential" \\
--location="global" \\
--oauth-client="myapp-iap-oauth-client"
IAP 設定手順
WIF と OAuth Client を IAP のバックエンドサービスに紐付けます。
必要な情報
| 変数名 | 説明 |
|---|---|
PROJECT_ID |
Google Cloud プロジェクト ID |
BACKEND_SERVICE_NAME |
IAP 保護対象のバックエンドサービス名 |
WIF_POOL_ID |
myapppool(WIF 設定担当者から受領) |
OAUTH_CLIENT_ID |
OAuth 手順 Step 3 で取得した値 |
OAUTH_CLIENT_SECRET |
OAuth 手順 Step 5 で取得した値 |
Step 1: IAP 設定ファイルの作成
以下の内容を /tmp/iap_settings.yaml として保存します:
access_settings:
identity_sources: ["WORKFORCE_IDENTITY_FEDERATION"]
workforce_identity_settings:
workforce_pools: ["locations/global/workforcePools/<WIF_POOL_ID>"]
oauth2:
client_id: "<OAUTH_CLIENT_ID>"
client_secret: "<OAUTH_CLIENT_SECRET>"
Step 2: IAP 設定の適用
gcloud iap settings set /tmp/iap_settings.yaml \\
--project="<PROJECT_ID>" \\
--resource-type="backend-services" \\
--service="<BACKEND_SERVICE_NAME>"
Step 3: WIF ユーザーへのアクセス権付与
WIF Pool に属するすべてのユーザーへ IAP アクセス権を付与します:
gcloud iap web add-iam-policy-binding \\
--member="principalSet://iam.googleapis.com/locations/global/workforcePools/<WIF_POOL_ID>/*" \\
--role="roles/iap.httpsResourceAccessor" \\
--project="<PROJECT_ID>" \\
--resource-type="backend-services" \\
--service="<BACKEND_SERVICE_NAME>"
補足: 上記は Pool 内の全ユーザーにアクセスを許可する設定です。実運用では、SAML 属性(グループ情報など)を使って
principalSet://iam.googleapis.com/locations/global/workforcePools/<WIF_POOL_ID>/group/admin-teamのように特定のグループのみに制限することも可能です。
動作確認
アプリケーションURLにアクセス
アプリケーションURLにアクセスすると、何回かリダイレクトが走った後、IdPのログイン画面が出れば、IAP, WIFの設定が成功しています。
そこからログイン後、アプリケーションが表示されれば全て成功となります。
苦労したこと
1. WIF Pool は Organization レベルのリソース
WIF Pool は Google Cloud Organization に紐づくリソースであり、特定のプロジェクトには属しません。作成には roles/iam.workforcePoolAdmin を 組織レベル で持つアカウントが必要です。プロジェクトレベルの Owner でも作成できないため、注意が必要です。
2. WIF Pool 内の Provider は 1 つに限定する
IAP 対応のアプリケーションごとに構成できる Workforce プールは 1 つのみで、Workforce プールに含めることができるプロバイダは 1 つのみです。
Pool 内に Provider を複数作成すると、アプリへのアクセス時に 701 エラーが発生します。
IAP が認証フローを開始する際、WIF Pool 内のどの Provider にリダイレクトするかを一意に決定できないためエラーになる。IAP + WIF の構成では、1 Pool につき Provider は 1 つに留める。
3. ここで作る OAuth Client は通常の OAuth 2.0 クライアントとは別物
gcloud iam oauth-clients で管理される WIF 専用の OAuth Client です。通常の Google OAuth 2.0 クライアント(APIs & Services > Credentials で作成するもの)とは異なり、IAP の認証フロー以外では使用できません。
4. WIF Pool を無効化→有効化すると IAP が 401 を返すようになる
症状:
WIF Pool を一度無効化してから再度有効化すると、その後アプリへのアクセス時に IAP が 401 エラーを返すようになる。社内 IdP のログイン画面へのリダイレクトすら起きない。
根本原因:
WIF Pool の無効化→有効化操作により、IAP と OAuth Client の紐付けが失われる。
解決策:
iap_settings.yaml を使って全バックエンドサービスの IAP 設定を正しい OAuth Client で上書きする:
access_settings:
identity_sources: ["WORKFORCE_IDENTITY_FEDERATION"]
workforce_identity_settings:
workforce_pools: ["locations/global/workforcePools/<WIF_POOL_ID>"]
oauth2:
client_id: "<OAUTH_CLIENT_ID>"
client_secret: "<OAUTH_CLIENT_SECRET>"
gcloud iap settings set /tmp/iap_settings.yaml \\
--project="<PROJECT_ID>" \\
--resource-type="backend-services" \\
--service="<BACKEND_SERVICE_NAME>"
WIF Pool を操作した後は、必ず IAP の OAuth Client 設定を確認する習慣をつけておくとよい。
5. Keycloak のログイン操作で 431 が発生する
注: 以下は今回の検証で使用した Keycloak(Cloud Run)での事例です。
症状:
Keycloak のログイン画面は表示されるが、ログインボタンを押すと 431 Request Header Fields Too Large になる。
根本原因:
IAP + WIF アーキテクチャでは、WIF が生成する state パラメータに IAP のコンテキスト(redirect URI・code challenge 等)が丸ごと埋め込まれるため、Keycloak に届くリクエストラインが 7.5KB 前後になる。Keycloak(Quarkus HTTP サーバー)のデフォルト上限は約 4KB であるため、この組み合わせでは常に上限を超えてしまう。
解決策:
Keycloak の Cloud Run 環境変数でヘッダーサイズ上限を引き上げる:
| 環境変数 | 値 |
|---|---|
QUARKUS_HTTP_LIMITS_MAX_INITIAL_LINE_LENGTH |
65536 |
QUARKUS_HTTP_LIMITS_MAX_HEADER_SIZE |
65536 |
QUARKUS_HTTP_LIMITS_MAX_HEADER_LIST_SIZE |
65536 |
Terraform で管理している場合は必ず IaC に反映させること。手動で gcloud run services update した場合、次回の terraform apply で環境変数が消えてしまう。
6. IdP のログイン画面で「Cookie not found」エラーが発生する
注: 以下は今回の検証で使用した Keycloak(Cloud Run)での事例です。社内 IdP の種類や構成によっては異なる原因・対処が必要になる場合があります。
症状:
IAP → WIF → IdP のリダイレクトは成功し、ブラウザに IdP のログイン画面が表示される。しかしログインボタンを押すと Cookie not found エラーが返り、認証が完了しない。
根本原因:
HTTP レスポンスに Set-Cookie ヘッダーが含まれていなかった。Keycloak はログインセッションを Cookie で管理するため、Cookie が設定されないとセッションが成立せずエラーになる。
調査過程:
IAP → WIF → Keycloak というリダイレクトの連鎖の中で、state パラメータ等が暗号化・エンコードされ、URL 文字列が非常に大きくなっていた。これが Cookie の設定に何らかの影響を与えている可能性があった。
検証のため、Cloud Run ではなく GCE(Compute Engine)上に Keycloak を立てたところ、同じ認証フローで問題なく動作した。つまり原因は Keycloak 自体や認証フローの設定ではなく、Cloud Run 側の通信プロトコルにあると判断した。
解決策:
Cloud Run のポートプロトコルを h2c(HTTP/2 cleartext)に変更したところ、Set-Cookie が正しくレスポンスに含まれるようになり、ログインが成功した。
h2c(HTTP/2)に変更することで改善される技術的な理由:
HTTP/1.1 では、Keycloak が発行する複数の大きな Cookie(AUTH_SESSION_ID、KC_RESTART、KEYCLOAK_SESSION 等)をプレーンテキストのヘッダーとして送信する。ロードバランサーやプロキシにはヘッダーサイズの上限があり、合計サイズが上限を超えると Set-Cookie ヘッダーが欠落・切り捨てされることがある。
HTTP/2(h2c)では以下の 2 つの仕組みによりこの問題が解消される:
- HPACK によるヘッダー圧縮: HTTP/2 は HPACK 形式でヘッダーを圧縮して送受信する。同じ Cookie 情報をより小さいサイズで伝送できるため、サイズ起因の欠落リスクが減る。
-
HEADERS / CONTINUATION フレームによる分割伝送: HTTP/2 はヘッダーをフレーム単位で扱う。1 フレームに収まらない大きなヘッダーは
CONTINUATIONフレームで分割して送信され、受信側で確実に結合される。HTTP/1.1 にはこの仕組みがなく、サイズ超過時に単純に切り捨てられる。
[HEADERS frame] → Set-Cookie ヘッダーの先頭部分
[CONTINUATION frame] → Set-Cookie ヘッダーの続き
[DATA frame] → レスポンスボディ
まとめ
| フェーズ | 主な作業 |
|---|---|
| ① WIF 設定 | WIF Pool と SAML Provider を作成し、WIF Pool ID を共有する |
| ② OAuth 設定 | OAuth Client と Credentials を作成する |
| ③ IAP 設定 | IAP に WIF + OAuth を紐付け、ユーザーへアクセス権を付与する |
この構成により、社内 IdP のユーザー管理を変えるだけでアプリへのアクセス制御を一元管理できます。既存の社内 IdP を活用するため、IdP 自体の新たな構築は不要で、WIF・OAuth・IAP の設定のみで導入できる点が大きな利点です。







