Google Cloud の権限をチーム単位で付与する — アクセスグループとフォルダの二軸設計
はじめに
Google Cloud を運用していると、遅かれ早かれ「ユーザー個別に権限を付けるのをやめたい」という話になります。人が入り、異動し、抜けるたびに IAM ポリシーを手で追いかけるのは現実的ではなく、権限の棚卸しもできなくなるからです。
そこでよく出てくるのが「フォルダで切るのがいいのか、グループアドレスに渡すのがいいのか」という問いですが、この問いの立て方自体がずれています。結論から書くと、この二つは代替案ではなく別の軸です。フォルダは権限のスコープを切るためのリソース階層であり、グループは IAM ポリシーで参照するプリンシパル識別子です。したがって答えは「どちらか」ではなく「専用のアクセスグループを作り、そのグループにフォルダレベルでロールを付与する」という組み合わせになります。
本記事では、この二軸設計の根拠、グループの分類と命名、実装例、移行手順、そして踏みやすい落とし穴を整理します。
二軸で考える
リソース階層は組織・フォルダ・プロジェクト・リソースという木構造で、上位で付与した IAM の allow ポリシーや組織ポリシーは下位へ和集合で継承されます。これがスコープの軸です。
一方、IAM のプリンシパルには Google アカウント、サービスアカウント、Google グループ、ドメインなどがあり、グループは group:GROUP_EMAIL_ADDRESS という形式で参照されます。これがプリンシパルの軸です。
つまり「グループアドレスを使うかどうか」ではなく、「何の目的で管理されているグループを IAM に使うか」が本質になります。50 人に同じ権限を渡すとき、50 個の個別バインディングではなく、1 個のグループバインディングと 50 個のメンバーシップで表現する。人の増減は IAM ポリシーに触らずメンバーシップだけで完結する。これがグループ軸の効用です。
flowchart TD
HR[HR / 外部 IdP] --> ORGGRP[組織グループ]
ORGGRP --> ACCGRP[専用アクセスグループ]
ORGGRP --> ENFGRP[強制グループ]
ACCGRP --> ORGIAM[組織レベル IAM]
ACCGRP --> FOLDERIAM[フォルダレベル IAM]
ACCGRP --> PROJIAM[プロジェクトレベル IAM]
ORGIAM --> ORG[Organization]
FOLDERIAM --> FOLDER[Folder]
PROJIAM --> PROJ[Project]
PROJ --> RES[Service Resources]
SA[サービスアカウント] --> RES
SACTRL[SA 個別 IAM / Impersonation 制御] --> SA
ORGPOL[組織ポリシー] --> ORG
ORGPOL --> FOLDER
ORGPOL --> PROJ
ACCGRP --> LOG[Cloud Audit Logs]
FOLDERIAM --> LOG
LOG --> REVIEW[Policy Analyzer / Recommender]
グループを 4 種類に分ける
Google の Google グループの使用に関するベストプラクティスは、グループを目的別に分類し、それぞれ異なるライフサイクルで扱うよう求めています。
- 組織グループ: 人事マスタや外部 IdP 由来。部門・レポートライン・地理などの所属を表す。信頼できるソースから排他的にプロビジョニングし、手動編集を許さない。
- アクセスグループ: Google Cloud のロールをバインドするためだけに作る。メールや共同作業には一切使わない。
- 強制グループ: 2 段階認証プロセスや SAML プロファイル割り当てなど、制限を一括強制する。
whoCanLeaveGroupをNONE_CAN_LEAVEにして離脱を禁止する。 - コラボレーショングループ: メーリングリストや共同編集用。エンドユーザー主導で運用される。
ここで一番効く指針は、組織グループに直接ロールを付けず、アクセスグループのメンバーとして入れて間接化することです。所属はアクセス要件を表しません。「エンジニアリング部所属」と「本番の Cloud Run をデプロイしてよい」は別の事実であり、前者に後者を紐付けると過剰権限が構造的に生まれます。
同じ理由で、team-a@example.com のようなメーリングリストを IAM に流用してはいけません。コラボレーショングループはライフサイクルが緩く、誰でも参加できることが多く、権限昇格の温床になります。
ネストのルールも重要です。組織グループ同士のネストは推奨されますが、アクセスグループと強制グループは原則ネストしません。誰が何にアクセスできるか追えなくなるうえ、異なるメンバーシップポリシーが混ざって意図しないバイパスが起きます。伝播も遅くなります。
命名規則
グループの目的が名前から一目で分かるようにします。Google 公式の推奨ユーザーグループは横断管理系に grp-gcp-* 形式を提示していますが、チーム権限まで含めるなら型を接頭辞で明示する方式が扱いやすいです。
org.finance-all@example.com # 所属・人事由来
access.team-a-viewers@example.com # アクセス付与用
access.team-a-admins@example.com # アクセス付与用
access.team-a-prod-deployers@example.com # 本番限定の職務
enforcement.mfa-required@example.com # 強制ポリシー適用用
collab.team-a@example.com # メール・共同作業用もう一段踏み込むなら、アクセスグループを access.example.com のようなセカンダリドメインに置き、そのドメインに MX レコードを作らない構成が有効です。Cloud Identity では所有権を証明済みのメインドメインのサブドメインは追加検証なしで登録でき、MX を持たせないことで IAM 専用グループへの外部メールの流入を DNS レベルで遮断できます。
誰がグループを操作できるのか
ここで実務上よくつまずくのが、Google Cloud の Owner なのにグループを作れない、というパターンです。これは異常ではなく仕様です。ID プレーン(Cloud Identity / Google Workspace)とリソースプレーン(Google Cloud IAM)は権限体系が別で、roles/owner は組織配下のリソースに対する権限にすぎず、ディレクトリ上のグループは Google Cloud のリソースではありません。実際、Cloud コンソールのグループ管理画面は IAM 側の roles/resourcemanager.organizationViewer に加えて、IAM ではなく Workspace が管理するグループ権限を別途要求します。つまり、GCP 側の IAM ロールをいくら足しても解決しません。
打ち手は権限の強さ順に 3 つあります。
- グループのオーナーにしてもらう: 管理者ロールは一切不要で、そのグループのメンバー管理だけができます。Groups API のセットアップ手順も、全グループへのアクセスが不要なら管理者ロールを付けずに対象グループのオーナーにする方法を挙げています。各アクセスグループにオーナーを置く運用とそのまま噛み合うため、通常はこれが第一選択です。
- グループ管理者(Groups Admin)ロールを付与してもらう: Workspace の事前構築済み管理者ロールで、特権管理者ではありません。ドメイン全体のグループを自分で作成・管理する必要がある場合に使います。
- カスタム管理者ロールを作ってもらう: Groups Admin でも広すぎるなら、Admin API のグループ作成権限だけを持つロールに絞ります。
いずれも特権管理者への依頼が起点になるのは避けられませんが、これは設計上の意図です。グループ管理権が Google Cloud の Owner に自動で付いてしまうと、Owner が自分のグループに外部メンバーを入れてドメイン制限共有を回避できてしまいます(後述する昇格経路そのものです)。実務では、アクセスグループの器は特権管理者側で作り、メンバー管理は各グループのオーナーであるチーム側が持つ、という線引きを Workspace 管理チームと合意しておくのが落としどころになります。
グループの権限がどうしても得られないとき
前項の依頼が通らない組織もあります。その場合でも、諦める前に確認する回線が 2 つあります。
ひとつは依頼ベースの運用に落とすことです。グループの作成とメンバー追加を Workspace チームへのチケットにするだけなら、ここまでの設計はそのまま成立します。遅いのが問題であって、壊れてはいません。SLA と申請フォーム(グループ名・目的・オーナー・初期メンバー)を決めれば、月に数件程度の異動には十分追随できます。
もうひとつは、頼む相手を変えることです。外部 IdP がある組織なら、グループの実体は IdP 側にあり、Workspace 管理者は同期設定を一度作るだけです。IdP チームのほうが話が通ることは珍しくありません。さらに Workforce Identity Federation を使えば Cloud Identity へ同期せず、IdP のグループを principalSet で直接 IAM にマッピングできます。Cloud Identity のグループ管理権限が不要になるため、正面突破としては最も効きます(後述する 400 グループ・40 文字の制限は前提になります)。
それでも何も得られない場合は、グループの「形」だけを IaC で再現します。
locals {
team_a_viewers = [
"user:alice@example.com",
"user:bob@example.com",
]
}
resource "google_folder_iam_member" "team_a_viewers" {
for_each = toset(local.team_a_viewers)
folder = "456789012345"
role = "roles/viewer"
member = each.value
}これは個別付与そのものなので推奨構成ではありませんが、手でコンソールを触るのとは決定的に違います。人の追加・削除が locals の 1 箇所に集約され、PR レビューで可視化され、plan で drift が出て、棚卸しは Git を見れば済みます。グループの利点のうち、運用と監査の部分だけをコード側で取り戻す形です。将来グループが使えるようになったとき、locals のリストをそのままアクセスグループのメンバーシップへ移せるので、移行コストも小さく保てます。
失うものは正直に見ておく必要があります。バインディング数が増えて allow ポリシーのサイズ上限に近づきますし、退職者の一括剥奪は「全ポリシーから当該 user: を消す」作業になり、消し漏れが即リスクになります。Google グループでしか共有できないサービスとも噛み合いません。
そして、これを恒久策ではなく一時凌ぎとして扱うことが大事です。グループ権限が下りない理由はたいてい技術ではなく組織で、「Workspace の管理権限は情シス、Google Cloud は開発」という線引きが固いだけのことが多いからです。交渉材料になるのは依頼の粒度を下げることです。Groups Admin をくれ、ではなく「access.example.com というセカンダリドメイン配下のグループに限り、器の作成は情シス、オーナーは我々」と提案する。オーナー指定なら管理者ロールは一切発生しないので、相手のリスクはほぼゼロです。説得の芯は退職者処理に置きます。個別付与のままでは、退職時に全フォルダ・全プロジェクトのポリシーを走査しないと権限が残り、監査で必ず指摘されます。これは開発チームの都合ではなく情シス側の説明責任の話なので、話が噛み合いやすい論点です。
フォルダをどう切るか
もう一方の軸であるフォルダは、単なる整理用コンテナではなく、ポリシーのアタッチポイントであり分離境界です。エンタープライズ基盤ブループリントは組織直下に bootstrap・common・production・nonproduction・development・networking の 6 フォルダを置き、フォルダ単位で allow ポリシーと組織ポリシーを継承させる構造を示しています。
ただしこれは大企業と専任プラットフォームチームを前提にした構成です。中小規模なら development と production の 2 フォルダから始め、必要が生じてから common や networking を足すのが現実的です。数人のチームで深いフォルダネストを最初から作るのは過剰設計になります。ランディングゾーンの階層設計の判断軸は、子会社や地域でポリシー要件が大きく異なるなら地域ベース、プロダクトチームが強い自律性を必要とするならチームベース、それ以外は環境ベース、というものです。多くの組織では環境ベースか、上位に環境・下位にチームを置くハイブリッドに落ち着きます。
なお Resource Manager のクォータ上、フォルダは 10 階層までネストでき、1 つの親フォルダの直下には 300 フォルダまで置けます。実務でこの上限に当たることはまずありませんが、当たりそうなら設計を疑ったほうがよいでしょう。
付与レベルの四層構成
| 付与レベル | 推奨度 | 用途 | 注意点 |
|---|---|---|---|
| 組織レベル | 限定的 | ネットワーク・セキュリティ・監査・課金など、実際に組織横断責務を持つ共通職能のみ | 全子孫へ継承されるため blast radius が極めて大きい。開発チームには広げない |
| フォルダレベル | 標準形 | 事業部・プロダクト・環境単位のチームアクセスグループ付与 | フォルダ設計が悪いと権限とコストの両方が歪む。プロジェクト移動時は継承が変わる |
| プロジェクトレベル | 差分表現 | ワークロード固有の職務分離(deployer / data-reader など) | プロジェクト数が増えるとバインディング数が膨らむ。共通権限の重複に注意 |
| リソースレベル | 例外のみ | 期限付き対応、特定バケットのみ閲覧、タグベースの切り出しなどの条件付き例外 | ポリシースプロールを招く。標準形にはしない |
実務的には、access.team-a-viewers@example.com のような共通閲覧グループをフォルダへ、access.team-a-prod-deployers@example.com のような限定グループを個別プロジェクトへ、と重ねる形がよく機能します。ここでのアクセスグループは職務ごとに作り、複数ワークロードへ再利用しないのが原則です。
基本ロールを使わない
グループとフォルダが決まったら、次はロールです。基本ロール(Owner / Editor / Viewer)は本番で使いません。Google 公式のロール推奨事項の概要によれば、Editor だけで 3,000 を超える権限を含み、プロジェクトへの広範なアクセスを与えます。これをグループに付けると、影響が全メンバーに一斉に波及します。過剰権限のグループは、過剰権限の個人より危険です。
事前定義ロールを最小権限で付与し、粒度が合わない場合にだけカスタムロールを検討します。カスタムロールは再利用するなら組織レベル、局所要件ならプロジェクトレベルで作るのが自然ですが、保守責任は自組織に残ることを忘れないでください。
さらに、本番の強い権限は常時付与をやめ、Privileged Access Manager(PAM)で時間制限・承認付きの一時昇格に置き換えられます。エンタイトルメントで「誰が申請できるか」「どのロールを」「最大何分」「承認要否」「理由の要否」を定義します。申請可能者は個人ではなくグループで指定するのが定石です。基本ロールは PAM では使えません。
一時アクセスを PAM ではなく IAM で表現したい場合は、IAM Conditions を使い、恒久グループの付与を崩さずに条件付きバインディングを別途追加します。
{
"version": 3,
"bindings": [
{
"role": "roles/run.developer",
"members": [
"group:access.team-a-prod-deployers@example.com"
],
"condition": {
"title": "temporary_prod_access",
"description": "Temporary deployment access during cutover window",
"expression": "request.time < timestamp('2026-08-01T00:00:00Z')"
}
}
]
}ここには落とし穴があります。同じプリンシパルに同じロールを条件なしで既に付与している場合、条件付きバインディングは何も制限しません。条件付き付与は無条件付与を上書きしないからです。また、基本ロールと allUsers / allAuthenticatedUsers には条件を付けられません。
実装例
gcloud
# チーム用フォルダを作成
gcloud resource-manager folders create \
--display-name=team-a \
--organization=123456789012
# フォルダにチーム閲覧グループを付与
gcloud resource-manager folders add-iam-policy-binding 456789012345 \
--member="group:access.team-a-viewers@example.com" \
--role="roles/viewer" \
--condition=None
# フォルダにチーム管理グループを付与
gcloud resource-manager folders add-iam-policy-binding 456789012345 \
--member="group:access.team-a-admins@example.com" \
--role="roles/resourcemanager.projectIamAdmin" \
--condition=None
# プロジェクト固有の細分化グループを付与
gcloud projects add-iam-policy-binding my-prod-project \
--member="group:access.team-a-prod-deployers@example.com" \
--role="roles/run.developer" \
--condition=Noneチームに自律性を与えつつシャドー IT を防ぐパターンも同じ構造で作れます。組織ノードでは開発者グループに roles/resourcemanager.organizationViewer だけを付けて階層のメタデータのみ見せ、チーム専用のサンドボックスフォルダにだけ roles/resourcemanager.projectCreator を付与します。これでプロジェクト作成の自由度は自分のフォルダ内に閉じます。
Terraform
Terraform のベストプラクティスは、google_*_iam_policy と google_*_iam_binding が authoritative であること、つまり記載されていないメンバーを次の apply で削除してしまうことを警告しています。既定は加算的な google_*_iam_member を使い、authoritative なリソースはそのロールやポリシーを完全に自チームが所有している場合にだけ使ってください。同一ロールで binding と member を混在させるのも事故のもとです。
resource "google_folder_iam_member" "team_a_admin" {
folder = "456789012345"
role = "roles/resourcemanager.projectIamAdmin"
member = "group:access.team-a-admins@example.com"
}
resource "google_project_iam_member" "team_a_prod_deployer" {
project = "my-prod-project"
role = "roles/run.developer"
member = "group:access.team-a-prod-deployers@example.com"
}グループ自体も google_cloud_identity_group と google_cloud_identity_group_membership で管理できますが、メンバーシップは Terraform の外(IdP や HR)で管理するほうが素直です。そして、デプロイパイプラインに包括的な「グループ管理者(Groups Admin)」を渡すのは禁じ手です。パイプラインが乗っ取られれば、攻撃者は任意のアクセスグループを作って自分を放り込み、本番への恒久的な最高権限を手に入れられます。代わりに Google Workspace のカスタム管理者ロールとして Admin API のグループ作成権限だけを持つロールを作り、それをデプロイ用サービスアカウントに割り当てます。作成 API を呼ぶ際は WITH_INITIAL_OWNER フラグを有効にし、作成された瞬間のオーナーをパイプラインではなく人間の管理アカウントにします。自動化は作成だけを担い、メンバーの追加・削除は人間側に残ります。
組織ポリシー
IAM だけでは防ぎにくい構成事故は、組織ポリシーで予防します。人向けのチームアクセス設計なら、最低限この 3 つは検討してください。
iam.allowedPolicyMemberDomains: ドメイン制限共有。許可ドメイン外のプリンシパルを IAM ポリシーから締め出す。iam.disableServiceAccountKeyCreationとiam.disableServiceAccountKeyUpload: サービスアカウント鍵の作成・アップロードを禁止する。iam.automaticIamGrantsForDefaultServiceAccounts: デフォルトサービスアカウントへの自動 Editor 付与を止める。
name: organizations/123456789012/policies/iam.disableServiceAccountKeyCreation
spec:
rules:
- enforce: trueこれらは組織またはフォルダの上位で定義し、例外は必要最小限の下位ノードでだけオーバーライドします。
落とし穴
グループ経由の権限昇格が最大の罠です。ドメイン制限共有は「グループのドメイン」しか評価せず、「グループメンバーの」ドメインは評価しません。したがって、プロジェクト管理者が外部メンバーを自分のグループに追加し、そのグループにロールを付ければ制限をバイパスできます。対策は Workspace 側でグループオーナーによる外部メンバー追加を禁止することです。なお 2024 年 5 月 3 日以降に作成された組織では iam.allowedPolicyMemberDomains のレガシーマネージド制約がデフォルトで適用され、自ドメインが唯一の許可ドメインとして登録されます。それ以前に作った組織では明示的な有効化が必要です。
allUsers / allAuthenticatedUsers はそれぞれインターネット全員・全 Google 認証ユーザーを意味し、公開アクセスになります。ドメイン制限共有のレガシー制約はこれらを直接カバーしないため、Cloud Storage の公開アクセスの防止のようなサービス別制御やカスタム組織ポリシーで別途遮断します。
サービスアカウントはチームの代替ではありません。人を表さないので組織グループには入れず、人間用グループとは分離します。サービスアカウントを安全に使用するベストプラクティスは、サービスアカウントへのアクセス、特に impersonation の権限をプロジェクトやフォルダで広く付けず、個別サービスアカウント単位で管理するよう勧めています。Token Creator の広範な付与は水平移動の経路になります。
タグベースの条件にも昇格経路があります。roles/resourcemanager.tagUser と roles/viewer 程度の一見無害な組み合わせでも、条件付きバインディングが参照するタグを後から自分で付与できるなら昇格が成立します。タグ付与権限と、そのタグで恩恵を受ける ID は分離してください。
伝播遅延も忘れがちです。ロール付与の伝播は通常約 2 分ですが、7 分以上かかることもあります。グループ変更はさらに遅く、ネストグループはもっと遅くなります。新規作成グループにドメイン制限共有下でロールを付けると failedPrecondition になることがあり、最大 24 時間待つ必要があります。メンバー追加は削除より速く伝播する、という非対称性は移行手順の設計にそのまま効きます。
グループ削除は不可逆です。IAM ロールを階層のすべてのレベルから外し、剥奪が完全に伝播したことを確認するため最低 7 日待ってから物理削除してください。
移行手順
既存の直付け IAM からグループ中心へ移すのは、権限設計と ID ライフサイクルを同時に変える作業です。単なる置き換えではなくアクセスモデルの再設計として進めます。
- 現状棚卸し。組織・主要フォルダ・主要プロジェクトの IAM ポリシーを
get-iam-policyで取得し、直接ユーザー付与・既存グループ付与・サービスアカウント関連付与に分類する。gcloud asset search-all-iam-policiesを使えば組織全体を横断して外部ドメイン ID の混入も洗える。 - ターゲットモデル設計。
alice@example.com + roles/viewer on folder Xをaccess.team-a-viewers@example.com + roles/viewer on folder Xへ、という形でマトリクス化する。 - グループ種別の整理。既存の
team-a@...はコラボレーション用として残し、IAM 用にaccess.*を新設する。 - 組織グループとの接続方針を決める。
org.*をアクセスグループへネストするか、申請ベースで直接参加させるか。 - グループオーナーと承認ルールを決める。各アクセスグループにオーナーを置き、参加理由・承認者・最大有効期間・更新手順を定義する。
- テスト用フォルダで検証する。フォルダ移動や継承変更は影響が大きいので、まず sandbox で継承の形を再現する。
- Policy Simulator で影響を事前確認する。直付けを削除したときに必要アクセスが失われないかをシミュレーションする。
- グループ付与を先に追加し、直付け削除は後で実施する。追加のほうが削除より速く伝播するため、追加→確認→削除の順が安全。
- サービスアカウント関連は別トラックで処理する。人向けの権限移行と impersonation / Token Creator の整理を混ぜない。
- ロールバック計画を保持する。取得済みのポリシー JSON を保存し、変更単位を小さく保つ。
フォルダ間でプロジェクトを移動する場合は、移動を実行するプリンシパルに移動元と移動先の双方の親フォルダで roles/resourcemanager.projectMover が必要です(対象プロジェクト側の編集権限も要ります)。継承ポリシーが動的に変わるため、移動後に既存のワークロードが権限を失って止まる事故が起きやすい箇所です。
外部 IdP から同期する場合は、Google Cloud Directory Sync(GCDS)の設定にも注意が必要です。設定ファイルに <INDEPENDENT_GROUP_SYNC> が存在する場合は削除し、<ADD_VALID_GROUP_MEMBERS_ONLY> を有効にします。前者はユーザーの同期状況と独立してグループを処理しようとするため、ユーザーのプロビジョニング未完了状態でメンバーシップを解決しようとしてメンバーを取りこぼします。設定変更後はいきなり本番同期せず、キャッシュクリアを有効にした模擬同期でグループの強制解散やユーザー削除の警告が出ないか確認してください。
Workforce Identity Federation を使って Cloud Identity へ同期せずフェデレーションする構成もありますが、制限を前提に設計する必要があります。google.groups の属性マッピングではグループ名を 40 文字以内に抑えることが推奨され、ユーザーが外部 IdP 側で 400 を超えるグループに所属しているとトークンサイズが上限を超えてサインイン自体が失敗します。属性条件でフィルタし、トークンに含めるのはアクセスグループだけに絞るのが安全です。
運用と監査
移行が終わってからが本番です。
- 人間ユーザーへの直付けは原則禁止し、例外は期限付き・承認付きで管理する。
- アクセスグループには必ずオーナーを置く。参加には理由・承認・有効期限を持たせる。
- 組織グループは HR / IdP 起点のみで更新し、手動編集を許さない。
- 外部メンバーを含むアクセスグループは明示的に区別し、制限する。
- IAM Recommender を月次でレビューする。ロール推奨の最大観測期間は 90 日で、プロジェクトレベルの推奨は 30 日または 60 日に短縮できる。
- Policy Analyzer で有効アクセスを確認する。グループ展開を使えばグループ経由の実ユーザーまで追える。
- IAM 監査ログで
SetIamPolicyなどの変更を追える状態にし、Workspace のグループ変更ログも Cloud Logging に集約する。
レビュー頻度自体は組織のガバナンス判断ですが、月次でログと変更確認、四半期でグループ棚卸し、半期でロール設計見直し、というのが実務的な最小セットです。
まとめ
「フォルダで切るか、グループアドレスで渡すか」は二択ではありません。フォルダはスコープの軸、グループはプリンシパルの軸で、組み合わせて初めて機能します。役割分離を一文にすると、所属は組織グループ、アクセスはアクセスグループ、制約は強制グループと組織ポリシー、ワークロードはサービスアカウント、となります。
小さく始めるなら、Cloud Identity を有効化して組織ノードを作り、grp-gcp-* 系の横断管理グループを組織レベルで付与し、iam.allowedPolicyMemberDomains と鍵作成禁止を有効化するところまでが最初の 1〜2 週間です。そこから development / production の 2 フォルダを切り、チーム別アクセスグループをフォルダに付け、Terraform に載せる。本番の特権を PAM に移すのはその後で構いません。
以上、フォルダをスコープ、アクセスグループをプリンシパルとして分けて設計する Google Cloud の権限モデルを整理した、現場からお送りしました。