Skip to content

GCP_CREATE_SA_KEYS

Copy page

Summary

Forestall ACL Alias GCP_CREATE_SA_KEYS
GCP Alias Persistence & Impersonation
Affected Object Types Service Accounts
Exploitation Certainty Certain
Granting Roles roles/owner, roles/editor, roles/iam.serviceAccountKeyAdmin

Description

GCP_CREATE_SA_KEYS indicates that an identity can call iam.serviceAccounts.keys.create on a GCP Service Account. This is one of the most impactful persistence mechanisms in GCP: creating a service account key produces a long-lived JSON credential file that is valid until explicitly revoked, has no expiry by default, and can be used from anywhere on the internet — completely independent of GCP’s infrastructure.

Unlike short-lived tokens (which expire in 1 hour), a SA JSON key represents untethered persistence. An attacker who creates and exfiltrates a key retains access even after being detected and removed from IAM policy bindings, until the key itself is revoked.

Key abuse scenarios:

  • Create a new JSON key for a high-privilege SA and exfiltrate it for persistent access.
  • Backdoor a high-privilege SA with a key under attacker control before being removed.
  • Create keys on multiple SAs for redundant persistence.

Identification

gcloud CLI

# Find who has SA key creation rights at project level
PROJECT_ID="my-project"
gcloud projects get-iam-policy $PROJECT_ID --format=json | \
jq '.bindings[] | select(.role | test("owner|editor|serviceAccountKeyAdmin")) | {role: .role, members: .members}'
# List existing keys for all service accounts (look for 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 --format="table(name, validAfterTime, validBeforeTime)" 2>/dev/null)
if [ ! -z "$KEYS" ]; then
echo "=== $SA ==="
echo "$KEYS"
fi
done
# Check for recently created keys (last 7 days)
gcloud logging read \
'protoPayload.methodName="google.iam.admin.v1.CreateServiceAccountKey"' \
--project=$PROJECT_ID \
--freshness=7d \
--format="table(timestamp, protoPayload.authenticationInfo.principalEmail, protoPayload.resourceName)"

Exploitation

gcloud CLI

# Create a new key for a high-privilege service account
gcloud iam service-accounts keys create /tmp/stolen_key.json \
--iam-account=$SA_EMAIL
# Verify the key works
gcloud auth activate-service-account --key-file=/tmp/stolen_key.json
gcloud auth print-access-token
# After exfiltration, use the key from any machine
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/stolen_key.json
# Now all gcloud/API calls authenticate as the target SA
gcloud projects list

The JSON key works from any internet-connected machine and persists until explicitly revoked — even if the original attacker identity is removed from all IAM policies.

Mitigation

  1. Disable SA key creation with org policy: constraints/iam.disableServiceAccountKeyCreation.
  2. Use Workload Identity Federation instead of SA keys for non-GCP workloads.
  3. Audit and rotate existing user-managed keys — delete any key not tied to an active use case.
  4. Set key expiry policies using constraints/iam.serviceAccountKeyExpiryHours.
  5. Alert on key creation immediately.

Detection

Log Type Method Key Fields
Admin Activity CreateServiceAccountKey resource.type=service_account, methodName=google.iam.admin.v1.CreateServiceAccountKey
gcloud logging read \
'protoPayload.methodName="google.iam.admin.v1.CreateServiceAccountKey"' \
--project=$PROJECT_ID \
--format="table(timestamp, protoPayload.authenticationInfo.principalEmail, protoPayload.resourceName)"

Alert on:

  • Any CreateServiceAccountKey call — key creation should be rare and intentional.
  • Key creation on high-privilege SAs (Cloud Build SA, default compute SA, owner-bound SAs).
  • Key creation outside of known automation pipelines.

References

Was this page helpful?

Thank you for your feedback.