Summary
| Forestall ACL Alias | GCP_HAS_ROLE |
| GCP Alias | Entity Relation (Structural) |
| Affected Object Types | Roles |
| Exploitation Certainty | Certain |
Description
GCP_HAS_ROLE is a structural edge representing that a GCP identity (user, group, or service account) holds an IAM role binding. It connects principals to their role assignments in the graph. A role binding is a triple of (principal, role, resource). GCP_HAS_ROLE captures the principal → role binding relationship.
Identification
gcloud CLI
# List all roles granted to a specific user at project levelPROJECT_ID="my-project"gcloud projects get-iam-policy $PROJECT_ID --format=json | \ jq --arg user "user:$USER_EMAIL" '.bindings[] | select(.members[] | contains($user)) | .role'
# List all roles granted to a specific service accountgcloud projects get-iam-policy $PROJECT_ID --format=json | \ jq --arg sa "serviceAccount:$SA_EMAIL" '.bindings[] | select(.members[] | contains($sa)) | .role'
# List all direct bindings for a principal across org, folder, and project scopeORG_ID=$(gcloud organizations list --format="value(name)" | head -1)echo "=== Org level ===" && gcloud organizations get-iam-policy $ORG_ID --format=json | \ jq --arg user "user:$USER_EMAIL" '[.bindings[] | select(.members[] | contains($user)) | .role]'echo "=== Project level ===" && gcloud projects get-iam-policy $PROJECT_ID --format=json | \ jq --arg user "user:$USER_EMAIL" '[.bindings[] | select(.members[] | contains($user)) | .role]'GCP Console
- Open GCP Console → IAM & Admin → IAM.
- Each row in the IAM table represents a
GCP_HAS_ROLEedge between the listed principal and the shown role. - Use the Principal filter to view all roles held by a specific identity.
- The Inheritance tag indicates bindings from parent scope resources.
Exploitation
GCP_HAS_ROLE is the starting point for all privilege analysis. Every attack path edge in the system is derived from role bindings surfaced by this edge.
Key risk patterns:
- Over-assignment: Identities accumulating many role bindings over time, especially after project migrations or team changes.
- Stale bindings: Former employees or deprecated service accounts retaining
GCP_HAS_ROLEedges to privileged roles. - Cross-project service accounts: A service account from Project A holding roles in Project B creates cross-project attack paths.
- Conditional bindings: GCP IAM supports conditions on bindings (e.g., time-based access).
GCP_HAS_ROLEedges may reflect conditional bindings that are inactive outside their condition window.
Mitigation
- Enforce least privilege — remove role bindings that are no longer needed. Conduct periodic access reviews, especially after team changes or project migrations.
- Eliminate stale bindings — disable or delete service accounts and user accounts that are no longer active before removing their role bindings.
- Scope bindings narrowly — prefer project-level bindings over folder- or org-level to limit blast radius.
- Review cross-project service account assignments — a service account from Project A holding roles in Project B creates cross-project attack paths; document and minimize these.
- Audit conditional bindings — verify that time-based or condition-based bindings are still appropriate and tightly scoped.
Detection
| Log Type | Method | Key Fields |
|---|---|---|
| Admin Activity | SetIamPolicy |
New role binding added to any principal |
gcloud logging read \ 'protoPayload.methodName="SetIamPolicy"' \ --project=$PROJECT_ID \ --format="table(timestamp, protoPayload.authenticationInfo.principalEmail, protoPayload.serviceData.policyDelta.bindingDeltas)"Alert on:
- New privileged role bindings (
roles/owner,roles/editor, admin roles) added to any identity. - Role bindings added at org or folder scope — these carry the highest blast radius.
- Bindings added to external identities (Gmail accounts, federated identities outside the org domain).