Prefer Using Dedicated GCP Service Accounts And Workload
More Info:
Enable Workload Identity so pods authenticate to Google Cloud as dedicated service accounts rather than sharing node credentials. This provides fine-grained, per-workload access control.
Risk Level
High
Address
Security
Compliance Standards
- CIS GKE
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Confirm whether Workload Identity is enabled at the cluster level
- Run on any machine with
gcloudaccess:gcloud container clusters describe CLUSTER_NAME \--location LOCATION \--project PROJECT_ID \--format json | jq '.workloadIdentityConfig' - If the output is
nullor empty, Workload Identity is not enabled and should be considered for activation.
- Run on any machine with
-
If not enabled, plan and enable Workload Identity for the cluster
- Decide the workload pool name (typically
PROJECT_ID.svc.id.goog) and assess impact on existing workloads and tooling (e.g., anything relying on node service account credentials). - Enable Workload Identity:
gcloud container clusters update CLUSTER_NAME \--location LOCATION \--project PROJECT_ID \--workload-pool PROJECT_ID.svc.id.goog
- Note: this changes the control-plane configuration; it does not retroactively change existing node pools.
- Decide the workload pool name (typically
-
Review and update existing node pools to use GKE metadata server
- List node pools:
gcloud container node-pools list \--cluster CLUSTER_NAME \--location LOCATION \--project PROJECT_ID
- For each node pool, inspect its workload metadata setting:
gcloud container node-pools describe NODE_POOL_NAME \--cluster CLUSTER_NAME \--location LOCATION \--project PROJECT_ID \--format 'value(config.workloadMetadataConfig.mode)'
- For node pools not set to
GKE_METADATA, schedule maintenance (this may recreate nodes) and update:gcloud container node-pools update NODE_POOL_NAME \--cluster CLUSTER_NAME \--location LOCATION \--project PROJECT_ID \--workload-metadata=GKE_METADATA - Alternatively, create new node pools with
--workload-metadata=GKE_METADATAand migrate workloads before draining/deleting old pools.
- List node pools:
-
Identify workloads that currently depend on node service account credentials
- On any machine with
kubectlaccess, look for pods not using projected service account tokens or that are known to call Google Cloud APIs:kubectl get pods -A -o wide - Review deployment/statefulset manifests for these workloads to see how they obtain credentials (environment variables, mounted keys, or default ADC from node).
- Document which namespaces and service accounts will need Workload Identity mappings.
- On any machine with
-
Plan and apply per-workload migrations to Workload Identity
- For each identified workload:
- Choose/create a dedicated Google service account.
- Bind the Kubernetes service account in the relevant namespace to that Google service account using IAM policy binding (done via
gcloudor IaC, outside Kubernetes). - Update manifests so pods run under the intended Kubernetes service account (not
default) and do not rely on node credentials or legacy key files.
- Use the guidance in the Workload Identity documentation to complete these mappings and test that each workload still has the required access.
- For each identified workload:
-
Verify configuration after changes
- Re-run the cluster-level check:
Confirm it shows the expectedgcloud container clusters describe CLUSTER_NAME \--location LOCATION \--project PROJECT_ID \--format json | jq '.workloadIdentityConfig'
workloadPool(for example,PROJECT_ID.svc.id.goog). - For each node pool, confirm
GKE_METADATAmode:gcloud container node-pools describe NODE_POOL_NAME \--cluster CLUSTER_NAME \--location LOCATION \--project PROJECT_ID \--format 'value(config.workloadMetadataConfig.mode)' - Optionally, exec into a migrated pod and validate that Google Cloud API calls succeed using the intended service account (per your application’s logging or diagnostic commands).
- Re-run the cluster-level check:
Using kubectl
kubectl cannot enable Workload Identity or change node pool metadata, because this finding is controlled entirely through the GKE/Google Cloud control plane (gcloud, console, or IaC). Use the cloud provider configuration steps described in the Manual Steps section to remediate this finding.
Automation
#!/usr/bin/env bash
#
# Report GKE Workload Identity usage at cluster and workload level.
#
# Requirements:
# - gcloud configured with access to the project
# - kubectl configured to talk to the target cluster
# - jq installed
#
# Usage:
# PROJECT_ID="my-project" CLUSTER_NAME="my-cluster" LOCATION="us-central1" ./report-workload-identity.sh
set -euo pipefail
PROJECT_ID="${PROJECT_ID:?Set PROJECT_ID}"
CLUSTER_NAME="${CLUSTER_NAME:?Set CLUSTER_NAME}"
LOCATION="${LOCATION:?Set LOCATION}"
echo "=== Cluster-level Workload Identity configuration ==="
gcloud container clusters describe "$CLUSTER_NAME" \
--location "$LOCATION" \
--project "$PROJECT_ID" \
--format="json(workloadIdentityConfig,workloadIdentityConfig.workloadPool,workloadIdentityConfig.identityNamespace)" \
| jq .
echo
echo "Interpretation:"
echo "- If workloadIdentityConfig is null or {} => Workload Identity NOT enabled at cluster level (non-compliant with CIS 5.2.2)."
echo "- If workloadIdentityConfig.workloadPool ends with '.svc.id.goog' => Workload Identity ENABLED."
echo
echo "=== Node pool metadata configuration (Workload Identity plumbing) ==="
gcloud container node-pools list \
--cluster "$CLUSTER_NAME" \
--location "$LOCATION" \
--project "$PROJECT_ID" \
--format="table(name,config.workloadMetadataConfig.mode)"
echo
echo "Interpretation:"
echo "- For each node pool, config.workloadMetadataConfig.mode should be 'GKE_METADATA'."
echo "- Any node pool with mode 'GCE_METADATA' or empty => nodes still exposing VM metadata, workloads not using Workload Identity on those nodes."
echo
echo "=== Namespaces and service accounts annotated for Workload Identity ==="
echo "Listing service accounts with potential Workload Identity annotations..."
echo
# Check namespace-level annotation (rare, but sometimes used for policy)
echo "Namespace-level annotations related to Workload Identity:"
kubectl get ns -o json \
| jq -r '.items[]
| .metadata
| {name: .name, annotations: (.annotations // {})
| with_entries(select(.key | test("iam.gke.io|workloadIdentity|gsa"))) }
| select(.annotations != {})
| "NAMESPACE: \(.name)\n ANNOTATIONS: \(.annotations)\n"' || true
echo "ServiceAccount-level annotations (recommended for Workload Identity):"
kubectl get sa --all-namespaces -o json \
| jq -r '.items[]
| .metadata as $m
| ($m.annotations // {}) as $a
| select(($a | keys[]? | test("iam.gke.io/gcp-service-account")))?
| "NAMESPACE: \($m.namespace)\n SA: \($m.name)\n iam.gke.io/gcp-service-account: \($a["iam.gke.io/gcp-service-account"])\n"' || true
echo
echo "Interpretation:"
echo "- Service accounts used with Workload Identity should have annotation:"
echo " iam.gke.io/gcp-service-account: <gsa-name>@${PROJECT_ID}.iam.gserviceaccount.com"
echo "- Workloads lacking this annotation on their service account will not use a dedicated GCP service account."
echo
echo "=== Workloads and the service accounts they use ==="
echo "Deployments:"
kubectl get deploy --all-namespaces -o json \
| jq -r '.items[]
| "DEPLOYMENT: \(.metadata.namespace)/\(.metadata.name)\n SERVICE ACCOUNT: \(.spec.template.spec.serviceAccountName // "default")\n"' || true
echo
echo "StatefulSets:"
kubectl get statefulset --all-namespaces -o json \
| jq -r '.items[]
| "STATEFULSET: \(.metadata.namespace)/\(.metadata.name)\n SERVICE ACCOUNT: \(.spec.template.spec.serviceAccountName // "default")\n"' || true
echo
echo "DaemonSets:"
kubectl get daemonset --all-namespaces -o json \
| jq -r '.items[]
| "DAEMONSET: \(.metadata.namespace)/\(.metadata.name)\n SERVICE ACCOUNT: \(.spec.template.spec.serviceAccountName // "default")\n"' || true
echo
cat <<'EOF'
How to review this output for CIS GKE 5.2.2:
1) Cluster-level:
- Problem if: workloadIdentityConfig is null or missing.
- Desired: workloadIdentityConfig.workloadPool is set (e.g. "PROJECT_ID.svc.id.goog").
2) Node pools:
- Problem if: any node pool shows config.workloadMetadataConfig.mode = "GCE_METADATA" or is blank.
- Desired: all node pools show mode = "GKE_METADATA".
3) Workload Identity usage:
- Identify important Deployments/StatefulSets/DaemonSets and note their serviceAccountName.
- For each such ServiceAccount:
* Problem if: it has no iam.gke.io/gcp-service-account annotation
(workload will either use node credentials or fail, depending on config).
* Desired: annotation exists and references a dedicated GCP service account.
This script only reports state; enabling Workload Identity or changing node pools
must be done through gcloud / console / IaC as described in the benchmark guidance.
EOF