Skip to main content

Enable Customer-Managed Encryption Keys (CMEK) For GKE

More Info:

Encrypt GKE Persistent Disks with Customer-Managed Encryption Keys so you control the key lifecycle for data at rest. CMEK provides stronger control than default Google-managed encryption.

Risk Level

Medium

Address

Security

Compliance Standards

  • CIS GKE

Triage and Remediation

Remediation

Manual Steps
  1. Inventory and review existing Persistent Disks and their encryption

    • On any machine with gcloud access, list all PDs used by the cluster’s PVs:
      # Get all PVs and their backing PDs
      kubectl get pv -o json | jq -r '.items[].spec.gcePersistentDisk.pdName' | sort -u
      For each PD name returned (replace DISK_NAME, LOCATION, PROJECT_ID):
      gcloud compute disks describe DISK_NAME \
      --location=LOCATION \
      --project=PROJECT_ID \
      --format=json | jq '.diskEncryptionKey'
    • Record whether kmsKeyName is present and which key is used. Disks without kmsKeyName use Google-managed keys.
  2. Review or create a suitable CMEK key in Cloud KMS

    • On any machine with gcloud access, list existing keys that could be used for PD encryption:
      gcloud kms keyrings list --location=LOCATION --project=PROJECT_ID
      gcloud kms keys list --location=LOCATION --keyring=KEYRING_NAME --project=PROJECT_ID
    • If no appropriate key exists, create one per your org’s policy (algorithm, rotation, IAM). Ensure key location matches or is compatible with your cluster’s region/zone.
  3. Verify IAM and key policy allow GKE to use the CMEK key

    • On any machine with gcloud access, get the GKE node service account (or default compute service account):
      gcloud container clusters describe CLUSTER_NAME \
      --region=REGION \
      --project=PROJECT_ID \
      --format='get(nodeConfig.serviceAccount)'
    • Grant it the necessary roles on the KMS key (e.g. roles/cloudkms.cryptoKeyEncrypterDecrypter), adjusting for your security policy:
      gcloud kms keys add-iam-policy-binding KEY_NAME \
      --location=LOCATION \
      --keyring=KEYRING_NAME \
      --member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \
      --role="roles/cloudkms.cryptoKeyEncrypterDecrypter" \
      --project=PROJECT_ID
  4. Decide per-PV remediation: recreate disks with CMEK or accept current state

    • For each disk without CMEK, decide:
      • If data is non-critical or can be migrated: plan to create a new PD with CMEK and migrate workload data (e.g., via backup/restore, filesystem copy, or application-level migration).
      • If data is critical and difficult to migrate: document the business justification for continuing with Google-managed keys until a migration window is available.
    • Note that existing PDs cannot be “converted” in-place from Google-managed to CMEK; they must be recreated and data migrated.
  5. Implement CMEK for new PD-backed PVs and workloads

    • For disks created via gcloud, specify the key:
      gcloud compute disks create NEW_DISK_NAME \
      --size=SIZE_GB \
      --type=DISK_TYPE \
      --zone=ZONE \
      --project=PROJECT_ID \
      --kms-key=projects/PROJECT_ID/locations/LOCATION/keyRings/KEYRING_NAME/cryptoKeys/KEY_NAME
    • For dynamically provisioned PVs, update your StorageClass (via IaC or console) to use the CMEK key per the GKE CMEK documentation, so future PVs are backed by CMEK-encrypted PDs.
  6. Verify remediation

    • After recreating/migrating PVs and/or updating StorageClasses, re-run the disk inspection for relevant PDs:
      gcloud compute disks describe DISK_NAME \
      --location=LOCATION \
      --project=PROJECT_ID \
      --format=json | jq '.diskEncryptionKey.kmsKeyName'
    • Confirm kmsKeyName is set to the expected KMS key for all disks that must comply with this control.
Using kubectl

kubectl cannot enable or change Customer-Managed Encryption Keys (CMEK) for GKE Persistent Disks, because CMEK is configured at the GCP/GKE and disk/IaC level, not via Kubernetes API objects. Use the Google Cloud Console, gcloud CLI, or your IaC to configure CMEK as described in the Manual Steps section.

Automation
#!/usr/bin/env bash
#
# Report GKE PersistentVolumes and their underlying PD CMEK status
# Requirements:
# - Run on any machine with kubectl and gcloud configured
# - kubectl context set to the target cluster
# - gcloud configured with the correct project and permissions
#
# Usage:
# PROJECT_ID="my-project" LOCATION="us-central1" ./check-gke-pd-cmek.sh
#

set -euo pipefail

PROJECT_ID="${PROJECT_ID:-}"
LOCATION="${LOCATION:-}" # zone or region, e.g. us-central1-a or us-central1

if [[ -z "$PROJECT_ID" || -z "$LOCATION" ]]; then
echo "ERROR: Set PROJECT_ID and LOCATION env vars before running."
echo "Example: PROJECT_ID=my-proj LOCATION=us-central1-a $0"
exit 1
fi

if ! command -v kubectl >/dev/null 2>&1; then
echo "ERROR: kubectl not found in PATH."
exit 1
fi

if ! command -v gcloud >/dev/null 2>&1; then
echo "ERROR: gcloud not found in PATH."
exit 1
fi

echo "Checking PersistentVolumes in cluster: $(kubectl config current-context)"
echo "Project: $PROJECT_ID"
echo "Location: $LOCATION"
echo

# Header
printf '%-40s %-15s %-20s %-80s\n' "PV_NAME" "STATUS" "PD_NAME" "KMS_KEY_NAME"
printf '%-40s %-15s %-20s %-80s\n' "------" "------" "-------" "-----------"

# Get PVs that are backed by GCE PDs
kubectl get pv -o json \
| jq -r '.items[]
| select(.spec.gcePersistentDisk != null or .spec.persistentVolumeSource.gcePersistentDisk != null)
| [
.metadata.name,
(if .spec.gcePersistentDisk != null
then .spec.gcePersistentDisk.pdName
else .spec.persistentVolumeSource.gcePersistentDisk.pdName
end)
]
| @tsv' \
| while IFS=$'\t' read -r PV_NAME PD_NAME; do
# Query the disk encryption key
KMS_KEY_NAME=$(gcloud compute disks describe "$PD_NAME" \
--location "$LOCATION" \
--project "$PROJECT_ID" \
--format="value(diskEncryptionKey.kmsKeyName)" 2>/dev/null || true)

if [[ -z "$KMS_KEY_NAME" ]]; then
STATUS="NO_CMEK"
else
STATUS="CMEK_ENABLED"
fi

printf '%-40s %-15s %-20s %-80s\n' "$PV_NAME" "$STATUS" "$PD_NAME" "${KMS_KEY_NAME:-<google-managed>}"
done

cat <<'EOF'

Interpretation:

- Rows with STATUS = CMEK_ENABLED:
These Persistent Disks are encrypted with a Customer-Managed Encryption Key.
KMS_KEY_NAME shows the full key resource path.

- Rows with STATUS = NO_CMEK and KMS_KEY_NAME = <google-managed> (or empty):
These Persistent Disks rely on default Google-managed encryption.
For the CIS GKE 5.9.1 control, these entries are the findings that
require review. Decide whether they should be recreated or future
disks configured with CMEK following:
https://cloud.google.com/kubernetes-engine/docs/how-to/using-cmek

Note: This script only reports current state; it does not modify any disks or keys.
EOF