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
Remediation
Manual Steps
-
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)"
- Determine:
-
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.stateis"ENCRYPTED".databaseEncryption.keyNameis set to aprojects/.../locations/.../keyRings/.../cryptoKeys/...Cloud KMS key.
- If
stateis"DECRYPTED"orkeyNameis empty, plan to enable KMS-based encryption in step 5.
- For each cluster to review, run:
-
Inspect existing KMS key and its IAM policy (workstation with gcloud)
- From the
keyNamein 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.
- From the
-
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
- Create key ring (if needed):
-
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.
- For a new cluster, plan creation with KMS encryption:
-
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"andkeyNamematches the intended Cloud KMS key.
- Re-run the audit and confirm state and key name:
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_kmsisfalsefor a cluster that should comply with CIS GKE 5.3.1.db_encryption_stateisDECRYPTED,UNKNOWN, or anything other thanENCRYPTED.db_encryption_stateisENCRYPTEDbutdb_encryption_key_nameis 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_stateisENCRYPTEDdb_encryption_key_nameis a valid Cloud KMS key resourceuses_cloud_kmsistrue