Skip to main content

Ensure The Gke Metadata Server Is Enabled

More Info:

Running The Gke Metadata Server Prevents Workloads From Accessing Sensitive Instance Metadata And Facilitates Workload Identity

Risk Level

Medium

Address

Security

Compliance Standards

  • CIS GKE

Triage and Remediation

Remediation

Manual Steps
  1. List and review current cluster Workload Identity configuration

    • Machine: Any machine with gcloud configured
    • Command:
      gcloud container clusters describe CLUSTER_NAME \
      --location LOCATION \
      --format="json(workloadIdentityConfig,workloadPool)"
    • Review: Confirm whether workloadIdentityConfig / workloadPool is set to PROJECT_ID.svc.id.goog. If not set, enabling Workload Identity (and thus defaulting new node pools to use the GKE Metadata Server) is likely required.
  2. Check each node pool’s metadata server configuration

    • Machine: Any machine with gcloud configured
    • Command:
      gcloud container clusters describe CLUSTER_NAME \
      --location LOCATION \
      --format json | jq '.nodePools[].config.workloadMetadataConfig'
    • Review: For each node pool, confirm mode is GKE_METADATA_SERVER. Any node pool with a different mode (e.g. GCE_METADATA, UNSPECIFIED, or null) does not have the GKE Metadata Server enabled.
  3. Assess application impact and readiness for Workload Identity

    • Machine: Any machine with kubectl and cluster access
    • Commands (evidence gathering):
      kubectl get serviceaccounts -A
      kubectl get pods -A -o wide
      kubectl get iamserviceaccounts --project=PROJECT_ID 2>/dev/null || true
    • Review:
      • Identify workloads that currently rely on node service account credentials or direct instance metadata access.
      • Determine which workloads will need to be updated to use GCP IAM via Workload Identity (e.g., binding Kubernetes ServiceAccounts to GCP service accounts).
  4. Decide and plan enabling Workload Identity and GKE Metadata Server

    • If the cluster is not using Workload Identity, plan a change window to:
      • Enable Workload Identity at cluster level.
      • Migrate affected workloads to use Kubernetes ServiceAccounts mapped to GCP Service Accounts.
    • If Workload Identity is already enabled but some node pools are not using GKE_METADATA_SERVER, plan to update those node pools, considering any workloads tightly coupled to legacy metadata access patterns.
  5. Enable Workload Identity at the cluster level (if not already enabled)

    • Machine: Any machine with gcloud configured
    • Command:
      gcloud container clusters update CLUSTER_NAME \
      --location LOCATION \
      --identity-namespace=PROJECT_ID.svc.id.goog
    • Impact: Control-plane configuration change; does not automatically alter existing node pools, but new node pools will default to --workload-metadata-from-node=GKE_METADATA_SERVER.
  6. Enable GKE Metadata Server on existing node pools and verify

    • Machine: Any machine with gcloud configured
    • Command (per node pool):
      gcloud container node-pools update NODE_POOL_NAME \
      --cluster=CLUSTER_NAME \
      --location=LOCATION \
      --workload-metadata-from-node=GKE_METADATA_SERVER
    • Verification:
      gcloud container clusters describe CLUSTER_NAME \
      --location LOCATION \
      --format json | jq '.nodePools[].config.workloadMetadataConfig'
      Confirm all node pools now report "mode": "GKE_METADATA_SERVER".
Using kubectl

kubectl cannot enable the GKE Metadata Server or configure Workload Identity, because these settings are managed at the GKE control-plane and node pool level via gcloud/console/IaC, not through Kubernetes API objects. To remediate this finding, follow the guidance in the Manual Steps section using the Google Cloud Console or gcloud commands.

Automation
#!/usr/bin/env bash
# Report GKE Metadata Server / Workload Identity status for all clusters in a project.
# Runs from: any machine with gcloud, jq, and appropriate IAM permissions.

set -euo pipefail

PROJECT_ID="$(gcloud config get-value project 2>/dev/null)"

if [[ -z "${PROJECT_ID}" || "${PROJECT_ID}" == "(unset)" ]]; then
echo "ERROR: gcloud project is not set. Run: gcloud config set project YOUR_PROJECT_ID" >&2
exit 1
fi

echo "Project: ${PROJECT_ID}"
echo

# List all clusters (both zonal and regional)
mapfile -t CLUSTERS < <(gcloud container clusters list \
--project "${PROJECT_ID}" \
--format='value(name,location)' 2>/dev/null)

if [[ "${#CLUSTERS[@]}" -eq 0 ]]; then
echo "No GKE clusters found in project ${PROJECT_ID}."
exit 0
fi

printf "%-30s %-20s %-25s %-35s %-15s\n" \
"CLUSTER" "LOCATION" "NODE_POOL" "WORKLOAD_METADATA_MODE" "ISSUE?"
printf "%0.s-" {1..130}; echo

for ENTRY in "${CLUSTERS[@]}"; do
CLUSTER_NAME="$(awk '{print $1}' <<< "${ENTRY}")"
LOCATION="$(awk '{print $2}' <<< "${ENTRY}")"

# Describe cluster nodePools and workloadMetadataConfig
DESC_JSON="$(gcloud container clusters describe "${CLUSTER_NAME}" \
--location "${LOCATION}" \
--project "${PROJECT_ID}" \
--format=json 2>/dev/null || true)"

if [[ -z "${DESC_JSON}" ]]; then
printf "%-30s %-20s %-25s %-35s %-15s\n" \
"${CLUSTER_NAME}" "${LOCATION}" "-" "DESCRIBE_FAILED" "REVIEW"
continue
fi

# Get identity namespace for the cluster (Workload Identity enabled if non-empty)
ID_NS="$(jq -r '.workloadIdentityConfig.identityNamespace // empty' <<< "${DESC_JSON}")"
if [[ -z "${ID_NS}" ]]; then
CLUSTER_WI="DISABLED"
else
CLUSTER_WI="ENABLED (${ID_NS})"
fi

# Iterate node pools
NP_COUNT="$(jq '.nodePools | length' <<< "${DESC_JSON}")"
if [[ "${NP_COUNT}" -eq 0 ]]; then
printf "%-30s %-20s %-25s %-35s %-15s\n" \
"${CLUSTER_NAME}" "${LOCATION}" "-" "NO_NODE_POOLS" "REVIEW"
continue
fi

for ((i=0; i<NP_COUNT; i++)); do
NP_NAME="$(jq -r ".nodePools[${i}].name" <<< "${DESC_JSON}")"
MODE="$(jq -r ".nodePools[${i}].config.workloadMetadataConfig.mode // \"UNSPECIFIED\"" <<< "${DESC_JSON}")"

# Normalize and flag issues:
# EXPECTED secure setting: GKE_METADATA or GKE_METADATA_SERVER (legacy name).
# Problematic / needs review:
# - MODE == "GCE_METADATA" (pods can query full instance metadata)
# - MODE == "UNSPECIFIED" (older clusters / unclear behavior)
# - Cluster workload identity disabled but node pool uses GKE_METADATA(_SERVER)
ISSUE="OK"

case "${MODE}" in
"GKE_METADATA"|"GKE_METADATA_SERVER")
# Good from metadata-server perspective, but cluster WI disabled is a config mismatch
if [[ "${CLUSTER_WI}" == "DISABLED" ]]; then
ISSUE="REVIEW_WI"
fi
;;
"GCE_METADATA")
ISSUE="PROBLEM"
;;
"UNSPECIFIED")
ISSUE="REVIEW"
;;
*)
ISSUE="REVIEW"
;;
esac

printf "%-30s %-20s %-25s %-35s %-15s\n" \
"${CLUSTER_NAME}" "${LOCATION}" "${NP_NAME}" "${MODE} / WI:${CLUSTER_WI}" "${ISSUE}"
done
done

cat <<'EOF'

Interpretation:

- WORKLOAD_METADATA_MODE = GCE_METADATA (ISSUE=PROBLEM)
-> Workloads access the full Compute Engine metadata server from the node.
This is *not* using the GKE Metadata Server and should be remediated.

- WORKLOAD_METADATA_MODE = UNSPECIFIED or any unexpected value (ISSUE=REVIEW)
-> Older / unclear configuration. Manually inspect the cluster and node pool
settings in the GCP Console or with:
gcloud container clusters describe CLUSTER --location LOCATION --format=json

- WORKLOAD_METADATA_MODE = GKE_METADATA or GKE_METADATA_SERVER AND WI enabled (ISSUE=OK)
-> Desired state: GKE Metadata Server is used and Workload Identity is configured.

- WORKLOAD_METADATA_MODE = GKE_METADATA or GKE_METADATA_SERVER AND WI disabled (ISSUE=REVIEW_WI)
-> Node pools use GKE Metadata Server, but the cluster identity namespace is not set.
Review whether Workload Identity should be enabled for this cluster.

Use this report to decide where to apply:
- Cluster-level Workload Identity:
gcloud container clusters update CLUSTER_NAME \
--location=LOCATION \
--identity-namespace=PROJECT_ID.svc.id.goog

- Node pool-level metadata mode:
gcloud container node-pools update NODE_POOL_NAME \
--cluster=CLUSTER_NAME \
--location=LOCATION \
--workload-metadata-from-node=GKE_METADATA_SERVER

These commands are *not* executed by this script; they require manual review
and explicit action per cluster and node pool.
EOF

Additional Reading: