Skip to main content

google.iam.admin.v1.CreateRole

Event Information

  • Event purpose & scope

    • google.iam.admin.v1.CreateRole is 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 of includedPermissions.
  • 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 like iam.roles.*, resourcemanager.*).
    • 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.

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.create to a tightly-controlled admin group
  • Backdoor Role for Service Account Impersonation

    • Custom role with iam.serviceAccounts.getAccessToken, iam.serviceAccounts.signJwt, iam.serviceAccounts.actAs created 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.CreateRole logs for SA-related permissions, alert on roles containing sensitive IAM permissions, and enforce SCP/org policy to block dangerous permission sets
  • Role Enabling Unrestricted Data Access (e.g., Wide Storage / BigQuery Read)

    • Custom role with storage.objects.list/get on * buckets or bigquery.tables.getData on 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

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 like resourcemanager.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.
  • 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.
  • 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.getData across 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.

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"
    • 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.

  • 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 permissions
      gcloud iam roles update ROLE_ID --organization=ORG_ID \
      --remove-permissions=iam.serviceAccounts.getAccessToken,iam.serviceAccounts.signJwt,iam.serviceAccounts.actAs

      # Review & clean bindings
      gcloud 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.CreateRole and on roles containing iam.serviceAccounts.* sensitive perms; implement org policy / SCP equivalent (for multi‑cloud) to disallow those permissions in custom roles except in a whitelisted project/folder.

  • 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 perms
      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[] | test("storage.objects.list|storage.objects.get|bigquery.tables.getData"))'

      # For a given role, list project-level bindings
      gcloud 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-bucket
        gcloud storage buckets add-iam-policy-binding gs://SENSITIVE_BUCKET \
        --member="group:analytics@org.com" \
        --role="roles/storage.objectViewer"

        # BigQuery: per-dataset
        bq update --dataset --description="Approved analytics access" \
        project:dataset
    • 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.

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 build
    from google.oauth2 import service_account

    SCOPES = ["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_PERMS
    if hit:
    flagged.append({
    "name": r["name"],
    "title": r.get("title"),
    "stage": r.get("stage"),
    "dangerous_permissions": list(hit)
    })
    return flagged

    if __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.update are only granted to a controlled admin group.
  • 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 build
    from google.oauth2 import service_account

    SA_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 roles

    def 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 projects

    def 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_PERMS
    for r in roles
    if 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 build
    from google.oauth2 import service_account

    SA_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 roles

    def 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 projects

    def 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_PERMS
    for r in roles
    if 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.