Skip to main content

Kubernetes Secrets Are Encrypted Using Keys Managed In Cloud Kms

More Info:​

Where etcd encryption is used, appropriate providers should be configured.

Risk Level​

Medium

Address​

Security

Compliance Standards​

  • APRA CPS 234 (Australia)
  • BSI C5 (Germany)
  • Brazil LGPD
  • CCPA / CPRA (California)
  • CIS Critical Security Controls v8
  • CIS GKE
  • CMMC 2.0
  • CSA Cloud Controls Matrix v4
  • DPDPA
  • Digital Operational Resilience Act (EU)
  • ISO/IEC 27017
  • ISO/IEC 27018
  • ISO/IEC 27701
  • KSA PDPL
  • MAS Technology Risk Management (Singapore)
  • MITRE ATT&CK (Cloud)
  • NIS2 Directive
  • NIST SP 800-171
  • NYDFS 23 NYCRR 500
  • SWIFT Customer Security Controls Framework
  • Sarbanes-Oxley IT General Controls
  • Securities and Exchange Board of India (SEBI) - Cloud Security Adoption Framework
  • UK NCSC Cyber Assessment Framework

Triage and Remediation​

Remediation​

Manual Steps
  1. Identify cluster and projects (workstation with gcloud configured)

    • Determine:
      • GKE cluster project ID
      • Cluster name and location (zone/region)
      • KMS key project ID (can be same or different)
    • List clusters to confirm exact identifiers:
      gcloud container clusters list --format="table(name,location,project)"
  2. Check whether database encryption is enabled and KMS key in use

    • For each cluster to review, run:
      gcloud container clusters describe CLUSTER_NAME \
      --location LOCATION \
      --project CLUSTER_PROJECT_ID \
      --format="json(databaseEncryption)"
    • Confirm:
      • .databaseEncryption.state is "ENCRYPTED"
      • .databaseEncryption.keyName is set to a projects/.../locations/.../keyRings/.../cryptoKeys/... Cloud KMS key.
    • If state is "DECRYPTED" or keyName is empty, plan to enable KMS-based encryption in step 5.
  3. Inspect existing KMS key and its IAM policy (workstation with gcloud)

    • From the keyName in step 2, extract:
      • KEY_PROJECT_ID, LOCATION, RING_NAME, KEY_NAME.
    • View key details:
      gcloud kms keys describe KEY_NAME \
      --location LOCATION \
      --keyring RING_NAME \
      --project KEY_PROJECT_ID
    • Check current IAM bindings:
      gcloud kms keys get-iam-policy KEY_NAME \
      --location LOCATION \
      --keyring RING_NAME \
      --project KEY_PROJECT_ID
    • Verify that the Kubernetes Engine Service Agent service account for the cluster project has the role roles/cloudkms.cryptoKeyEncrypterDecrypter.
  4. If there is no suitable KMS key, create one (workstation with gcloud)

    • Create key ring (if needed):
      gcloud kms keyrings create RING_NAME \
      --location LOCATION \
      --project KEY_PROJECT_ID
    • Create key:
      gcloud kms keys create KEY_NAME \
      --location LOCATION \
      --keyring RING_NAME \
      --purpose encryption \
      --project KEY_PROJECT_ID
    • Grant the Kubernetes Engine Service Agent:
      gcloud kms keys add-iam-policy-binding KEY_NAME \
      --location LOCATION \
      --keyring RING_NAME \
      --member "serviceAccount:SERVICE_ACCOUNT_NAME" \
      --role roles/cloudkms.cryptoKeyEncrypterDecrypter \
      --project KEY_PROJECT_ID
  5. Enable or update Application-layer Secrets Encryption on the cluster (workstation with gcloud)

    • For a new cluster, plan creation with KMS encryption:
      gcloud container clusters create CLUSTER_NAME \
      --cluster-version=latest \
      --zone ZONE \
      --database-encryption-key \
      "projects/KEY_PROJECT_ID/locations/LOCATION/keyRings/RING_NAME/cryptoKeys/KEY_NAME" \
      --project CLUSTER_PROJECT_ID
    • For an existing cluster, enable/update encryption:
      gcloud container clusters update CLUSTER_NAME \
      --zone ZONE \
      --database-encryption-key \
      "projects/KEY_PROJECT_ID/locations/LOCATION/keyRings/RING_NAME/cryptoKeys/KEY_NAME" \
      --project CLUSTER_PROJECT_ID
    • Note: this is a control-plane change; review maintenance windows and any organizational constraints before applying.
  6. Verify configuration after change (workstation with gcloud)

    • Re-run the audit and confirm state and key name:
      gcloud container clusters describe CLUSTER_NAME \
      --location LOCATION \
      --project CLUSTER_PROJECT_ID \
      --format="json(databaseEncryption)"
    • Ensure the output shows "state": "ENCRYPTED" and keyName matches the intended Cloud KMS key.
Using kubectl

kubectl cannot configure etcd / database encryption or attach Cloud KMS keys, because this setting is controlled at the managed control plane level (via gcloud, console, or IaC), not through Kubernetes API objects. To remediate this finding, follow the provider-level guidance in the Manual Steps section.

Automation
#!/usr/bin/env bash
#
# audit-gke-secrets-kms-encryption.sh
#
# Purpose:
# Report whether GKE clusters have Kubernetes Secrets encrypted using
# Cloud KMS–managed keys (Application-layer Secrets Encryption).
#
# Requirements:
# - gcloud (authenticated with sufficient permissions to list/describe clusters)
# - jq
#
# Usage examples:
# # Single project, all locations
# ./audit-gke-secrets-kms-encryption.sh my-gcp-project
#
# # Multiple projects
# ./audit-gke-secrets-kms-encryption.sh proj-a proj-b proj-c
#

set -euo pipefail

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

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

if [ "$#" -lt 1 ]; then
echo "Usage: $0 <gcp-project-id> [<gcp-project-id>...]" >&2
exit 1
fi

echo "timestamp,project,cluster_name,location,release_channel,dataplane,db_encryption_state,db_encryption_key_name,uses_cloud_kms"

for PROJECT in "$@"; do
# List all clusters in the project (all locations)
CLUSTERS_JSON="$(gcloud container clusters list --project "${PROJECT}" --format=json 2>/dev/null || echo '[]')"
CLUSTER_COUNT="$(echo "${CLUSTERS_JSON}" | jq 'length')"

if [ "${CLUSTER_COUNT}" -eq 0 ]; then
echo "INFO: No GKE clusters found in project ${PROJECT}" >&2
continue
fi

echo "${CLUSTERS_JSON}" | jq -r '.[] | [.name, .location] | @tsv' | while IFS=$'\t' read -r CLUSTER_NAME LOCATION; do
# Describe cluster to get databaseEncryption details
DESC_JSON="$(gcloud container clusters describe "${CLUSTER_NAME}" \
--location "${LOCATION}" \
--project "${PROJECT}" \
--format=json 2>/dev/null || echo '{}')"

# Extract fields of interest
DB_STATE="$(echo "${DESC_JSON}" | jq -r '.databaseEncryption.state // "UNKNOWN"')"
DB_KEY_NAME="$(echo "${DESC_JSON}" | jq -r '.databaseEncryption.keyName // ""')"

# Optional: context fields to help with review
RELEASE_CHANNEL="$(echo "${DESC_JSON}" | jq -r '.releaseChannel.channel // "UNSPECIFIED"')"
DATAPLANE="$(echo "${DESC_JSON}" | jq -r '.datapathProvider // "UNSPECIFIED"')"

# Determine if Cloud KMS is used:
# - state == "ENCRYPTED"
# - keyName is a fully-qualified KMS resource
if [ "${DB_STATE}" = "ENCRYPTED" ] && [[ "${DB_KEY_NAME}" == projects/*/locations/*/keyRings/*/cryptoKeys/* ]]; then
USES_KMS="true"
else
USES_KMS="false"
fi

echo "$(date -u +"%Y-%m-%dT%H:%M:%SZ"),${PROJECT},${CLUSTER_NAME},${LOCATION},${RELEASE_CHANNEL},${DATAPLANE},${DB_STATE},${DB_KEY_NAME},${USES_KMS}"
done
done

How to run (any machine with gcloud access):

chmod +x audit-gke-secrets-kms-encryption.sh
./audit-gke-secrets-kms-encryption.sh my-gcp-project-1 my-gcp-project-2 > gke-kms-audit.csv

How to interpret the output:

The CSV columns are:

timestamp,project,cluster_name,location,release_channel,dataplane,db_encryption_state,db_encryption_key_name,uses_cloud_kms

Problematic / needs review:

  • uses_cloud_kms is false for a cluster that should comply with CIS GKE 5.3.1.
  • db_encryption_state is DECRYPTED, UNKNOWN, or anything other than ENCRYPTED.
  • db_encryption_state is ENCRYPTED but db_encryption_key_name is empty or not a Cloud KMS resource of the form: projects/<key_project_id>/locations/<location>/keyRings/<ring_name>/cryptoKeys/<key_name>

Compliant (for this control):

  • db_encryption_state is ENCRYPTED
  • db_encryption_key_name is a valid Cloud KMS key resource
  • uses_cloud_kms is true

Additional Reading:​