Granting Google Cloud Access by Team — Access Groups and Folders as Two Axes

Tadashi Shigeoka · Fri, March 13, 2026

Introduction

Run Google Cloud long enough and the conversation always arrives at the same place: stop granting permissions to individuals. Chasing IAM policies by hand every time someone joins, moves, or leaves does not scale, and it makes access review impossible.

The question that usually follows is “should we split by folders, or hand roles to a group address?” That framing is the problem. These are not alternatives; they are different axes. Folders are the resource hierarchy that scopes permissions. Groups are the principal identifiers that IAM policies reference. So the answer is not “one or the other” but a combination: create dedicated access groups, and grant roles to those groups at the folder level.

This article works through the reasoning behind that two-axis design, group classification and naming, implementation, migration, and the traps worth knowing before you hit them.

Thinking in two axes

The resource hierarchy is a tree of organization, folders, projects, and resources. IAM allow policies and organization policies set at a higher level are inherited downward as a union. That is the scope axis.

Separately, IAM principals include Google accounts, service accounts, Google groups, and domains. Groups are referenced as group:GROUP_EMAIL_ADDRESS. That is the principal axis.

So the real question is never “do we use a group address” but “which group, managed for what purpose, do we put in IAM?” Granting the same access to 50 people should be one group binding plus 50 memberships, not 50 individual bindings. Staffing changes then resolve through membership alone, without touching IAM policy. That is what the group axis buys you.

flowchart TD
    HR[HR / External IdP] --> ORGGRP[Organizational groups]
    ORGGRP --> ACCGRP[Dedicated access groups]
    ORGGRP --> ENFGRP[Enforcement groups]
    ACCGRP --> ORGIAM[Org-level IAM]
    ACCGRP --> FOLDERIAM[Folder-level IAM]
    ACCGRP --> PROJIAM[Project-level IAM]

    ORGIAM --> ORG[Organization]
    FOLDERIAM --> FOLDER[Folder]
    PROJIAM --> PROJ[Project]
    PROJ --> RES[Service Resources]

    SA[Service accounts] --> RES
    SACTRL[Per-SA IAM / impersonation control] --> SA

    ORGPOL[Organization policies] --> ORG
    ORGPOL --> FOLDER
    ORGPOL --> PROJ

    ACCGRP --> LOG[Cloud Audit Logs]
    FOLDERIAM --> LOG
    LOG --> REVIEW[Policy Analyzer / Recommender]

Four kinds of groups

Google’s best practices for using Google groups classify groups by purpose and give each a different lifecycle.

  • Organizational groups: sourced from HR systems or an external IdP. They represent department, reporting line, geography. Provision them exclusively from the trusted source and forbid manual edits.
  • Access groups: created solely to bind Google Cloud roles. Never used for mail or collaboration.
  • Enforcement groups: used to apply restrictions like two-step verification or SAML profile assignment. Set whoCanLeaveGroup to NONE_CAN_LEAVE so members cannot opt out.
  • Collaboration groups: mailing lists and shared editing, driven by end users.

The single most valuable rule here: never grant roles directly to organizational groups. Nest them inside access groups instead. Membership does not express an access requirement. “Works in Engineering” and “may deploy to production Cloud Run” are different facts, and wiring the second to the first manufactures excess privilege structurally.

For the same reason, do not repurpose a mailing list like team-a@example.com for IAM. Collaboration groups have loose lifecycles, often allow self-service joins, and become a privilege escalation path.

Nesting rules matter too. Nesting organizational groups within each other is fine and encouraged, but access groups and enforcement groups should not be nested. It becomes impossible to trace who can reach what, mismatched membership policies produce unintended bypasses, and propagation gets slower.

Naming

A group’s purpose should be obvious from its name. Google’s recommended user groups propose a grp-gcp-* form for cross-cutting admin functions, but once team access is in scope, a type prefix scales better.

org.finance-all@example.com                # membership, HR-sourced
access.team-a-viewers@example.com          # for granting access
access.team-a-admins@example.com           # for granting access
access.team-a-prod-deployers@example.com   # production-only job function
enforcement.mfa-required@example.com       # policy enforcement
collab.team-a@example.com                  # mail and collaboration

Going one step further, put access groups on a secondary domain such as access.example.com and deliberately create no MX record for it. Cloud Identity lets you add a subdomain of an already-verified domain without a fresh ownership check, and omitting MX blocks inbound mail to IAM-only groups at the DNS layer.

Who can actually manage groups

A common stumbling block: you are an Owner on Google Cloud and still cannot create a group. That is by design, not a misconfiguration. The identity plane (Cloud Identity and Google Workspace) and the resource plane (Google Cloud IAM) have separate authorization systems. roles/owner grants access to resources under the organization, and a directory group is not a Google Cloud resource. The Cloud console’s group management page makes this explicit: alongside roles/resourcemanager.organizationViewer on the IAM side, it requires separate group permissions that are managed by Workspace rather than IAM. No amount of additional Google Cloud IAM roles will fix it.

There are three ways out, from least to most privilege.

  • Have yourself made an owner of the group. No admin role at all, and you can manage that group’s membership. The Groups API setup guide suggests exactly this when access to every group is unnecessary. It lines up with giving every access group an owner, so it is usually the first choice.
  • Get the Groups Admin role. This is a prebuilt Workspace admin role, not super admin. Use it when you genuinely need to create and manage groups across the domain yourself.
  • Get a custom admin role. If Groups Admin is still too broad, narrow it to just the Admin API group-create permission.

All three start with a request to a super admin, and that friction is intentional. If group management came bundled with Google Cloud Owner, an Owner could add external members to their own group and bypass domain restricted sharing (the escalation path described below). In practice, the workable split is that super admins create the access group shells while the owning team manages membership as group owners.

When you cannot get group permissions at all

Some organizations will not grant even that. Before giving up, there are two avenues worth trying.

The first is to fall back to a request-based workflow. If group creation and member additions become tickets to the Workspace team, the design above still holds. It is slow, not broken. With an SLA and an intake form (group name, purpose, owner, initial members), it keeps up with a handful of staffing changes a month.

The second is to change who you are asking. If an external IdP is in play, the groups really live there and the Workspace admin only sets up the sync once. The IdP team is often more receptive. Better still, Workforce Identity Federation skips Cloud Identity entirely and maps IdP groups straight into IAM via principalSet, so Cloud Identity group management permissions stop being a prerequisite. That is the most effective way through (subject to the 400-group and 40-character limits described later).

If nothing at all is available, reproduce the shape of a group in your 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
}

This is per-user granting, so it is not the recommended design, but it is decisively better than clicking through the console. Adding and removing people happens in one place in locals, review happens in the PR, plan surfaces drift, and the access review is just reading Git. You recover the operational and audit benefits of groups in code, even without groups. And when group permissions finally arrive, the locals list transfers directly into access group membership, so the migration stays cheap.

Be honest about what you lose. Binding counts grow and push you toward allow policy size limits, revoking a departing employee becomes “delete this user: from every policy” with a real risk of missing one, and none of it helps for services that can only be shared with a Google group.

Most importantly, treat this as a stopgap rather than a destination. The reason group permissions get withheld is usually organizational rather than technical: a hard line where IT owns Workspace admin and engineering owns Google Cloud. The lever is making the ask smaller. Instead of “give us Groups Admin,” propose that IT creates the shells for groups under a secondary domain like access.example.com while your team holds ownership. Owner designation involves no admin role whatsoever, so the risk to them is close to zero. Anchor the argument on offboarding: with per-user grants, revoking a departing employee requires sweeping every folder and project policy, leftovers are inevitable, and audits will flag it. That is IT’s accountability rather than an engineering convenience, which makes it the argument that actually lands.

How to cut folders

The other axis, folders, is not just a container for tidiness. It is a policy attachment point and an isolation boundary. The enterprise foundations blueprint places six folders directly under the organization (bootstrap, common, production, nonproduction, development, networking) and inherits allow policies and organization policies from the folder level down.

That structure assumes a large enterprise with a dedicated platform team. For a small or mid-sized organization, starting with just development and production, then adding common or networking when a real need appears, is more realistic. Building a deep folder hierarchy for a team of five is over-engineering. The landing zone guidance frames the decision this way: go region- or subsidiary-based if policy requirements genuinely diverge across them, team-based if product teams need strong autonomy, and environment-based otherwise. Most organizations land on environment-based, or a hybrid with environments on top and teams beneath.

For the record, Resource Manager quotas allow nesting folders up to 10 levels deep, with at most 300 direct child folders under one parent. You are unlikely to hit either limit, and if you are close, question the design.

Four tiers of grant level

Grant levelRecommendationUse forWatch out for
OrganizationLimitedNetwork, security, audit, and billing functions that genuinely own cross-org responsibilityInherited by every descendant, so the blast radius is enormous. Never extend to product teams
FolderThe defaultTeam access groups per business unit, product, or environmentBad folder design distorts both permissions and cost. Moving projects changes inheritance
ProjectDeltas onlyWorkload-specific job separation (deployer, data-reader)Binding count grows with project count. Watch for duplicated common grants
ResourceExceptions onlyTime-bound access, read on one bucket, tag-based carve-outsInvites policy sprawl. Do not make it the norm

In practice, a shared viewer group like access.team-a-viewers@example.com at the folder level, layered with a narrow access.team-a-prod-deployers@example.com on a specific project, works well. Create one access group per job function and do not reuse a group across multiple workloads.

Do not use basic roles

Once groups and folders are settled, roles come next. Basic roles (Owner, Editor, Viewer) have no place in production. Per Google’s overview of role recommendations, Editor alone includes more than 3,000 permissions and grants extensive access to a project. Attach that to a group and the excess hits every member at once. An over-privileged group is more dangerous than an over-privileged person.

Grant predefined roles at least privilege, and reach for custom roles only when the granularity genuinely does not fit. Create custom roles at the organization level if they will be reused, at the project level for local needs, and remember that maintenance stays your problem.

Beyond that, strong production access does not have to be standing. Privileged Access Manager (PAM) replaces it with time-bound, approval-gated elevation. An entitlement defines who may request, which role, for how long, whether approval is required, and whether a justification is needed. Specify eligible requesters as groups, not individuals. Note that PAM cannot use basic roles.

If you want to express temporary access in IAM rather than PAM, use IAM Conditions and add a conditional binding alongside your permanent group grants rather than disturbing them.

{
  "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')"
      }
    }
  ]
}

There is a trap here. If the same principal already holds the same role through an unconditional binding, the conditional one restricts nothing, because conditional grants do not override unconditional ones. Conditions also cannot be attached to basic roles or to allUsers / allAuthenticatedUsers.

Implementation

gcloud

# Create a folder for the team
gcloud resource-manager folders create \
  --display-name=team-a \
  --organization=123456789012
 
# Grant the team viewer group on the folder
gcloud resource-manager folders add-iam-policy-binding 456789012345 \
  --member="group:access.team-a-viewers@example.com" \
  --role="roles/viewer" \
  --condition=None
 
# Grant the team admin group on the folder
gcloud resource-manager folders add-iam-policy-binding 456789012345 \
  --member="group:access.team-a-admins@example.com" \
  --role="roles/resourcemanager.projectIamAdmin" \
  --condition=None
 
# Grant a narrow, project-specific group
gcloud projects add-iam-policy-binding my-prod-project \
  --member="group:access.team-a-prod-deployers@example.com" \
  --role="roles/run.developer" \
  --condition=None

The same structure gives teams autonomy without inviting shadow IT. At the organization node, grant developers only roles/resourcemanager.organizationViewer so they can see hierarchy metadata, then grant roles/resourcemanager.projectCreator on their own sandbox folder alone. Project creation freedom is now scoped to their folder.

Terraform

The Terraform best practices warn that google_*_iam_policy and google_*_iam_binding are authoritative: they will remove members not listed on the next apply. Default to the additive google_*_iam_member, and use authoritative resources only where your team fully owns that role or policy. Mixing binding and member for the same role is another way to cause an outage.

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"
}

You can manage groups themselves with google_cloud_identity_group and google_cloud_identity_group_membership, though keeping membership outside Terraform (in the IdP or HR system) is cleaner. What you must not do is hand a deployment pipeline the broad Groups Admin role. If the pipeline is compromised, an attacker creates an arbitrary access group, drops their own account in, and holds permanent top-level access to production. Instead, build a Google Workspace custom admin role carrying only the Admin API group-create permission and assign that to the deployment service account. When calling the create API, enable the WITH_INITIAL_OWNER flag so the group’s owner at creation is a designated human admin account rather than the pipeline. Automation gets to create; humans keep control of adding and removing members.

Organization policies

Organization policies prevent the configuration accidents IAM alone cannot. For human team access, consider at least these three.

  • iam.allowedPolicyMemberDomains: domain restricted sharing, keeping out-of-domain principals out of IAM policies.
  • iam.disableServiceAccountKeyCreation and iam.disableServiceAccountKeyUpload: forbid service account key creation and upload.
  • iam.automaticIamGrantsForDefaultServiceAccounts: stop the automatic Editor grant to default service accounts.
name: organizations/123456789012/policies/iam.disableServiceAccountKeyCreation
spec:
  rules:
    - enforce: true

Define these at the organization or a high folder, and override only at the minimum set of descendant nodes that truly need an exception.

Pitfalls

Privilege escalation through groups is the big one. Domain restricted sharing evaluates the domain of the group, not the domains of the group’s members. A project admin can therefore add an external member to a group they own, grant that group a role, and bypass the restriction entirely. The countermeasure is a Workspace-side restriction preventing group owners from adding external members. Note that for organizations created on or after May 3, 2024, the iam.allowedPolicyMemberDomains legacy managed constraint is enforced by default with your own domain as the sole allowed domain. Older organizations must enable it explicitly.

allUsers and allAuthenticatedUsers mean everyone on the internet and every Google-authenticated user respectively, which is public access. The legacy domain restricted sharing constraint does not cover them directly, so block them with per-service controls such as Cloud Storage public access prevention, or with custom organization policies.

Service accounts are not a substitute for teams. They do not represent people, so keep them out of organizational groups and separate from human groups. The best practices for using service accounts securely recommend managing access to a service account (impersonation especially) at the individual service account level rather than granting it broadly at the project or folder. Broad Token Creator grants are a lateral movement path.

Tag-based conditions carry their own escalation path. Even a combination as innocuous-looking as roles/resourcemanager.tagUser plus roles/viewer becomes escalation if the holder can later apply the very tag a conditional binding keys on. Separate the ability to apply a tag from the identities that benefit from it.

Propagation delay is easy to forget. Role grant propagation usually takes about two minutes but can sometimes take seven or more. Group changes are slower, and nested groups slower still. Granting a role to a newly created group under domain restricted sharing can return failedPrecondition and may require waiting up to 24 hours. The asymmetry (additions propagate faster than removals) directly shapes your migration order.

Group deletion is irreversible. Strip IAM roles at every level of the hierarchy, then wait at least seven days for the revocation to fully propagate before deleting the group itself.

Migration

Moving from direct per-user IAM to group-centric IAM changes your permission design and your identity lifecycle at the same time. Treat it as an access model redesign, not a find-and-replace.

  1. Inventory the current state. Pull IAM policies for the organization and major folders and projects with get-iam-policy, and classify direct user grants, existing group grants, and service account grants. gcloud asset search-all-iam-policies sweeps the whole organization and surfaces stray external-domain identities.
  2. Design the target model. Map alice@example.com + roles/viewer on folder X to access.team-a-viewers@example.com + roles/viewer on folder X.
  3. Sort out group types. Keep the existing team-a@... for collaboration and create a new access.* for IAM.
  4. Decide how organizational groups connect. Nest org.* into access groups, or require request-based direct joins.
  5. Define owners and approval rules. Every access group gets an owner, a required justification, an approver, a maximum duration, and a renewal procedure.
  6. Validate in a test folder. Folder moves and inheritance changes are high-impact, so reproduce the inheritance shape in a sandbox first.
  7. Check the blast radius with Policy Simulator before removing direct grants.
  8. Add group grants first, remove direct grants later. Additions propagate faster than removals, so add, verify, then remove.
  9. Handle service accounts on a separate track. Do not mix human access migration with impersonation and Token Creator cleanup.
  10. Keep a rollback plan. Save the policy JSON you captured and keep each change small.

When moving projects between folders, the principal doing the move needs roles/resourcemanager.projectMover on both the source and destination parent folders (plus edit rights on the project itself). Inherited policies change dynamically, so this is exactly where workloads silently lose permissions and stop.

If you sync from an external IdP, Google Cloud Directory Sync (GCDS) configuration deserves attention. Delete the <INDEPENDENT_GROUP_SYNC> option if it is present in your config file, and enable <ADD_VALID_GROUP_MEMBERS_ONLY>. The former processes groups independently of user sync state, so it tries to resolve memberships before the users themselves are provisioned and drops members. After changing the config, do not run a live sync straight away: run a simulation sync with cache clearing and confirm no warnings about forced group dissolution or user deletion.

Workforce Identity Federation lets you federate without syncing into Cloud Identity, but design around its limits. The google.groups attribute mapping strongly favors group names of 40 characters or less, and a user who belongs to more than 400 groups on the IdP side will produce a token that exceeds the size limit and fails sign-in outright. Use attribute conditions to filter, and put only access groups into the token.

Operations and audit

The real work starts after the migration.

  • Forbid direct grants to human users as a rule, and manage exceptions with expiry and approval.
  • Give every access group an owner. Require justification, approval, and an expiry to join.
  • Update organizational groups only from HR or the IdP, never by hand.
  • Explicitly mark and restrict access groups containing external members.
  • Review IAM Recommender monthly. The maximum observation period for role recommendations is 90 days, and project-level recommendations can be shortened to 30 or 60.
  • Use Policy Analyzer to check effective access. Group expansion traces all the way to the real users behind a group.
  • Keep IAM audit logs queryable for SetIamPolicy and friends, and centralize Workspace group change logs in Cloud Logging.

Review cadence is a governance call, but a workable minimum is monthly log and change review, quarterly group inventory, and semiannual role design review.

Wrapping up

“Folders or group addresses?” is a false choice. Folders are the scope axis, groups are the principal axis, and they only work together. The separation of concerns fits in one sentence: membership belongs in organizational groups, access in access groups, constraints in enforcement groups and organization policies, and workloads in service accounts.

To start small: enable Cloud Identity, create the organization node, grant the grp-gcp-* cross-cutting admin groups at the organization level, and turn on iam.allowedPolicyMemberDomains and service account key restrictions. That is week one or two. Then cut development and production folders, attach per-team access groups at the folder level, and move it all into Terraform. Shifting production privileges to PAM can wait until after that.

That’s all from separating Google Cloud’s permission model into folders as scope and access groups as principals, from the Gemba.