> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Enable Customer-Managed Encryption Keys (Cmek) For Gke Persistent Disks

### More Info:

Use Customer-Managed Encryption Keys (Cmek) To Encrypt Node Boot And Dynamicallyprovisioned Attached Google Compute Engine Persistent Disks (Pds) Using Keys Managed Within Cloud Key Management Service

### Risk Level

Low

### Address

Security

### Compliance Standards

* CIS GKE
* Securities and Exchange Board of India (SEBI) - Cloud Security Adoption Framework

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify disks in use by the cluster (pods + node boot disks)**
           * On any machine with `gcloud` access:
             ```bash theme={null}
             PROJECT_ID="your-project-id"
             CLUSTER_NAME="your-cluster-name"
             CLUSTER_LOCATION="your-cluster-location" # zone or region

             gcloud container clusters describe "$CLUSTER_NAME" \
               --project "$PROJECT_ID" \
               --location "$CLUSTER_LOCATION" \
               --format="json|jq '.nodePools[].config.diskType,.nodePools[].config.bootDiskKmsKey'"
             ```
             ```bash theme={null}
             gcloud compute instances list \
               --project "$PROJECT_ID" \
               --format="value(name,zone)" | while read NAME ZONE; do
                 echo "== $NAME ($ZONE) =="
                 gcloud compute instances describe "$NAME" \
                   --project "$PROJECT_ID" \
                   --zone "$ZONE" \
                   --format="json|jq '.disks[].diskEncryptionKey.kmsKeyName'"
               done
             ```

        2. **Review PDs backing PersistentVolumes for CMEK usage**
           * For each PV in the cluster, get its disk name and location (any machine with `kubectl` and `gcloud`):
             ```bash theme={null}
             kubectl get pv -o wide
             ```
           * For each PD found (example disk name and zone):
             ```bash theme={null}
             PV_NAME="pd-disk-name"
             LOCATION="zone-or-region"
             PROJECT_ID="your-project-id"

             gcloud compute disks describe "$PV_NAME" \
               --location "$LOCATION" \
               --project "$PROJECT_ID" \
               --format json | jq '.diskEncryptionKey.kmsKeyName'
             ```
           * Decide: if `.diskEncryptionKey.kmsKeyName` is `null`, that disk is **not** CMEK-encrypted and should be considered for migration.

        3. **Check StorageClasses used for dynamic PD provisioning**
           * On any machine with `kubectl` access:
             ```bash theme={null}
             kubectl get storageclass
             kubectl get storageclass -o yaml
             ```
           * For each `StorageClass`, look for:
             * `parameters.kmsKeyName` (GCE PD driver)
             * Or `parameters.csi.storage.gke.io/kms-key` (GKE PD CSI driver)
           * Decide: StorageClasses without a CMEK parameter will provision non‑CMEK disks and should be updated or replaced.

        4. **Review KMS key configuration and IAM access**
           * On any machine with `gcloud` access, list KMS keys intended for GKE use:
             ```bash theme={null}
             LOCATION="kms-location"     # e.g. us-central1
             KEYRING="your-keyring"

             gcloud kms keys list \
               --location "$LOCATION" \
               --keyring "$KEYRING"
             ```
           * Check that the GKE node service accounts and GKE PD CSI SA (if used) have appropriate `cloudkms.cryptoKeyEncrypterDecrypter` on the chosen key:
             ```bash theme={null}
             KEY_NAME="your-key-name"

             gcloud kms keys get-iam-policy "$KEY_NAME" \
               --location "$LOCATION" \
               --keyring "$KEYRING"
             ```
           * Decide: ensure only intended principals are allowed and that GKE identities can use the key.

        5. **Plan and apply configuration changes to enable CMEK**
           * For **new clusters / node pools**, follow the GKE CMEK guide to create or update clusters with CMEK-specified `bootDiskKmsKey` and CMEK-enabled StorageClasses (via `kmsKeyName`/CSI parameter).
           * For **existing non‑CMEK disks**, plan a migration: create CMEK-enabled StorageClasses, create new CMEK-backed PVs, and migrate data (e.g., backup/restore, application-level replication) because encryption type cannot be changed in-place.

        6. **Re-verify after changes**
           * For newly created or migrated PVs, re-run:
             ```bash theme={null}
             gcloud compute disks describe "$PV_NAME" \
               --location "$LOCATION" \
               --project "$PROJECT_ID" \
               --format json | jq '.diskEncryptionKey.kmsKeyName'
             ```
             Confirm it shows the intended KMS key.
           * Re-check `StorageClass` definitions to ensure all default / actively used classes specify the appropriate CMEK key parameter.
      </Accordion>

      <Accordion title="Using kubectl">
        `kubectl` cannot configure Customer-Managed Encryption Keys for GKE node boot disks or dynamically provisioned persistent disks; this is set at the GKE/Compute Engine and Cloud KMS level via the Google Cloud console, `gcloud`, or IaC. Use `kubectl` only to identify affected PersistentVolumes if needed, then follow the guidance in the Manual Steps section to apply CMEK at the cloud provider configuration layer.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Report GKE PD encryption (CMEK vs Google-managed) for all PersistentVolumes
        # that use GCE Persistent Disk in the current cluster.
        #
        # REQUIREMENTS:
        #   - Run on: any machine with kubectl and gcloud configured
        #   - kubectl context set to target cluster
        #   - gcloud project set (or pass --project explicitly)
        #
        # USAGE:
        #   export PROJECT_ID="my-gcp-project"   # optional if gcloud default is set
        #   ./check_gke_pv_cmek.sh

        set -euo pipefail

        PROJECT_ID="${PROJECT_ID:-$(gcloud config get-value project 2>/dev/null || true)}"

        if [[ -z "$PROJECT_ID" || "$PROJECT_ID" == "(unset)" ]]; then
          echo "ERROR: PROJECT_ID is not set and gcloud default project is unset."
          echo "Set it with: export PROJECT_ID='my-gcp-project'"
          exit 1
        fi

        echo "Using GCP project: $PROJECT_ID" >&2

        # List all GCE PD-backed PVs
        echo "Collecting PersistentVolumes using GCE Persistent Disks..." >&2
        pv_json=$(kubectl get pv -o json)

        # Extract relevant fields: PV name, storage class, and PD identifier
        # Supports both:
        #   - pdName (kubernetes.io/gce-pd)
        #   - volumeHandle (CSI pd.csi.storage.gke.io)
        echo "pv_name,storage_class,driver,type,pd_identifier,zone_or_region,kms_key_name" 

        echo "$pv_json" | jq -r '.items[]
          | {
              pv_name: .metadata.name,
              storage_class: (.spec.storageClassName // ""),
              gce_pd_name: (
                ( .spec.gcePersistentDisk.pdName // "" ) as $pd
                | if $pd == "" then null else $pd end
              ),
              csi_driver: (.spec.csi.driver // ""),
              csi_handle: (.spec.csi.volumeHandle // "")
            }
          | select(.gce_pd_name != null or .csi_driver == "pd.csi.storage.gke.io")
          | if .gce_pd_name != null then
              {
                pv_name: .pv_name,
                storage_class: .storage_class,
                driver: "in-tree-gce-pd",
                type: "zonal-or-regional-unknown",
                pd_identifier: .gce_pd_name
              }
            else
              # CSI PD handles look like:
              #   projects/PROJECT/zones/ZONE/disks/DISK
              #   projects/PROJECT/regions/REGION/disks/DISK
              #   or similar variants
              ( .csi_handle
                | capture("^projects/(?<project>[^/]+)/(?<loc_type>zones|regions)/(?<location>[^/]+)/disks/(?<disk>.+)$")
              ) as $h
              | {
                  pv_name: .pv_name,
                  storage_class: .storage_class,
                  driver: "csi-pd",
                  type: $h.loc_type,
                  pd_identifier: $h.disk,
                  location: $h.location
                }
            end
          | @tsv' | while IFS=$'\t' read -r PV_NAME STORAGE_CLASS DRIVER TYPE PD_ID LOCATION; do

          # Determine API location flag and value for gcloud based on TYPE
          LOCATION_FLAG=""
          LOCATION_VALUE=""

          case "$DRIVER" in
            in-tree-gce-pd)
              # For in-tree PD we don't know zone/region from PV alone.
              # Try to find from an attached node (best-effort); if not found, skip.
              # This is a heuristic: we inspect nodes with this PV mounted.
              NODE=$(kubectl get pods --all-namespaces -o json \
                | jq -r --arg pv "$PV_NAME" '
                    .items[]
                    | select(.spec.volumes[]? | .persistentVolumeClaim? | .persistentVolumeClaim.claimName != null)
                    | .spec.nodeName' \
                | head -n1)

              if [[ -z "$NODE" || "$NODE" == "null" ]]; then
                echo "$PV_NAME,$STORAGE_CLASS,$DRIVER,$TYPE,$PD_ID,UNKNOWN,UNKNOWN (node/zone not inferred)" 
                continue
              fi

              ZONE=$(kubectl get node "$NODE" -o json \
                | jq -r '.metadata.labels["topology.gke.io/zone"] // .metadata.labels["topology.kubernetes.io/zone"] // empty')

              if [[ -z "$ZONE" ]]; then
                echo "$PV_NAME,$STORAGE_CLASS,$DRIVER,$TYPE,$PD_ID,UNKNOWN,UNKNOWN (zone label missing)" 
                continue
              fi

              LOCATION_FLAG="--zone"
              LOCATION_VALUE="$ZONE"
              ;;
            csi-pd)
              if [[ "$TYPE" == "zones" ]]; then
                LOCATION_FLAG="--zone"
                LOCATION_VALUE="$LOCATION"
              elif [[ "$TYPE" == "regions" ]]; then
                LOCATION_FLAG="--region"
                LOCATION_VALUE="$LOCATION"
              else
                echo "$PV_NAME,$STORAGE_CLASS,$DRIVER,$TYPE,$PD_ID,UNKNOWN,UNKNOWN (unrecognized CSI handle type)" 
                continue
              fi
              ;;
            *)
              echo "$PV_NAME,$STORAGE_CLASS,$DRIVER,$TYPE,$PD_ID,UNKNOWN,UNKNOWN (unsupported driver)" 
              continue
              ;;
          esac

          # Query GCE disk encryption key
          KMS_KEY_NAME=$(gcloud compute disks describe "$PD_ID" \
            $LOCATION_FLAG "$LOCATION_VALUE" \
            --project "$PROJECT_ID" \
            --format json 2>/dev/null \
            | jq -r '.diskEncryptionKey.kmsKeyName // "GOOGLE-MANAGED-DEFAULT"')

          echo "$PV_NAME,$STORAGE_CLASS,$DRIVER,$TYPE,$PD_ID,${LOCATION_VALUE:-UNKNOWN},$KMS_KEY_NAME"
        done
        ```

        Explanation of output and what indicates a problem:

        * The script prints a CSV header:
          * `pv_name,storage_class,driver,type,pd_identifier,zone_or_region,kms_key_name`
        * For each GCE PD-backed PersistentVolume:
          * `kms_key_name = GOOGLE-MANAGED-DEFAULT`\
            → The disk is **not using CMEK** (uses Google-managed keys) and is non-compliant with the intent of CIS GKE 5.9.1.
          * `kms_key_name` contains a full KMS resource, for example:\
            `projects/PROJECT/locations/LOCATION/keyRings/RING/cryptoKeys/KEY`\
            → The disk **is using CMEK** (candidate for compliance; you still must review that the key/rotation/policy meet your org’s requirements).
        * Lines with `UNKNOWN (...)` in the last field mean the script could not determine encryption status for that PV (for example, missing zone or node information). These need manual investigation with:
          * `kubectl get pv <pv_name> -o yaml`
          * `gcloud compute disks describe <disk> --zone <zone> --project <project>`
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>

### Additional Reading:

* [https://cloud.google.com/kubernetes-engine/docs/how-to/using-cmek](https://cloud.google.com/kubernetes-engine/docs/how-to/using-cmek)
