Kubernetes Secrets Are Encrypted Using Keys Managed In
More Info:​
Enable application-layer secrets encryption so Kubernetes Secrets are encrypted at rest using a Cloud KMS key you control. This protects secret data beyond Googles default encryption.
Risk Level​
High
Address​
Security
Compliance Standards​
- CIS GKE
Triage and Remediation​
- Remediation
Remediation​
Manual Steps
-
Identify the GKE cluster(s) and current encryption status
Run on any machine withgcloudconfigured:gcloud container clusters list --project <cluster_project_id>gcloud container clusters describe <cluster_name> \--location <location> \--project <cluster_project_id> \--format json | jq '.databaseEncryption'Review whether
stateis"ENCRYPTED"and akeyNameis present. Ifstateis"DECRYPTED"orkeyNameis empty, application‑layer secrets encryption with Cloud KMS is not enabled. -
Decide the KMS project, location, and key structure
With your security/ops team, determine:- Which project will own the KMS key (
<key_project_id>). - Region (
<location>) that matches or is appropriate for your cluster. - Key ring name (
<ring_name>) and key name (<key_name>).
Document whether you will use a dedicated key per cluster, per environment, or shared across clusters, considering key‑rotation and access‑control policies.
- Which project will own the KMS key (
-
Create or verify the Cloud KMS key
If a suitable key does not already exist, create one (run on any machine withgcloud):gcloud kms keyrings create <ring_name> \--location <location> \--project <key_project_id>gcloud kms keys create <key_name> \--location <location> \--keyring <ring_name> \--purpose encryption \--project <key_project_id>If using an existing key, confirm it is of purpose
ENCRYPT_DECRYPTand in the correct region:gcloud kms keys describe <key_name> \--location <location> \--keyring <ring_name> \--project <key_project_id> -
Grant the GKE service agent access to the KMS key
First, identify the Kubernetes Engine Service Agent for the cluster project (run on any machine withgcloud):gcloud beta services identity create \--service container.googleapis.com \--project <cluster_project_id> \--quiet \--format 'value(email)'Then grant it
roles/cloudkms.cryptoKeyEncrypterDecrypteron the key: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>Replace
<service_account_name>with the email printed by the previous command. -
Enable application‑layer secrets encryption on the cluster
For a new cluster (decide whether to recreate vs update an existing cluster):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>To enable on an existing cluster:
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>Assess operational impact: cluster control‑plane reconfiguration may temporarily affect control‑plane availability; coordinate during a maintenance window if needed.
-
Verify encryption is enabled and using the intended key
After the update/recreate completes, run (any machine withgcloud):gcloud container clusters describe <cluster_name> \--location <location> \--project <cluster_project_id> \--format json | jq '.databaseEncryption'Confirm that
stateis"ENCRYPTED"and thatkeyNamematches
projects/<key_project_id>/locations/<location>/keyRings/<ring_name>/cryptoKeys/<key_name>.
Using kubectl
kubectl cannot configure application-layer secrets encryption or Cloud KMS keys, because this setting is managed entirely in the GKE control plane via the Google Cloud Console, gcloud CLI, or IaC. To remediate this finding, follow the guidance in the Manual Steps section for making the change at the cloud provider configuration level.
Automation
#!/usr/bin/env bash
#
# Check GKE clusters for Application-layer Secrets Encryption with Cloud KMS.
#
# REQUIREMENTS:
# - Runs on: any machine with gcloud, jq, and access to the relevant projects.
# - Auth: 'gcloud auth login' (or service account) and 'gcloud config set project' as needed.
#
# USAGE:
# ./check_gke_kms_encryption.sh # use current gcloud project
# GCP_PROJECTS="proj-a proj-b" ./check_gke_kms_encryption.sh
# GCP_PROJECTS="$(gcloud projects list --format='value(projectId)')" ./check_gke_kms_encryption.sh
#
# OUTPUT:
# Columns:
# PROJECT GKE project ID
# CLUSTER GKE cluster name
# LOCATION Cluster location (zone or region)
# ENABLED true/false if databaseEncryption is enabled
# KEY_RESOURCE Full KMS key resource, or "-" if not set
# STATE OK / NOT_ENCRYPTED / MISCONFIGURED
#
# INTERPRETATION (what indicates a problem):
# - STATE=NOT_ENCRYPTED
# => Application-layer Secrets Encryption is NOT enabled. This fails CIS GKE 5.3.1.
# - STATE=MISCONFIGURED
# => 'databaseEncryption' block exists but is missing key fields
# (e.g., keyName or state). Requires manual review.
# - STATE=OK
# => Application-layer Secrets Encryption is enabled and has a KMS key configured.
#
set -euo pipefail
# ANSI colors for readability (can be disabled by setting NO_COLOR=1)
if [[ "${NO_COLOR:-}" == "1" ]]; then
RED=""; GREEN=""; YELLOW=""; RESET=""
else
RED="$(printf '\033[31m')"
GREEN="$(printf '\033[32m')"
YELLOW="$(printf '\033[33m')"
RESET="$(printf '\033[0m')"
fi
PROJECTS=()
if [[ -n "${GCP_PROJECTS:-}" ]]; then
# Space-separated list from env var
read -r -a PROJECTS <<< "${GCP_PROJECTS}"
else
# Use current gcloud project
CURRENT_PROJECT="$(gcloud config get-value project 2>/dev/null || true)"
if [[ -z "${CURRENT_PROJECT}" ]]; then
echo "ERROR: No projects specified and no current gcloud project set." >&2
echo " Set GCP_PROJECTS or run: gcloud config set project <project_id>" >&2
exit 1
fi
PROJECTS=("${CURRENT_PROJECT}")
fi
echo "PROJECT,CLUSTER,LOCATION,ENABLED,KEY_RESOURCE,STATE" # CSV header
for PROJECT in "${PROJECTS[@]}"; do
CLUSTERS_JSON="$(gcloud container clusters list \
--project "${PROJECT}" \
--format=json 2>/dev/null || true)"
if [[ -z "${CLUSTERS_JSON}" || "${CLUSTERS_JSON}" == "[]" ]]; then
# No clusters in this project; continue silently.
continue
fi
echo "${CLUSTERS_JSON}" | jq -c '.[]' | while read -r CLUSTER; do
NAME="$(echo "${CLUSTER}" | jq -r '.name')"
LOCATION="$(echo "${CLUSTER}" | jq -r '.location')"
# Describe cluster to inspect databaseEncryption
DESCRIBE_JSON="$(gcloud container clusters describe "${NAME}" \
--location "${LOCATION}" \
--project "${PROJECT}" \
--format=json 2>/dev/null || true)"
if [[ -z "${DESCRIBE_JSON}" ]]; then
STATE="MISCONFIGURED"
ENABLED="unknown"
KEY_RESOURCE="-"
printf '%s,"%s",%s,%s,%s,%s%s%s\n' \
"${PROJECT}" "${NAME}" "${LOCATION}" "${ENABLED}" "${KEY_RESOURCE}" \
"${YELLOW}" "${STATE}" "${RESET}"
continue
fi
DB_ENCRYPTION="$(echo "${DESCRIBE_JSON}" | jq -c '.databaseEncryption // {}')"
RAW_STATE="$(echo "${DB_ENCRYPTION}" | jq -r '.state // empty')"
KEY_NAME="$(echo "${DB_ENCRYPTION}" | jq -r '.keyName // empty')"
if [[ -z "${RAW_STATE}" ]]; then
# No state field at all => not configured
ENABLED="false"
KEY_RESOURCE="-"
STATE="NOT_ENCRYPTED"
COLOR="${RED}"
elif [[ "${RAW_STATE}" != "ENCRYPTED" ]]; then
# State present but not ENCRYPTED (e.g., DECRYPTED or UNKNOWN)
ENABLED="false"
KEY_RESOURCE="${KEY_NAME:-"-"}"
STATE="NOT_ENCRYPTED"
COLOR="${RED}"
else
# State == ENCRYPTED, verify keyName present
if [[ -z "${KEY_NAME}" ]]; then
ENABLED="true"
KEY_RESOURCE="-"
STATE="MISCONFIGURED"
COLOR="${YELLOW}"
else
ENABLED="true"
KEY_RESOURCE="${KEY_NAME}"
STATE="OK"
COLOR="${GREEN}"
fi
fi
printf '%s,"%s",%s,%s,"%s",%s%s%s\n' \
"${PROJECT}" "${NAME}" "${LOCATION}" "${ENABLED}" "${KEY_RESOURCE}" \
"${COLOR}" "${STATE}" "${RESET}"
done
done
What indicates a problem in the script output
- Any row with
STATE=NOT_ENCRYPTED:- The
databaseEncryptionblock is missing, or .databaseEncryption.stateis notENCRYPTED.- This means Kubernetes Secrets are not protected with Cloud KMS as required.
- The
- Any row with
STATE=MISCONFIGURED:stateisENCRYPTEDbutkeyNameis missing, or the cluster could not be described.- Requires manual review to ensure a valid Cloud KMS key is configured.
- Only rows with
STATE=OKmeet the requirement for CIS GKE 5.3.1.