Ensure Secure Boot For Shielded GKE Nodes Is Enabled
More Info:
Enable Secure Boot on Shielded GKE nodes so only signed, trusted boot components are permitted to load. This blocks unsigned kernel modules and boot-level malware.
Risk Level
Medium
Address
Security
Compliance Standards
- CIS GKE
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On any machine with
gcloudconfigured, list node pools and identify which are non‑compliant:gcloud container node-pools list \--cluster CLUSTER_NAME \--location LOCATION \--project PROJECT_ID \--format="table(name,config.shieldedInstanceConfig)"Review the
shieldedInstanceConfigfield; node pools whereenableSecureBootis missing orfalseneed remediation. -
For each node pool, gather full Shielded VM details to confirm status:
gcloud container node-pools describe POOL_NAME \--cluster CLUSTER_NAME \--location LOCATION \--project PROJECT_ID \--format=json | jq '.config.shieldedInstanceConfig'Document which workloads and namespaces currently run on each non‑compliant pool.
-
Plan the remediation (per cluster / environment): decide which node pools will be replaced, the desired machine type, size, labels/taints, and maintenance window; validate that enabling Secure Boot is supported for the node image and machine type used in your organization.
-
Create a new compliant node pool with Secure Boot enabled (repeat per replacement pool):
gcloud container node-pools create NEW_POOL_NAME \--cluster CLUSTER_NAME \--location LOCATION \--project PROJECT_ID \--shielded-secure-bootAdd any required flags (e.g.,
--machine-type,--num-nodes,--node-labels,--node-taints) to match your existing pool’s configuration. -
Migrate workloads from each non‑compliant node pool to its new Secure Boot pool using your standard process (e.g., update node selectors/affinity, taints/tolerations, PodDisruptionBudgets, and scale settings), then drain the old nodes and delete the non‑compliant pool:
gcloud container node-pools delete OLD_POOL_NAME \--cluster CLUSTER_NAME \--location LOCATION \--project PROJECT_ID -
Verify remediation for all pools:
gcloud container node-pools list \--cluster CLUSTER_NAME \--location LOCATION \--project PROJECT_ID \--format="table(name,config.shieldedInstanceConfig)"Confirm every active node pool shows Secure Boot enabled in
shieldedInstanceConfig.
Using kubectl
kubectl cannot enable Secure Boot for Shielded GKE nodes, because this setting is only configurable on the node pool via GCP (Cloud Console, gcloud, or IaC such as Terraform). Use those cloud-provider tools instead and follow the guidance in the Manual Steps section to recreate node pools with --shielded-secure-boot and migrate workloads.
Automation
#!/usr/bin/env bash
# Report Secure Boot status for all node pools in all GKE clusters in a project.
# REQUIREMENTS:
# - gcloud SDK installed and authenticated
# - jq installed
# USAGE:
# PROJECT_ID=your-project-id ./check-gke-secure-boot.sh
set -euo pipefail
PROJECT_ID="${PROJECT_ID:-}"
if [[ -z "$PROJECT_ID" ]]; then
echo "ERROR: PROJECT_ID environment variable must be set." >&2
exit 1
fi
echo "Checking Secure Boot status for all GKE node pools in project: $PROJECT_ID"
echo
# List all clusters (zonal and regional)
clusters_json="$(gcloud container clusters list \
--project "$PROJECT_ID" \
--format=json)"
if [[ "$clusters_json" == "[]" ]]; then
echo "No GKE clusters found in project $PROJECT_ID"
exit 0
fi
# Header
printf "%-40s %-20s %-25s %-25s %-15s\n" \
"CLUSTER" "LOCATION" "NODE_POOL" "SHIELDED_SECURE_BOOT" "ISSUE"
printf "%0.s-" {1..130}; echo
# Iterate over clusters and their node pools
echo "$clusters_json" | jq -r '.[] | @base64' | while read -r cluster_b64; do
_jq() { echo "$cluster_b64" | base64 --decode | jq -r "$1"; }
CLUSTER_NAME=$(_jq '.name')
LOCATION=$(_jq '.location')
node_pools_json="$(gcloud container node-pools list \
--cluster "$CLUSTER_NAME" \
--location "$LOCATION" \
--project "$PROJECT_ID" \
--format=json)"
if [[ "$node_pools_json" == "[]" ]]; then
printf "%-40s %-20s %-25s %-25s %-15s\n" \
"$CLUSTER_NAME" "$LOCATION" "-" "-" "NO_NODE_POOLS"
continue
fi
echo "$node_pools_json" | jq -r '.[] | @base64' | while read -r np_b64; do
_npjq() { echo "$np_b64" | base64 --decode | jq -r "$1"; }
NODE_POOL_NAME=$(_npjq '.name')
# Describe node pool to get shieldedInstanceConfig
np_desc="$(gcloud container node-pools describe "$NODE_POOL_NAME" \
--cluster "$CLUSTER_NAME" \
--location "$LOCATION" \
--project "$PROJECT_ID" \
--format=json)"
# secureBoot is a boolean; may be null/absent
SECURE_BOOT_VAL="$(echo "$np_desc" \
| jq -r '.config.shieldedInstanceConfig.secureBoot // "false"')"
ISSUE="OK"
if [[ "$SECURE_BOOT_VAL" != "true" ]]; then
ISSUE="MISSING_SECURE_BOOT"
fi
printf "%-40s %-20s %-25s %-25s %-15s\n" \
"$CLUSTER_NAME" "$LOCATION" "$NODE_POOL_NAME" "$SECURE_BOOT_VAL" "$ISSUE"
done
done
cat <<'EOF'
INTERPRETING RESULTS
--------------------
- SHIELDED_SECURE_BOOT == "true" and ISSUE == "OK":
The node pool is configured with Secure Boot enabled and conforms to CIS GKE 5.5.7.
- SHIELDED_SECURE_BOOT == "false" (or blank/null) and ISSUE == "MISSING_SECURE_BOOT":
This node pool does NOT have Secure Boot enabled. It should be reviewed.
To remediate, create a new node pool with:
gcloud container node-pools create <node_pool_name> \
--cluster <cluster_name> \
--location <location> \
--shielded-secure-boot
Then migrate workloads and delete the non-conforming node pool.
NOTE: This script only reports state; it does NOT change any configuration.
EOF