> For the complete documentation index, see [llms.txt](https://docs.forestall.io/forestall/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/forestall/edges/gcp/gcp_runs_as.md).

# GCP\_RUNS\_AS

## Summary

|                            |                                     |
| -------------------------- | ----------------------------------- |
| **Forestall ACL Alias**    | GCP\_RUNS\_AS                       |
| **GCP Alias**              | Compute Execution & Pipeline Pivots |
| **Affected Object Types**  | Service Accounts                    |
| **Exploitation Certainty** | Certain                             |

## Description

`GCP_RUNS_AS` represents that a **GCE virtual machine runs as an attached service account**. It points from the VM to the service account bound to it at creation time:

```
Virtual Machine ── GCP_RUNS_AS ──► Service Account
```

Attaching a service account to a VM makes that identity available to every process on the machine. The guest does not hold a key file; instead the **metadata server** at `metadata.google.internal` issues short-lived OAuth access tokens on request, with no authentication beyond being on the instance:

```
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
```

This is by design and is how workloads authenticate to Google APIs without managing credentials. Its consequence for the graph is that **code execution on a VM is equivalent to holding the attached service account's IAM privileges**. `GCP_RUNS_AS` is the edge that makes that equivalence explicit, and it is what turns a shell into a privilege escalation.

**Why the edge matters for attack paths**

`GCP_RUNS_AS` is the pivot that converts machine access into identity. It completes several compound paths that are incomplete without it:

| Preceding edge                                                                                                                                                            | Combined effect                                                                                               |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| [`GCP_SSH_TO`](https://docs.forestall.io/fsprotect/edges/gcp/gcp_ssh_to)                                                                                                  | OS Login session yields the attached SA's token                                                               |
| [`GCP_INJECT_SSH_KEY`](https://docs.forestall.io/fsprotect/edges/gcp/gcp_inject_ssh_key)                                                                                  | Planted key yields a shell, then the attached SA's token                                                      |
| [`GCP_CREATE_COMPUTE`](https://docs.forestall.io/fsprotect/edges/gcp/gcp_create_compute) + [`GCP_ACT_AS_SA`](https://docs.forestall.io/fsprotect/edges/gcp/gcp_act_as_sa) | Attacker creates a *new* VM with a chosen SA attached, establishing this edge deliberately to harvest that SA |

The last is the important one. `GCP_ACT_AS_SA` on its own only grants the right to attach a service account to infrastructure — it is not directly exploitable. It becomes an escalation only when combined with the ability to create or modify a compute resource, because the new VM's `GCP_RUNS_AS` edge is what actually surrenders the token. An attacker with both can attach any service account they can act as to a VM they control, and read its token out of the metadata server.

The **default Compute Engine service account** deserves particular attention. Unless explicitly overridden, GCP attaches it to new VMs and grants it `roles/editor` on the project if `iam.automaticIamGrantsForDefaultServiceAccounts` is not enforced. In that case, any VM running as it therefore has an edge to a project-editor identity, which itself carries [`GCP_CREATE_SA_KEYS`](https://docs.forestall.io/fsprotect/edges/gcp/gcp_create_sa_keys) and `GCP_ACT_AS_SA`. A single low-value VM can be the entry point to full project control through this chain.

Access scopes are a legacy secondary restriction on what a VM's token may call. They are not a security boundary to rely on: `https://www.googleapis.com/auth/cloud-platform` is the common default and permits every API the service account's IAM roles allow.

## Identification

### gcloud CLI

```bash
PROJECT_ID="my-project"
VM_NAME="example-vm"
VM_ZONE="example-zone"

# Map every VM to the service account it runs as, with scopes
gcloud compute instances list --project=$PROJECT_ID \
  --format="table(name, zone, status, serviceAccounts[0].email, serviceAccounts[0].scopes.list())"

# Detail for a single instance
gcloud compute instances describe VM_NAME --zone=ZONE --project=$PROJECT_ID \
  --format="value(serviceAccounts)"

# Find VMs running as the default Compute Engine SA (usually project editor)
PROJECT_NUM=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
gcloud compute instances list --project=$PROJECT_ID \
  --filter="serviceAccounts.email=${PROJECT_NUM}-compute@developer.gserviceaccount.com" \
  --format="table(name, zone, status)"

# Resolve what an attached SA can actually do — the real blast radius of the VM
SA_EMAIL="workload-sa@my-project.iam.gserviceaccount.com"
gcloud projects get-iam-policy $PROJECT_ID --format=json | \
  jq --arg sa "serviceAccount:$SA_EMAIL" \
     '.bindings[] | select(.members[]? == $sa) | .role'

# Across the whole org, find every binding held by that SA
gcloud asset search-all-iam-policies \
  --scope=organizations/$(gcloud organizations list --format="value(name)" | head -1 | cut -d/ -f2) \
  --query="policy:$SA_EMAIL" \
  --format="table(resource, policy.bindings.role)"
```

A VM is only as sensitive as the roles held by the service account it runs as and service accounts parented in one project frequently holds roles elsewhere.

### GCP Console

1. Open **GCP Console** → **Compute Engine** → **VM Instances** → select an instance.
2. The **Service accounts** section on the instance detail page names the attached identity and its access scopes.
3. Follow that identity to **IAM & Admin** → **Service Accounts** → select the SA → **Permissions**, then check **IAM & Admin** → **IAM** for the roles it holds on the project.
4. Compare against **IAM & Admin** → **IAM** at folder and organization scope — an attached SA may hold bindings well above the VM's own project.

## Exploitation

`GCP_RUNS_AS` has no exploit of its own — the edge simply records an attachment, not a permission grant — but reaching it requires code execution on the VM, and cashing it in is immediate and unconditional. Its significance is what that execution is worth.

Given any shell on the instance, the attached identity is available immediately and without a credential:

```bash
# Which identity does this VM run as?
curl -s -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email"

# What may its token be used for?
curl -s -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/scopes"

# Mint an access token
curl -s -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
```

The token is a bearer credential usable from anywhere until it expires, so it can be exfiltrated and replayed off the instance:

```bash
TOKEN="ya29...."

# Enumerate reachable projects
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://cloudresourcemanager.googleapis.com/v1/projects"

# Enumerate secrets the SA can read
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://secretmanager.googleapis.com/v1/projects/my-project/secrets"
```

Where the SA is a project editor — as the default Compute Engine service account is — the path continues into durable persistence, since editor carries the ability to create service account keys:

```bash
gcloud auth activate-service-account --key-file=<(echo "$STOLEN_KEY_JSON")
```

Two properties make this pivot valuable to an attacker beyond the privileges themselves. Requests made with the token are attributed to the **service account**, not to the attacker's own principal, so activity blends into normal workload traffic. And the credential is issued on demand by the metadata server, so there is no key file on disk to find and no rotation event to observe.

## Mitigation

1. **Never leave the default Compute Engine service account attached.** Sometimes it holds `roles/editor` on the project by default. Create a dedicated, minimally privileged service account per workload and attach that instead.
2. **Grant the attached SA only the roles its workload needs**, at the narrowest scope that works. The VM's blast radius is exactly this SA's permission set — treat every role granted to it as granted to anyone who gets code execution on the machine.
3. **Do not attach an SA at all** to VMs that make no Google API calls. An instance with no attached identity has no token to steal.
4. **Block metadata server access** from workloads that do not need it, using network policy or host-level egress controls. This is the only control that severs the edge rather than limiting its value.
5. **Enforce the `iam.automaticIamGrantsForDefaultServiceAccounts` organization policy constraint** so newly created default service accounts do not automatically receive `roles/editor`.
6. **Restrict `roles/iam.serviceAccountUser`** (`GCP_ACT_AS_SA`) so attackers cannot choose which identity a new VM runs as. Pay particular attention to principals that hold it *together with* compute-create permissions — that combination lets them create this edge at will.
7. **Audit attachments of privileged service accounts to compute resources** on a schedule, and alert when an SA with folder- or org-scoped roles is attached to a VM.

## Detection

| Log Type       | Method                                   | Key Fields                                                                                |
| -------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------- |
| Admin Activity | `v1.compute.instances.insert`            | `serviceAccounts[].email` on the new instance — the edge being created                    |
| Admin Activity | `v1.compute.instances.setServiceAccount` | Attached identity changed on an existing VM                                               |
| Admin Activity | `SetIamPolicy`                           | New roles granted to an SA that is attached to VMs                                        |
| Data Access    | Token use by SA                          | `authenticationInfo.principalEmail` is the SA; compare `callerIp` against the VM's egress |

```bash
PROJECT_ID="my-project"

# VMs created or reconfigured with an attached service account
gcloud logging read \
  'log_id("cloudaudit.googleapis.com/activity")
   AND protoPayload.serviceName="compute.googleapis.com"
   AND protoPayload.methodName=~"v1\.compute\.instances\.(insert|setServiceAccount)"' \
  --project="$PROJECT_ID" \
  --freshness=400d \
  --limit=1000 \
  --order=desc \
  --format=json | jq '.[] | {
    time: .timestamp,
    actor: .protoPayload.authenticationInfo.principalEmail,
    method: .protoPayload.methodName,
    resource: .protoPayload.resourceName,
    attachedSA: (.protoPayload.request.serviceAccounts // [])
  }'

# Activity performed by an attached SA, to baseline its normal caller IPs
SA_EMAIL="workload-sa@my-project.iam.gserviceaccount.com"
gcloud logging read \
  "protoPayload.authenticationInfo.principalEmail=\"$SA_EMAIL\"" \
  --project=$PROJECT_ID \
  --format="table(timestamp, protoPayload.methodName, protoPayload.requestMetadata.callerIp)"
```

Alert on:

* VMs created with a service account that holds folder- or organization-scoped roles.
* `setServiceAccount` on an existing VM — reattaching a different identity is uncommon in steady-state operations and is a direct attempt to acquire this edge.
* Any VM created with the default Compute Engine service account attached, given its default `roles/editor` grant.
* Activity attributed to a VM-attached service account originating from an IP outside that VM's egress range, which indicates the token was exfiltrated rather than used in place.
* An attached service account calling APIs unrelated to its workload's normal behaviour, particularly `iam.serviceAccounts.keys.create` or Secret Manager reads it has never made before.
* The same principal holding `roles/iam.serviceAccountUser` and compute-create permissions, which allows this edge to be established on demand.

## References

* <https://cloud.google.com/compute/docs/access/service-accounts>
* <https://cloud.google.com/compute/docs/metadata/overview>
* <https://cloud.google.com/iam/docs/service-account-overview>
* <https://cloud.google.com/compute/docs/access/create-enable-service-accounts-for-instances>
* <https://cloud.google.com/iam/docs/best-practices-service-accounts>
