Skip to main content

resourcemanager.organizations.setIamPolicy

Event Information

  • What it is: resourcemanager.organizations.setIamPolicy is an Admin API call that replaces the IAM policy on an entire GCP organization resource (not incremental). It’s typically invoked via gcloud organizations set-iam-policy, REST API, or through tools that manage org-level IAM.

  • Security / GCPIAM impact: This event means someone or something has rewritten the org-level IAM policy, potentially adding/removing org-wide roles and members. It can:

    • Grant or revoke powerful roles (e.g., roles/owner, roles/resourcemanager.organizationAdmin) across all projects/folders.
    • Introduce risky principals (e.g., allUsers, external domains), affecting least-privilege and segregation-of-duties.
  • Compliance / practical actions:

    • Treat as high-sensitivity; log, alert, and review every occurrence for SOX/PCI-DSS/ISO 27001 evidence.
    • Compare before/after IAM policies (use Cloud Logging and version control) to detect privilege escalation or policy drift.
    • Restrict who can call this API (via resourcemanager.organizations.setIamPolicy permission) to a small break-glass/admin group and enforce approvals via change management.

Examples

  • Organization-level privilege escalation

    • An attacker sets an overly permissive policy (e.g., roles/owner or roles/resourcemanager.organizationAdmin) for their account or a compromised service account at the organization level, gaining full control over all projects and folders.
    • Violates least-privilege and separation-of-duties expectations in ISO 27001, SOC 2, and CIS GCP benchmarks.
  • Bypassing security & compliance controls

    • setIamPolicy is used to remove or weaken bindings for roles/orgPolicyPolicyAdmin, roles/securityAdmin, or roles/loggingAdmin, preventing security teams from enforcing org policies, SCC findings, and centralized logging.
    • Can break controls required by PCI DSS and HIPAA around centralized monitoring, auditability, and configuration hardening.
  • Persistence and data exfiltration path

    • A malicious actor adds a broad group (e.g., allAuthenticatedUsers or an external domain group) to roles like roles/resourcemanager.projectCreator or high-privilege custom roles at org level, enabling creation of shadow projects or access paths for data exfiltration.
    • Conflicts with identity governance and access control requirements in NIST 800-53 (AC-2, AC-6) and CIS GCP controls.

Remediation

Using Console

  • Containment & investigation (Org-level escalation)

    • In GCP Console, go to IAM & Admin → IAM, set the Scope selector (top bar) to your Organization.
    • Sort by Role and look for any principals with Owner, roles/resourcemanager.organizationAdmin, or other highly privileged custom roles.
    • For each suspicious principal:
      • Click the pencil icon → Remove high-privilege roles → Save.
      • If the account is compromised (user/service account): in IAM & Admin → Service Accounts or Admin Console (for users), disable it and rotate all related keys/secrets.
    • In IAM & Admin → Audit Logs and Cloud Logging, filter on protoPayload.methodName="SetIamPolicy" and resource.type="organization" to identify when/where the escalation occurred; preserve logs for compliance (ISO 27001 A.12, SOC 2 CC7, CIS GCP).
  • Restore and harden security/compliance controls (securityAdmin/orgPolicy/logging)

    • In IAM & Admin → IAM (scope: Organization), verify correct bindings for:
      • roles/orgPolicyPolicyAdmin (or tightly scoped policy admin)
      • roles/securityAdmin (or SCC/security team roles)
      • roles/loggingAdmin / roles/loggingConfigWriter (for centralized logs)
    • Re-add legitimate security/compliance groups (e.g., secops@, compliance@) to these roles at Org level: Grant access → Add principal → Select role → Save; ensure no unapproved principals retain these roles.
    • In Organization policies (IAM & Admin → Organization policies), review and re-enable critical policies (e.g., domain restriction, disable service account key creation); validate logging sinks in Logging → Log Router still send all ADMIN_READ / DATA_ACCESS logs to central SIEM for PCI DSS/HIPAA auditability.
  • Remove persistence paths & enforce least privilege (projectCreator/shadow projects)

    • In IAM & Admin → IAM (scope: Organization), search for principals such as allUsers, allAuthenticatedUsers, external domains, or broad groups on:
      • roles/resourcemanager.projectCreator
      • Any high-privilege custom roles or roles that can create projects/folders/service accounts.
    • For any broad or external principals: Edit principal → Remove these roles at org level → Save; replace with tightly scoped internal groups as needed (per NIST 800-53 AC-2/AC-6, CIS GCP).
    • In IAM & Admin → Folders and Manage Resources, list projects/folders recently created; for suspicious “shadow” projects, remove IAM bindings, disable APIs, and, if confirmed malicious, shut them down (select project → Shut down) after exporting required logs/evidence.

Using CLI

  • Immediate containment & rollback of org-level IAM escalation

    • Identify and revoke unauthorized org-level bindings (including roles/owner, roles/resourcemanager.organizationAdmin, broad groups) and re‑apply baseline access:
      ORG_ID="1234567890"

      # 1. Export current org policy
      gcloud organizations get-iam-policy $ORG_ID > org-iam-current.yaml

      # 2. (Offline) Edit org-iam-current.yaml: remove/adjust malicious bindings
      # Ensure only approved groups/service accounts keep org-level roles
      # e.g. roles/resourcemanager.organizationAdmin, roles/owner

      # 3. Apply corrected policy
      gcloud organizations set-iam-policy $ORG_ID org-iam-current.yaml
    • Enumerate and clean up shadow access paths (project creators, custom roles, external groups):
      # List all org IAM bindings for review
      gcloud organizations get-iam-policy $ORG_ID \
      --format="table(bindings.role, bindings.members)"

      # Remove overly broad project creator or custom-role grants
      gcloud organizations remove-iam-policy-binding $ORG_ID \
      --member="group:allAuthenticatedUsers" \
      --role="roles/resourcemanager.projectCreator"
  • Restore and lock down security/compliance control roles (Security Admin, Org Policy, Logging)

    • Re‑grant required roles only to tightly controlled security groups, and verify no weakened bindings:
      SEC_GROUP="group:security-admins@example.com"
      gcloud organizations add-iam-policy-binding $ORG_ID \
      --member="$SEC_GROUP" \
      --role="roles/orgpolicy.policyAdmin"

      gcloud organizations add-iam-policy-binding $ORG_ID \
      --member="$SEC_GROUP" \
      --role="roles/security.admin"

      gcloud organizations add-iam-policy-binding $ORG_ID \
      --member="$SEC_GROUP" \
      --role="roles/logging.admin"
    • Enforce constraints to prevent future org‑level privilege escalation and bypass of security controls (aligns with ISO 27001, SOC 2, PCI DSS, HIPAA, NIST 800‑53, CIS GCP):
      # Example: restrict who can create projects (blocks shadow projects)
      gcloud org-policies set-policy project_creation_policy.yaml

      # Example project_creation_policy.yaml
      # name: organizations/1234567890/policies/constraints/resourcemanager.projectCreator
      # spec:
      # rules:
      # - allow_all: false
      # - values:
      # allowedValues:
      # - user:admin@example.com
      # - group:project-creators@example.com
  • Detection, monitoring, and long‑term hardening

    • Review Cloud Audit Logs for SetIamPolicy on the organization and security‑sensitive roles, and enable high‑severity alerts:
      # Filter recent org-level SetIamPolicy calls
      gcloud logging read \
      'resource.type="organization" AND protoPayload.methodName="SetIamPolicy"' \
      --organization=$ORG_ID --limit=100 \
      --format="table(timestamp, protoPayload.authenticationInfo.principalEmail, protoPayload.serviceData.policyDelta.bindingDeltas)"
    • Implement continuous controls to meet CIS GCP, NIST AC‑2/AC‑6:
      • Require approval workflows and break‑glass processes for org‑level roles (via IAM Conditions, Cloud Functions / Cloud Workflows + security ticketing).
      • Regularly export and baseline‑compare org IAM and org policies with CI/CD or compliance tooling to detect unauthorized changes.

Using Python

  • Identify and contain org-level IAM abuse

    • Enumerate and review all org-level bindings, focusing on roles/owner, roles/resourcemanager.organizationAdmin, roles/resourcemanager.projectCreator, custom high-privilege roles, and security roles (roles/orgPolicyPolicyAdmin, roles/securityAdmin, roles/loggingAdmin), then immediately remove or narrow any non-approved principals and rotate credentials for compromised accounts.

    • Example Python (Cloud Resource Manager v1) to list and then clean risky bindings (ensure ORGANIZATION_ID and ALLOWED_PRINCIPALS reflect your policies and approval list):

      from googleapiclient import discovery
      from google.oauth2 import service_account

      ORG_ID = "123456789012" # your organization ID
      ALLOWED_PRINCIPALS = {
      "roles/owner": {"group:cloud-admins@example.com"},
      "roles/resourcemanager.organizationAdmin": {"group:org-admins@example.com"},
      "roles/resourcemanager.projectCreator": {"group:project-creators@example.com"},
      "roles/orgPolicyPolicyAdmin": {"group:secops@example.com"},
      "roles/securityAdmin": {"group:secops@example.com"},
      "roles/loggingAdmin": {"group:secops@example.com"},
      }

      SCOPES = ["https://www.googleapis.com/auth/cloud-platform"]
      creds = service_account.Credentials.from_service_account_file(
      "service-account.json", scopes=SCOPES
      )

      crm = discovery.build("cloudresourcemanager", "v1", credentials=creds)

      def get_org_policy():
      req = crm.organizations().getIamPolicy(
      resource=f"organizations/{ORG_ID}",
      body={"options": {"requestedPolicyVersion": 3}},
      )
      return req.execute()

      def set_org_policy(policy):
      # Required for conditional bindings
      if policy.get("version", 0) < 3:
      policy["version"] = 3
      req = crm.organizations().setIamPolicy(
      resource=f"organizations/{ORG_ID}", body={"policy": policy}
      )
      return req.execute()

      def is_high_risk_member(member: str) -> bool:
      if member in ("allUsers", "allAuthenticatedUsers"):
      return True
      # Add domain-level and external checks as per your governance
      if member.endswith("@gmail.com") or member.endswith("@yahoo.com"):
      return True
      # Example: block external domain
      if member.endswith("@external-partner.com"):
      return True
      return False

      def clean_org_bindings():
      policy = get_org_policy()
      bindings = policy.get("bindings", [])
      new_bindings = []

      for b in bindings:
      role = b["role"]
      members = set(b.get("members", []))

      # Enforce allow-list for specific sensitive roles
      if role in ALLOWED_PRINCIPALS:
      allowed = ALLOWED_PRINCIPALS[role]
      # Only keep members that are in the approved list
      filtered = {m for m in members if m in allowed}
      if filtered:
      b["members"] = sorted(filtered)
      new_bindings.append(b)
      # else drop the binding entirely
      continue

      # For all other roles, at least strip obviously risky principals
      filtered = {m for m in members if not is_high_risk_member(m)}
      if filtered:
      b["members"] = sorted(filtered)
      new_bindings.append(b)
      # if nothing left, we drop this binding

      policy["bindings"] = new_bindings
      result = set_org_policy(policy)
      print("Updated org IAM policy etag:", result.get("etag"))

      if __name__ == "__main__":
      clean_org_bindings()
  • Reinstate security/compliance guardrails and monitoring

    • Re-add or validate bindings so that only dedicated security/compliance groups hold roles/orgPolicyPolicyAdmin, roles/securityAdmin, roles/loggingAdmin, and roles/accessContextManager.policyAdmin, and ensure audit logs (Admin, Data Access, Policy Denied) are enabled org-wide; configure log sinks locked to a security project to meet PCI DSS / HIPAA centralized logging.

    • Example: Python to ensure security teams have required roles (idempotent “ensure-binding” pattern):

      SEC_BINDINGS = {
      "roles/orgPolicyPolicyAdmin": {"group:secops@example.com"},
      "roles/securityAdmin": {"group:secops@example.com"},
      "roles/loggingAdmin": {"group:secops@example.com"},
      }

      def ensure_security_bindings():
      policy = get_org_policy()
      bindings = policy.get("bindings", [])
      role_to_binding = {b["role"]: b for b in bindings}

      changed = False
      for role, must_have in SEC_BINDINGS.items():
      if role not in role_to_binding:
      bindings.append({"role": role, "members": sorted(must_have)})
      changed = True
      else:
      b = role_to_binding[role]
      members = set(b.get("members", []))
      if not must_have.issubset(members):
      members.update(must_have)
      b["members"] = sorted(members)
      changed = True

      if changed:
      policy["bindings"] = bindings
      set_org_policy(policy)
      print("Security bindings enforced")
      else:
      print("Security bindings already compliant")

      if __name__ == "__main__":
      ensure_security_bindings()
  • Prevent persistence / re-escalation and align with least-privilege controls

    • Remove broad identities like allAuthenticatedUsers, external domains, or unapproved groups from org-level high-privilege roles and especially from roles/resourcemanager.projectCreator; replace them with tightly controlled groups, enable Org Policy constraints (constraints/iam.allowedPolicyMemberDomains, constraints/iam.disableServiceAccountKeyCreation, constraints/compute.disableSerialPortAccess), and require approvals for org-level IAM changes (change management to align with ISO 27001, SOC 2, NIST 800-53 AC-2/AC-6).

    • Example: Python snippet to specifically harden roles/resourcemanager.projectCreator and strip public/external members to reduce “shadow project” risk:

      SENSITIVE_ROLE = "roles/resourcemanager.projectCreator"

      def harden_project_creator():
      policy = get_org_policy()
      bindings = policy.get("bindings", [])
      for b in bindings:
      if b["role"] != SENSITIVE_ROLE:
      continue
      members = set(b.get("members", []))
      safe_members = {
      m for m in members
      if not is_high_risk_member(m)
      and m.startswith("group:project-creators@yourcorp.com")
      }
      if safe_members:
      b["members"] = sorted(safe_members)
      else:
      # If nothing safe remains, remove binding entirely
      bindings.remove(b)
      policy["bindings"] = bindings
      set_org_policy(policy)
      print("Hardened project creator role")

      if __name__ == "__main__":
      harden_project_creator()