Skip to main content

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

Manual Steps
  1. Confirm whether Workload Identity is enabled at the cluster level

    • Run on any machine with gcloud access:
      gcloud container clusters describe CLUSTER_NAME \
      --location LOCATION \
      --project PROJECT_ID \
      --format json | jq '.workloadIdentityConfig'
    • If the output is null or empty, Workload Identity is not enabled and should be considered for activation.
  2. 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.
  3. 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_METADATA and migrate workloads before draining/deleting old pools.
  4. Identify workloads that currently depend on node service account credentials

    • On any machine with kubectl access, 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.
  5. 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 gcloud or 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.
  6. Verify configuration after changes

    • Re-run the cluster-level check:
      gcloud container clusters describe CLUSTER_NAME \
      --location LOCATION \
      --project PROJECT_ID \
      --format json | jq '.workloadIdentityConfig'
      Confirm it shows the expected workloadPool (for example, PROJECT_ID.svc.id.goog).
    • For each node pool, confirm GKE_METADATA mode:
      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).
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