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
Remediation
Manual Steps
-
Inventory and review existing Persistent Disks and their encryption
- On any machine with
gcloudaccess, list all PDs used by the cluster’s PVs:For each PD name returned (replace# Get all PVs and their backing PDskubectl get pv -o json | jq -r '.items[].spec.gcePersistentDisk.pdName' | sort -uDISK_NAME,LOCATION,PROJECT_ID):gcloud compute disks describe DISK_NAME \--location=LOCATION \--project=PROJECT_ID \--format=json | jq '.diskEncryptionKey' - Record whether
kmsKeyNameis present and which key is used. Disks withoutkmsKeyNameuse Google-managed keys.
- On any machine with
-
Review or create a suitable CMEK key in Cloud KMS
- On any machine with
gcloudaccess, list existing keys that could be used for PD encryption:gcloud kms keyrings list --location=LOCATION --project=PROJECT_IDgcloud 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.
- On any machine with
-
Verify IAM and key policy allow GKE to use the CMEK key
- On any machine with
gcloudaccess, 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
- On any machine with
-
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.
- For each disk without CMEK, decide:
-
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.
- For disks created via
-
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
kmsKeyNameis set to the expected KMS key for all disks that must comply with this control.
- After recreating/migrating PVs and/or updating StorageClasses, re-run the disk inspection for relevant PDs:
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