google.iam.admin.v1.CreateRole
Event Information
-
Event purpose & scope
google.iam.admin.v1.CreateRoleis emitted when a new custom IAM role is created in a GCP project or organization via the IAM Admin API.- It typically appears in Cloud Audit Logs (Admin Activity) and contains details such as
name,stage, and the list ofincludedPermissions.
-
Risk & governance implications
- Creating custom roles can expand or refine access beyond standard predefined roles, which directly impacts least-privilege posture.
- From a compliance viewpoint (e.g., ISO 27001, SOC 2, PCI DSS, HIPAA), this event is sensitive and should be monitored to detect introduction of overly permissive roles or unauthorized privilege escalation paths.
-
Practical actions & controls
- Continuously export and monitor these events (via Cloud Logging → Log Router → SCC / SIEM) and alert on:
- Roles created with broad permissions (e.g.,
*Admin,*Owner, or high-impact APIs likeiam.roles.*,resourcemanager.*).
- Roles created with broad permissions (e.g.,
- Enforce approval workflows and change management around custom-role creation (e.g., via policy-as-code with Terraform + code review, and Organization Policy constraints), and periodically review custom roles against least-privilege and regulatory requirements.
- Continuously export and monitor these events (via Cloud Logging → Log Router → SCC / SIEM) and alert on:
Examples
-
Creation of Over-Privileged Custom Role (e.g.,
roles/custom.orgAdmin)- Includes permissions like
resourcemanager.organizations.setIamPolicy,iam.serviceAccounts.actAs,iam.roles.update - Violates least privilege (ISO 27001 A.9 / SOC2 CC6) and enables lateral movement and privilege escalation across the org
- Mitigation: Require change control + approval for new roles, enable IAM Recommender, restrict
iam.roles.createto a tightly-controlled admin group
- Includes permissions like
-
Backdoor Role for Service Account Impersonation
- Custom role with
iam.serviceAccounts.getAccessToken,iam.serviceAccounts.signJwt,iam.serviceAccounts.actAscreated and bound to a low-profile identity - Enables silent persistence and data exfiltration via impersonated high‑priv SA; impacts PCI-DSS 7.1 / HIPAA access control
- Mitigation: Monitor
google.iam.admin.v1.CreateRolelogs for SA-related permissions, alert on roles containing sensitive IAM permissions, and enforce SCP/org policy to block dangerous permission sets
- Custom role with
-
Role Enabling Unrestricted Data Access (e.g., Wide Storage / BigQuery Read)
- Custom role with
storage.objects.list/geton*buckets orbigquery.tables.getDataon critical datasets created for a generic “analytics” group - Breaks data minimization and segregation (GDPR Art. 25 / 32; SOC2 CC6.6); enables bulk data exfiltration from regulated datasets
- Mitigation: Pre-approved role catalog, mandatory data owner approval for roles including data-read permissions, and continuous review of bindings to sensitive projects/folders/org-level resources
- Custom role with
Remediation
Using Console
-
Over-Privileged Custom Role (e.g.,
roles/custom.orgAdmin)- In GCP Console, go to IAM & Admin → Roles, filter for Custom and locate the role (e.g.,
roles/custom.orgAdmin); click the role → Permissions tab → remove high‑risk permissions likeresourcemanager.organizations.setIamPolicy,iam.roles.*,iam.serviceAccounts.actAs, then Save a reduced version or create a new, narrower role and migrate bindings before deleting the old one. - Go to IAM & Admin → IAM, filter by Role = the over-privileged custom role, and systematically remove bindings from identities that don’t require it; replace with least‑privilege predefined roles or approved custom roles, documenting approvals per change‑control requirements (ISO 27001 A.9 / SOC2 CC6).
- In IAM & Admin → IAM Recommender (or Recommendations), enable and review right‑sizing recommendations for roles, and in IAM & Admin → Roles restrict Role Administrator / Role Creator (e.g.,
roles/iam.roleAdmin, custom creators) to a tightly‑controlled admin group only.
- In GCP Console, go to IAM & Admin → Roles, filter for Custom and locate the role (e.g.,
-
Backdoor Role for Service Account Impersonation
- In IAM & Admin → Roles, search custom roles for permissions like
iam.serviceAccounts.getAccessToken,iam.serviceAccounts.signJwt,iam.serviceAccounts.actAs; open each flagged role and either remove these permissions or delete the role if not strictly justified, then replace with safer alternatives. - In IAM & Admin → IAM, filter by Role to find which identities are bound to these backdoor‑style roles; immediately remove bindings from low‑profile identities, rotate any impacted service account keys/tokens, and review Logs Explorer for
google.iam.credentials.*usage (to assess potential PCI‑DSS / HIPAA impact). - At the org level, use Organization Policies (e.g.,
constraints/iam.allowedPolicyMemberDomains,constraints/iam.disableServiceAccountKeyCreation) and, if using Google Cloud Organization Policy SCP‑like controls, define guardrails that disallow roles containing the combination of sensitive IAM permissions from being created or bound without central security approval.
- In IAM & Admin → Roles, search custom roles for permissions like
-
Role Enabling Unrestricted Data Access (Storage / BigQuery)
- In IAM & Admin → Roles, identify any “analytics” or wide data‑read custom roles containing
storage.objects.list,storage.objects.get,bigquery.tables.getDataacross broad resources; clone and trim these to dataset/bucket‑specific or column‑level (via BQ views) access, then phase out the wide role by updating all bindings. - For Storage: go to Cloud Storage → Buckets → [sensitive bucket] → Permissions, and remove the generic “analytics” group or wide custom role where present; for BigQuery: open BigQuery → [project] → [dataset] → Share and remove overly broad groups/roles, granting granular dataset/table access aligned with GDPR/SOC2 data minimization.
- Implement a pre‑approved role catalog by documenting allowed data‑access roles; then periodically use IAM & Admin → IAM → Download (export IAM policy) or Cloud Asset Inventory to list and review bindings granting data‑read permissions to sensitive projects/folders/org resources, and remove or narrow any that go beyond the catalog, with mandatory data‑owner sign‑off for any exceptions.
- In IAM & Admin → Roles, identify any “analytics” or wide data‑read custom roles containing
Using CLI
- Over-Privileged Custom Role (e.g.,
roles/custom.orgAdmin)- Identify & review risky custom roles (e.g., with
resourcemanager.organizations.setIamPolicy,iam.serviceAccounts.actAs,iam.roles.update):gcloud iam roles list --organization=ORG_ID --format="value(name)" \| xargs -I{} gcloud iam roles describe {} --organization=ORG_ID \--format="json(name,includedPermissions)" \| jq 'select(.includedPermissions[] | IN("resourcemanager.organizations.setIamPolicy","iam.serviceAccounts.actAs","iam.roles.update"))' - Replace with least‑privilege roles, remove dangerous permissions, and enforce change control:
- Update role:
gcloud iam roles update ROLE_ID --organization=ORG_ID --remove-permissions=PERM1,PERM2 - Lock down creation:
gcloud organizations add-iam-policy-binding ORG_ID \--member="group:controlled-admins@org.com" \--role="roles/iam.organizationRoleAdmin"
- Update role:
- Enable continuous tuning and monitoring:
- Turn on IAM Recommender for projects/org; require CAB approval for new org‑level roles; add org policy to restrict who can create/modify custom roles.
- Identify & review risky custom roles (e.g., with
- Backdoor Role for Service Account Impersonation
- Detect suspicious custom roles (SA impersonation/signing permissions):
gcloud iam roles list --organization=ORG_ID --format="value(name)" \| xargs -I{} gcloud iam roles describe {} --organization=ORG_ID --format="json(name,includedPermissions)" \| jq 'select(.includedPermissions[] | IN("iam.serviceAccounts.getAccessToken","iam.serviceAccounts.signJwt","iam.serviceAccounts.actAs"))'
- Remove or constrain backdoor roles and bindings:
# Strip SA-impersonation permissionsgcloud iam roles update ROLE_ID --organization=ORG_ID \--remove-permissions=iam.serviceAccounts.getAccessToken,iam.serviceAccounts.signJwt,iam.serviceAccounts.actAs# Review & clean bindingsgcloud organizations get-iam-policy ORG_ID --format=json \| jq '.bindings[] | select(.role=="organizations/ORG_ID/roles/ROLE_ID")'gcloud organizations remove-iam-policy-binding ORG_ID \--member="user:LOW_PROFILE@org.com" \--role="organizations/ORG_ID/roles/ROLE_ID"
- Prevent re‑creation:
- Add alerting on
google.iam.admin.v1.CreateRoleand on roles containingiam.serviceAccounts.*sensitive perms; implement org policy / SCP equivalent (for multi‑cloud) to disallow those permissions in custom roles except in a whitelisted project/folder.
- Add alerting on
- Detect suspicious custom roles (SA impersonation/signing permissions):
- Role Enabling Unrestricted Data Access (Storage / BigQuery)
- Discover overly broad data-read custom roles and where they’re bound:
# List custom roles with storage or BQ data-read permsgcloud iam roles list --organization=ORG_ID --format="value(name)" \| xargs -I{} gcloud iam roles describe {} --organization=ORG_ID --format="json(name,includedPermissions)" \| jq 'select(.includedPermissions[] | test("storage.objects.list|storage.objects.get|bigquery.tables.getData"))'# For a given role, list project-level bindingsgcloud projects list --format="value(projectId)" \| xargs -I{} gcloud projects get-iam-policy {} --format=json \| jq '.bindings[] | select(.role=="organizations/ORG_ID/roles/ANALYTICS_ROLE")'
- Replace with cataloged, pre‑approved least‑privilege analytics roles and require data owner approval for regulated datasets:
- Create constrained roles (per dataset/bucket):
gcloud iam roles update ANALYTICS_ROLE --organization=ORG_ID \--remove-permissions=storage.objects.list,storage.objects.get,bigquery.tables.getData
- Grant fine‑grained access at resource level instead:
# Storage: per-bucketgcloud storage buckets add-iam-policy-binding gs://SENSITIVE_BUCKET \--member="group:analytics@org.com" \--role="roles/storage.objectViewer"# BigQuery: per-datasetbq update --dataset --description="Approved analytics access" \project:dataset
- Create constrained roles (per dataset/bucket):
- Continuously review bindings to sensitive resources (PCI / HIPAA / GDPR datasets):
- Periodically export IAM:
gcloud asset search-all-iam-policies --scope=organizations/ORG_ID \--query='policy.bindings.role:"roles/bigquery.dataViewer" OR policy.bindings.role:"roles/storage.objectViewer"' \--format=json
- Compare against approved data owner list; remove unauthorized bindings and document in access review records for ISO 27001 / SOC 2 evidence.
- Periodically export IAM:
- Discover overly broad data-read custom roles and where they’re bound:
Using Python
-
Detect & flag over‑privileged custom roles (org-wide)
- Use Cloud Asset Inventory + IAM Admin API to list custom roles and highlight dangerous permissions (e.g.,
resourcemanager.organizations.setIamPolicy,iam.serviceAccounts.actAs,iam.roles.update, wide data‑read perms):
from googleapiclient.discovery import buildfrom google.oauth2 import service_accountSCOPES = ["https://www.googleapis.com/auth/cloud-platform"]SA_KEY = "service-account.json"ORG_ID = "123456789012"DANGEROUS_PERMS = {"resourcemanager.organizations.setIamPolicy","iam.serviceAccounts.actAs","iam.serviceAccounts.getAccessToken","iam.serviceAccounts.signJwt","iam.roles.update","iam.roles.create","storage.objects.list","storage.objects.get","bigquery.tables.getData",}def get_iam_service():creds = service_account.Credentials.from_service_account_file(SA_KEY, scopes=SCOPES)return build("iam", "v1", credentials=creds)def list_custom_roles(org_id):svc = get_iam_service()roles = []req = svc.organizations().roles().list(parent=f"organizations/{org_id}", view="FULL")while req is not None:resp = req.execute()roles.extend(resp.get("roles", []))req = svc.organizations().roles().list_next(req, resp)return [r for r in roles if r.get("name", "").startswith(f"organizations/{org_id}/roles/")]def find_dangerous_roles(org_id):roles = list_custom_roles(org_id)flagged = []for r in roles:perms = set(r.get("includedPermissions", []))hit = perms & DANGEROUS_PERMSif hit:flagged.append({"name": r["name"],"title": r.get("title"),"stage": r.get("stage"),"dangerous_permissions": list(hit)})return flaggedif __name__ == "__main__":risky = find_dangerous_roles(ORG_ID)for r in risky:print(r)- Use this output to drive change control reviews required by ISO 27001 A.9 / SOC2 CC6; deprecate / delete or reduce these roles and enforce that
iam.roles.create/iam.roles.updateare only granted to a controlled admin group.
- Use Cloud Asset Inventory + IAM Admin API to list custom roles and highlight dangerous permissions (e.g.,
-
Hunt for backdoor SA‑impersonation roles and bindings
- Extend scanning to detect roles containing SA‑impersonation / token permissions and enumerate who can use them:
from googleapiclient.discovery import buildfrom google.oauth2 import service_accountSA_KEY = "service-account.json"ORG_ID = "123456789012"SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]SA_IMPERSONATION_PERMS = {"iam.serviceAccounts.getAccessToken","iam.serviceAccounts.signJwt","iam.serviceAccounts.actAs",}def get_services():creds = service_account.Credentials.from_service_account_file(SA_KEY, scopes=SCOPES)return (build("iam", "v1", credentials=creds),build("cloudresourcemanager", "v3", credentials=creds),)def list_org_custom_roles(iam_svc, org_id):roles = []req = iam_svc.organizations().roles().list(parent=f"organizations/{org_id}", view="FULL")while req is not None:resp = req.execute()roles.extend(resp.get("roles", []))req = iam_svc.organizations().roles().list_next(req, resp)return rolesdef list_projects(crm_svc, org_id):projects = []req = crm_svc.projects().list(parent=f"organizations/{org_id}")while req is not None:resp = req.execute()projects.extend(resp.get("projects", []))req = crm_svc.projects().list_next(req, resp)return projectsdef get_policy(crm_svc, resource_name):req = crm_svc.projects().getIamPolicy(resource=resource_name,body={"options": {"requestedPolicyVersion": 3}},)return req.execute()if __name__ == "__main__":iam_svc, crm_svc = get_services()roles = list_org_custom_roles(iam_svc, ORG_ID)sa_roles = {r["name"]: set(r.get("includedPermissions", [])) & SA_IMPERSONATION_PERMSfor r in rolesif set(r.get("includedPermissions", [])) & SA_IMPERSONATION_PERMS}print("Backdoor‑like custom roles:")for name, perms in sa_roles.items():print(f"{name}: {perms}")print("\nBindings for these roles (project level):")projects = list_projects(crm_svc, ORG_ID)for p in projects:pid = p["projectId"]policy = get_policy(crm_svc, pid)for b in policy.get("bindings", []):if b["role"] in sa_roles:print(f"Project {pid} -> {b['role']} -> {b.get('members', [])}")- Use findings to: remove SA‑impersonation permissions from generic roles, rebind such roles only to tightly‑controlled break‑glass identities, and add org policies / SCP‑equivalent (e.g., Org Policy constraints + CI/CD policy checks) to prevent roles combining these permissions, satisfying PCI‑DSS 7.1 / HIPAA access‑control expectations.
-
Limit wide data‑read roles and enforce approvals
- Scan for any custom role with broad Storage / BigQuery read and track where they’re bound (supports GDPR Art.25/32 & SOC2 CC6.6 reviews):
from googleapiclient.discovery import buildfrom google.oauth2 import service_accountSA_KEY = "service-account.json"ORG_ID = "123456789012"SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]DATA_READ_PERMS = {"storage.objects.list","storage.objects.get","storage.objects.getIamPolicy","bigquery.tables.getData","bigquery.tables.get","bigquery.datasets.get",}def get_services():creds = service_account.Credentials.from_service_account_file(SA_KEY, scopes=SCOPES)return (build("iam", "v1", credentials=creds),build("cloudresourcemanager", "v3", credentials=creds),)def list_custom_roles(iam_svc, org_id):roles = []req = iam_svc.organizations().roles().list(parent=f"organizations/{org_id}", view="FULL")while req is not None:resp = req.execute()roles.extend(resp.get("roles", []))req = iam_svc.organizations().roles().list_next(req, resp)return rolesdef list_projects(crm_svc, org_id):projects = []req = crm_svc.projects().list(parent=f"organizations/{org_id}")while req is not None:resp = req.execute()projects.extend(resp.get("projects", []))req = crm_svc.projects().list_next(req, resp)return projectsdef get_policy(crm_svc, project_id):req = crm_svc.projects().getIamPolicy(resource=project_id,body={"options": {"requestedPolicyVersion": 3}},)return req.execute()if __name__ == "__main__":iam_svc, crm_svc = get_services()roles = list_custom_roles(iam_svc, ORG_ID)data_roles = {r["name"]: set(r.get("includedPermissions", [])) & DATA_READ_PERMSfor r in rolesif set(r.get("includedPermissions", [])) & DATA_READ_PERMS}print("Custom roles with wide data‑read permissions:")for name, perms in data_roles.items():print(f"{name}: {perms}")projects = list_projects(crm_svc, ORG_ID)print("\nBindings of these roles (project level):")for p in projects:pid = p["projectId"]policy = get_policy(crm_svc, pid)for b in policy.get("bindings", []):if b["role"] in data_roles:print(f"Project {pid} -> {b['role']} -> {b.get('members', [])}")- Feed these results into: (1) a pre‑approved catalog of analytics / read‑only roles, (2) mandatory data‑owner approval workflow for any role including
*getData/ object read on regulated datasets, and (3) periodic review/attestation of bindings on sensitive projects/folders to remove generic “analytics” groups and enforce least‑privilege.