Skip to main content

Service Account Keys Should Be Managed By Google

More Info:​

Ensure that there are only GCP-managed service account keys for each service account.

Risk Level​

High

Address​

Security

Compliance Standards​

CIS

Triage and Remediation​

Remediation​

Using Console

Below are concise, console-based steps to remediate the finding “Service Account Keys Should Be Managed By Google” for GCP IAM.


1. Identify service accounts with user‑managed keys​

  1. In the Google Cloud Console, go to:
    IAM & Admin → Service Accounts
    (URL: https://console.cloud.google.com/iam-admin/serviceaccounts)
  2. In each project, look at the KEYS column:
    • If it shows User-managed keys, that account is in violation.

Repeat for all relevant projects (or use the project selector at the top).


2. Plan migration to Google‑managed credentials (no JSON keys)​

Before deleting keys, make sure each workload can use keyless / Google‑managed authentication instead:

a) Google Cloud resources (GCE, GKE, Cloud Run, Cloud Functions, etc.)​

  1. For each workload, decide which service account it should run as.
  2. Assign that service account to the resource:
    • Compute Engine VM:
      • Go to Compute Engine → VM instances.
      • Click the VM → Edit.
      • Under Service account, select the target service account.
      • Save.
    • GKE (Workloads):
      • Use Workload Identity (recommended):
        • Enable Workload Identity on the cluster (if not already).
        • Create a Kubernetes service account and bind it to the GCP service account.
        • (Console part is mostly under Kubernetes Engine → Clusters → Security; detailed configuration usually uses gcloud/kubectl.)
    • Cloud Run / Cloud Functions / Cloud Scheduler, etc.:
      • In each service’s Edit page, set the Runtime service account (or equivalent) to the correct service account.

When a Google Cloud resource runs as a service account, Google automatically provides and rotates a Google‑managed key (not visible or downloadable).


b) External workloads (on‑prem, other clouds, laptops/CI)​

For anything that used a JSON key file:

  1. Go to IAM & Admin → Workload Identity Federation.
    • URL: https://console.cloud.google.com/iam-admin/workload-identity-pools
  2. Create or use an existing Workload Identity Pool and Provider for your external environment (OIDC, AWS, Azure AD, etc.).
  3. On the Service account page:
    • Go to IAM & Admin → Service Accounts.
    • Click the target service account → Permissions (or Show Info Panel).
    • Grant access to the workload identity pool (role roles/iam.workloadIdentityUser to the pool or provider).
  4. Update the external workload to use Workload Identity Federation instead of a JSON key (you’ll point it to the OIDC / AWS / other identity and the pool/provider; the app then exchanges external identity tokens for short‑lived Google credentials).

3. Remove user‑managed keys​

Once workloads are confirmed working with Google‑managed or federated credentials:

  1. Go to IAM & Admin → Service Accounts.
  2. Click the affected service account.
  3. Go to the KEYS tab.
  4. Under User-managed keys:
    • For each key ID:
      • Click Delete (trash icon).
      • Confirm deletion.

Do this for all service accounts reported with user‑managed keys.


You can use an Org Policy to block new user‑managed keys:

  1. Go to: IAM & Admin → Organization Policies.
    (URL: https://console.cloud.google.com/iam-admin/orgpolicies)
  2. Find policy:
    Constraints on service account key creation: constraints/iam.disableServiceAccountKeyCreation
  3. Click it → Edit:
    • Set to Enforced to disable creation of new user‑managed keys.
  4. Save.

5. Verify remediation​

  1. Re‑run your security scanner / SCC / policy check.
  2. In IAM & Admin → Service Accounts, confirm:
    • KEYS column shows only Google-managed keys (or none).
    • No service accounts have remaining user‑managed keys.

This satisfies the requirement that “Service Account Keys Should Be Managed By Google.”

Using CLI

Below are concise, CLI‑based steps to (1) prevent new user‑managed keys and (2) remove existing ones.


1. Prevent creation of new user‑managed keys​

You do this via Organization/Folder/Project policies:

1.1. Disable creation of new keys​

# Set at org level (replace ORG_ID)
gcloud org-policies set-policy <<EOF
name: organizations/ORG_ID/policies/constraints/iam.disableServiceAccountKeyCreation
spec:
rules:
- enforce: true
EOF

To do it at the project level instead (replace PROJECT_ID):

gcloud org-policies set-policy <<EOF
name: projects/PROJECT_ID/policies/constraints/iam.disableServiceAccountKeyCreation
spec:
rules:
- enforce: true
EOF
# Org level
gcloud org-policies set-policy <<EOF
name: organizations/ORG_ID/policies/constraints/iam.disableServiceAccountExternalKeyUpload
spec:
rules:
- enforce: true
EOF

Or at project level:

gcloud org-policies set-policy <<EOF
name: projects/PROJECT_ID/policies/constraints/iam.disableServiceAccountExternalKeyUpload
spec:
rules:
- enforce: true
EOF

These enforce using Google‑managed keys (no user‑managed key creation or upload).


2. Identify existing user‑managed service account keys​

List all service accounts for a project:

PROJECT_ID=your-project-id

gcloud iam service-accounts list \
--project="${PROJECT_ID}" \
--format="value(email)"

For each service account, list its keys:

SA_EMAIL=service-account@${PROJECT_ID}.iam.gserviceaccount.com

gcloud iam service-accounts keys list \
--iam-account="${SA_EMAIL}" \
--format="table(name, keyType, validAfterTime, validBeforeTime)"

Look for keys with keyType = USER_MANAGED.


3. Remove existing user‑managed keys​

Delete each user‑managed key by its full key ID (the name field):

KEY_NAME=projects/${PROJECT_ID}/serviceAccounts/${SA_EMAIL}/keys/KEY_ID

gcloud iam service-accounts keys delete "${KEY_NAME##*/}" \
--iam-account="${SA_EMAIL}" \
--project="${PROJECT_ID}" \
--quiet

You can script this for all user‑managed keys in a project:

PROJECT_ID=your-project-id

for SA in $(gcloud iam service-accounts list --project="${PROJECT_ID}" --format="value(email)"); do
for KEY in $(gcloud iam service-accounts keys list \
--iam-account="${SA}" \
--project="${PROJECT_ID}" \
--format="value(name)" \
--filter="keyType=USER_MANAGED"); do
echo "Deleting key ${KEY} for ${SA}"
gcloud iam service-accounts keys delete "${KEY##*/}" \
--iam-account="${SA}" \
--project="${PROJECT_ID}" \
--quiet
done
done

4. Ensure workloads use Google‑managed credentials​

For each workload:

  • GCE: Use service account attached to VM (no key file).
  • GKE: Use Workload Identity (no key file).
  • Cloud Run / Cloud Functions / App Engine: Use default or custom service account directly (no key file).

Once workloads no longer rely on user‑managed keys, and the org policies above are enforced, service account keys will be fully Google‑managed.

Using Python

To have only Google‑managed keys for your service accounts, you must:

  1. Find all user‑managed keys.
  2. Delete them (after migrating workloads off JSON keys).
  3. Optionally enforce org policies to prevent new ones.

Below is how to do 1–2 with Python for GCP IAM.


1. Prerequisites​

pip install google-api-python-client google-auth

Make sure you authenticate with an identity that has:

  • roles/iam.serviceAccountAdmin or
  • roles/iam.serviceAccountKeyAdmin

For local dev:

gcloud auth application-default login

2. Python: List and delete user‑managed keys for a service account​

keyType meanings in IAM:

  • USER_MANAGED → JSON keys you create (should be removed).
  • SYSTEM_MANAGED → Google‑managed keys (do not delete these; they’re for things like Workload Identity, GCE, GKE, Cloud Run, etc.).
from googleapiclient import discovery
from google.auth import default

def delete_user_managed_keys(project_id: str, service_account_email: str, dry_run: bool = True):
"""
Deletes all USER_MANAGED keys for a specific service account.
Set dry_run=False to actually delete.
"""
credentials, _ = default()
service = discovery.build("iam", "v1", credentials=credentials)

sa_name = f"projects/{project_id}/serviceAccounts/{service_account_email}"

# List keys
request = service.projects().serviceAccounts().keys().list(
name=sa_name,
keyTypes=["USER_MANAGED"] # Only user-managed keys
)
response = request.execute()

keys = response.get("keys", [])
if not keys:
print(f"No USER_MANAGED keys found for {service_account_email}")
return

print(f"Found {len(keys)} USER_MANAGED key(s) for {service_account_email}:")
for key in keys:
key_name = key["name"]
print(f" - {key_name}")

if dry_run:
print("Dry run only: no keys deleted. Set dry_run=False to delete.")
return

# Delete keys
for key in keys:
key_name = key["name"]
print(f"Deleting key: {key_name}")
del_request = service.projects().serviceAccounts().keys().delete(name=key_name)
del_request.execute()

print("Deletion complete.")

if __name__ == "__main__":
# Example usage
PROJECT_ID = "my-project-id"
SERVICE_ACCOUNT_EMAIL = "my-sa@my-project-id.iam.gserviceaccount.com"

# First run as dry run to verify:
delete_user_managed_keys(PROJECT_ID, SERVICE_ACCOUNT_EMAIL, dry_run=True)

# When ready to actually delete:
# delete_user_managed_keys(PROJECT_ID, SERVICE_ACCOUNT_EMAIL, dry_run=False)

3. Python: Iterate over all service accounts in a project​

If you want to clean up all service accounts in a project:

from googleapiclient import discovery
from google.auth import default

def delete_all_user_managed_keys_in_project(project_id: str, dry_run: bool = True):
credentials, _ = default()
service = discovery.build("iam", "v1", credentials=credentials)

parent = f"projects/{project_id}"

# List all service accounts
request = service.projects().serviceAccounts().list(name=parent)
response = request.execute()

service_accounts = response.get("accounts", [])
if not service_accounts:
print(f"No service accounts found in project {project_id}")
return

print(f"Found {len(service_accounts)} service account(s) in {project_id}")

for sa in service_accounts:
email = sa["email"]
print(f"\nChecking service account: {email}")

sa_name = sa["name"]
key_request = service.projects().serviceAccounts().keys().list(
name=sa_name,
keyTypes=["USER_MANAGED"]
)
key_response = key_request.execute()
keys = key_response.get("keys", [])

if not keys:
print(" No USER_MANAGED keys.")
continue

print(f" Found {len(keys)} USER_MANAGED key(s):")
for key in keys:
key_name = key["name"]
print(f" - {key_name}")

if dry_run:
print(" Dry run only: no keys deleted. Set dry_run=False to delete.")
continue

for key in keys:
key_name = key["name"]
print(f" Deleting key: {key_name}")
del_request = service.projects().serviceAccounts().keys().delete(name=key_name)
del_request.execute()

print("\nCleanup complete.")

if __name__ == "__main__":
PROJECT_ID = "my-project-id"

# First dry run:
delete_all_user_managed_keys_in_project(PROJECT_ID, dry_run=True)

# Then actually delete:
# delete_all_user_managed_keys_in_project(PROJECT_ID, dry_run=False)

At org/folder/project level, set these org policies to True to block new user‑managed keys:

  • constraints/iam.disableServiceAccountKeyCreation
  • constraints/iam.disableServiceAccountKeyUpload

Example (project scope):

gcloud org-policies set-policy policy.yaml

policy.yaml example:

name: projects/PROJECT_NUMBER/policies/constraints/iam.disableServiceAccountKeyCreation
spec:
rules:
- enforce: true
---
name: projects/PROJECT_NUMBER/policies/constraints/iam.disableServiceAccountKeyUpload
spec:
rules:
- enforce: true

Use the Python scripts to remove existing USER_MANAGED keys, and use the org policies to ensure only Google‑managed keys are used going forward.

Using Terraform
resource "google_service_account" "app_sa" {
account_id = "APP_SERVICE_ACCOUNT_ID" # e.g. "my-app-sa"
display_name = "APP_SERVICE_ACCOUNT_NAME" # e.g. "My App Service Account"
project = "PROJECT_ID"
}

To have keys managed by Google (and avoid user‑managed keys), do not define any google_service_account_key resources for this account (or remove existing google_service_account_key resources from Terraform so they are destroyed; this will immediately invalidate those keys and break anything still using them).

terraform plan should show only google_service_account resources (no google_service_account_key creates), and for any removed keys it should show -/+ or - (destroy) actions for the google_service_account_key resources.

Additional Reading:​