Entra IDでサインインは通るのにGoogle Cloudが403|原因はグループ

「Entra ID Groups」「No Sync, Only Claims」の文字。上段のAssumedレーンはグループがコピーされる図に赤い×が重なり、下段のActualレーンではgroups claimのカードが門へ飛び、その先の引き出しは空になっている図 認証・IAM

はじめに|サインインは通るのに403で止まる

Microsoft Entra ID のアカウントで Google Cloud のアプリを開く構成にすると、「サインインは通るのに 403 で止まる」という状態に必ず一度は出会います。

原因のほとんどは、グループの扱いにあります。そして多くの場合、「Entra ID のグループが Google Cloud に同期される」という前提が間違っているところから来ています。

この記事では、グループが許可に繋がるまでの仕組みと、繋がらなかったときの切り分けを扱います。認証と認可の分担やアクセス 1 回の流れは、Workforce Identity Federationの仕組みを図解に書きました。

本記事のまとめ

  • 同期は一切ない。アクセスのたびに ID トークンの groups クレームで申告されている。
  • groups クレームは既定では出ない。設定し忘れると全員が 403 になる。
  • 所属グループが多すぎるとクレームごと落ちる。その人だけが理由の出ない 403 になる。
  • 許可の宛先は、グループの表示名ではなくオブジェクト ID である。
Entra IDのすべてのグループ一覧。gcp-workforce-identity-testという1つのグループに対してオブジェクトIDの列があり、その値は<GROUP-OBJECT-ID>に伏せてある
Entra ID 側のグループには、表示名とは別にオブジェクト ID が割り当たっている。許可の宛先になるのはこの ID のほう。値は <GROUP-OBJECT-ID> に伏せた

Entra IDのグループはGoogle Cloudに同期されない

同期は一切ありません。Google Cloud 側にグループの実体はなく、コピーも置かれていません。

正しいモデルはこうです。

  • グループ情報は Google Cloud に保存されていない。
  • アクセスのたびに ID トークンの groups クレームとして運ばれてくる。クレームは「主張」の意味で、トークンに入る属性の申し立て 1 項目を指す。
  • IAP(アプリの手前で通すか止めるかを決める Google Cloud の仕組み)は、その瞬間に届いた一覧と許可設定を照合しているだけ。

Cloud Identity や Google Workspace のグループは、この構成には一切登場しません。Google Cloud 側にグループを作る作業そのものが存在しないので、探しても見つかりません。

証拠はグループ数の上限にぶつかったときの挙動

Entra ID は、1 人の利用者の所属グループが一定数を超えると、groups クレームをトークンから落とします。公式ドキュメントでは JWT で 200、SAML で 150 とされています。この記事の構成は OIDC の ID トークン(JWT)なので、200 が上限です。Entra ID は SAML のプロバイダとしても構成でき、その場合は 150 になります。

上限を超えると、groups の代わりに「残りは Microsoft Graph に問い合わせてください」という案内だけが入ります。

"_claim_names":   { "groups": "src1" }
"_claim_sources": { "src1": { "endpoint": "https://graph.microsoft.com/..." } }

クレームが落ちると、その利用者だけがアクセスできなくなります。Google Cloud 側にコピーがあれば、クレームが届かなくても通るはずです。通らないので、コピーは無いと分かります。

上限の数値と、グループが省略されて Microsoft Graph への案内に差し替わる挙動は、アプリケーションのグループ要求を構成する(Microsoft)に記載があります。この構成は自分の環境で動かしていますが、200 グループを超えたときの 403 だけは起こしていません。

そのため、対象の利用者の所属グループ数は事前に数えておくことをおすすめします。アプリに割り当てたグループだけを出す設定にして、数を抑える方法もあります。

Google管理コンソールのグループ一覧。GAS Owners、GCP Admins、GCP Marketing、GCP Operations、GCP Test Usersの5つが並び、Entra ID側のグループは1つも入っていない。グループのメールアドレスは<GROUP-EMAIL>に伏せてある
Google Workspace 側のグループ一覧。並んでいる 5 つはすべて Google 側で作ったもので、Entra ID のグループは 1 つも入っていない。メールアドレスは <GROUP-EMAIL> に伏せた

groupsクレームは既定では出ない

Google Cloud 側を正しく組んでも、Entra ID 側で設定しなければ全員が 403 になります。ここが抜けやすい箇所です。

Entra ID のアプリ登録のトークン構成で「グループ要求の追加」を実行して、はじめて groups がトークンに乗ります。アプリのマニフェストではこう書かれます。

{
  "groupMembershipClaims": "SecurityGroup"
}

設定の種類と、出せるグループの絞り込み方は オプションの要求を構成する(Microsoft)にまとまっています。

届いたクレームを Google Cloud 側の属性として扱うには、プール側に読み替え規則を書きます。プールは、Entra ID から来た人を受け入れる Google Cloud 側の窓口です。Terraform ではこうなります。

attribute_mapping = {
  "google.subject" = "assertion.oid"
  "google.groups"  = "assertion.groups"
}

assertion.* は「届いたトークンのこのクレーム」という参照です。対応づけているのはトークンの項目名と Google Cloud 側の属性名で、Entra ID のグループと Google Cloud のグループを繋いでいるのではありません。そもそも Google Cloud 側にグループは存在しません。

識別子に oid を使うのは、メールアドレスやユーザー名が変更されうるためです。変わる値を本人の識別に使うと、別の利用者として扱われる余地が生まれます。構成手順は Microsoft Entra ID との Workforce Identity 連携を構成するにあります。

Entra IDのアプリ登録のトークン構成画面。オプションの要求の表は「結果がありません。」のまま空で、上に「グループ要求の追加」のボタンが置かれている
トークン構成の表は空のまま。groups はここに自分で足すもので、既定では乗らない

許可の宛先は表示名ではなくオブジェクトID

IAM に書く宛先は、実体を指すポインタではなくただの文字列です。形式は 2 つあります。

グループ単位
principalSet://iam.googleapis.com/locations/global/workforcePools/<プール ID>/group/<グループのオブジェクト ID>

個人単位
principal://iam.googleapis.com/locations/global/workforcePools/<プール ID>/subject/<利用者のオブジェクト ID>

個人単位でも技術的には付与できます。私がグループ単位にしているのは、権限を個人に直付けしないという運用方針に合わせているためで、仕組み上の制約ではありません。グループ単位にすると、利用者の増減が Entra ID 側の操作だけで済むという利点が付きます。

宛先にはグループの表示名ではなくオブジェクト ID を使います。表示名は後から変更できてしまい、意図しないグループへ権限が移りかねないためです。連携相手からは、グループ名ではなくオブジェクト ID を共有してもらう必要があります。

混同しやすい点を 1 つ。グループ単位にするのは認可であって、認証ではありません。認証されるのは常に個人で、利用者の識別子は消えません。監査ログにも「どのグループの誰か」ではなく個人が残ります。

GCPのIAM画面をフィルタprincipalSet://で絞った結果。プリンシパルが<POOL-ID>/<GROUP-OBJECT-ID>の形式で表示され、ロールにCloud Run 閲覧者が付いている
IAM の一覧をフィルタ principalSet:// で絞ったところ。許可の宛先はプール ID とグループのオブジェクト ID の組で書かれている。プール ID とオブジェクト ID は伏せ、本記事と無関係なプリンシパル 1 行はプリンシパル名を伏せた

サインインできない・403になるとき、どこが切れているか

症状が似ていても、原因と直す側が違います。症状から当たりを付けられるように並べます。

症状切れている箇所直す側
Microsoft のサインイン画面にたどり着かないプール ID、Provider ID、Entra ID 側の戻り先 URL が一致していない両者で突き合わせ
ある日を境に全員がサインインできなくなったクライアントシークレットの有効期限切れEntra ID 側とプール管理者
サインインは通るが、特定の人だけ 403groups クレームが届いていない(グループ数の上限超え)Entra ID 側
サインインは通るが、全員 403グループの要求が未設定。または許可設定の宛先に書いたオブジェクト ID が違うEntra ID 側 / Google Cloud 側
サインインできるのに何も見えない必要な権限が不足。アプリを開く権限と、管理画面で一覧を見る権限は別Google Cloud 側

要点は 「全員か、一部か」で切り分けられることです。シークレットの期限切れは全員が同時にサインインできなくなり、グループ数の上限は所属が多い利用者だけが 403 になります。どちらもアプリにたどり着けない点は同じですが、原因と対処、担当者が違います。

クライアントシークレットの期限が近づいても、ポータルの画面には警告が出ません。期限を管理する担当と、更新のタイミングを決めておいてください。

なお、上の表は設定内容と仕様から導いた想定で、全ケースを検証環境で再現したものではありません。次の 3 点も未検証です。

  • グループからメンバーを外したとき、サインイン済みの利用者がいつアクセスできなくなるか。セッションの有効期間のあいだは古い情報で通る可能性がある。即時の遮断が要件なら、別途の検証が要る。
  • Microsoft Graph をソースにしてグループを取得する方式。上限超過への恒久対応になるが、私はまだ試していない。
  • グループ数の上限を超えたときの 403 を、自分の環境で再現すること。挙動は公式ドキュメントの記載にもとづいている。
Entra IDのアプリ登録の証明書とシークレット画面。クライアント シークレットのタブに1件あり、有効期限は2027/3/11。値とシークレットIDは<SECRET-VALUE>と<SECRET-ID>に伏せてある
クライアントシークレットには有効期限が 1 つ並んでいるだけで、画面には期限が近いことを知らせる表示が出ない。値とシークレット ID は伏せた

まとめ

グループは同期されず、アクセスのたびにトークンで申告されます。この 1 点が分かると、403 の切り分けが一気に楽になります。

  • Google Cloud 側にグループの実体は無く、コピーも置かれない。グループを作る作業は存在せず、Entra ID 側が唯一の正本になる。
  • groups クレームは既定では出ない。Entra ID 側でグループの要求を追加していないと、Google Cloud 側が正しくても全員 403 になる。
  • JWT では 200 グループを超えるとクレームごと落ちる。所属が多い利用者だけが、理由の出ない 403 になる。
  • 許可の宛先は表示名ではなくオブジェクト ID。グループ単位にするのは認可であって認証ではない。

構築前にやっておくとよいのは 2 つです。対象の利用者の所属グループ数を数えることと、クライアントシークレットの期限管理の担当と更新タイミングを決めること。どちらも後から気づくと、全員が使えない状態で慌てることになります。

なお、グループ数の上限と Entra ID の管理画面の構成は変わることがあります。手を動かす前に、Microsoft と Google Cloud の公式ドキュメントで最新を確かめてください。