Ensure Secure Boot For Shielded Gke Nodes Is Enabled
More Info:
Enable Secure Boot For Shielded Gke Nodes To Verify The Digital Signature Of Node Boot Components
Risk Level
Critical
Address
Security
Compliance Standards
- CIS GKE
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Identify node pools and check Secure Boot status
Run on any machine with gcloud configured:PROJECT_ID="YOUR_PROJECT_ID"CLUSTER_NAME="YOUR_CLUSTER_NAME"LOCATION="YOUR_CLUSTER_LOCATION"gcloud container node-pools list \--project "${PROJECT_ID}" \--cluster "${CLUSTER_NAME}" \--location "${LOCATION}"For each node pool from the output:
POOL_NAME="NODE_POOL_NAME"gcloud container node-pools describe "${POOL_NAME}" \--cluster "${CLUSTER_NAME}" \--location "${LOCATION}" \--project "${PROJECT_ID}" \--format json | jq .config.shieldedInstanceConfigRecord which pools have
"enableSecureBoot": trueand which do not or whereshieldedInstanceConfigis null. -
Decide which workloads must enforce Secure Boot
On any machine with kubectl access, list workloads per node pool (via label or taint if used):kubectl get nodes -o wide --show-labelsCorrelate nodes’
cloud.google.com/gke-nodepoollabel with running pods:kubectl get pods -A -o wide --sort-by=.spec.nodeNameDetermine which namespaces/workloads must be migrated to Secure Boot–enabled pools (e.g., sensitive/regulated workloads).
-
Create replacement node pools with Secure Boot enabled
For each non‑compliant node pool that you plan to replace, create a new one in the same cluster and location with compatible settings (machine type, disk size, autoscaling, etc.) and Secure Boot enabled:NEW_POOL_NAME="secureboot-pool-1"gcloud container node-pools create "${NEW_POOL_NAME}" \--cluster "${CLUSTER_NAME}" \--location "${LOCATION}" \--project "${PROJECT_ID}" \--machine-type "e2-standard-4" \--num-nodes 3 \--shielded-secure-bootAdjust flags (e.g.,
--node-locations,--disk-size,--enable-autoscaling) to match your existing pool’s configuration. -
Migrate workloads to Secure Boot–enabled node pools
On any machine with kubectl access, cordon and drain nodes from a non‑compliant pool so that pods are rescheduled onto the new pool (ensure PodDisruptionBudgets allow this):# List nodes in a specific node poolkubectl get nodes -l cloud.google.com/gke-nodepool="OLD_POOL_NAME"# For each node in that pool:NODE_NAME="NODE_NAME_IN_OLD_POOL"kubectl cordon "${NODE_NAME}"kubectl drain "${NODE_NAME}" --ignore-daemonsets --delete-emptydir-dataConfirm pods are now running on nodes in the Secure Boot–enabled pool:
kubectl get pods -A -o wide --sort-by=.spec.nodeName -
Delete non‑compliant node pools (after verifying migration)
Once you have verified that no critical workloads remain on an old non‑Secure‑Boot pool, delete that pool:gcloud container node-pools delete "OLD_POOL_NAME" \--cluster "${CLUSTER_NAME}" \--location "${LOCATION}" \--project "${PROJECT_ID}"Repeat for each non‑compliant pool you have successfully migrated off.
-
Re-verify Secure Boot compliance for all node pools
On any machine with gcloud configured, re-run the audit for every remaining pool:gcloud container node-pools list \--project "${PROJECT_ID}" \--cluster "${CLUSTER_NAME}" \--location "${LOCATION}" \--format="value(name)" | while read POOL_NAME; doecho "Node pool: ${POOL_NAME}"gcloud container node-pools describe "${POOL_NAME}" \--cluster "${CLUSTER_NAME}" \--location "${LOCATION}" \--project "${PROJECT_ID}" \--format json | jq .config.shieldedInstanceConfigdoneConfirm that all production‑relevant node pools have
"enableSecureBoot": true; document any exceptions with a risk acceptance if they remain.
Using kubectl
kubectl cannot enable Secure Boot on GKE nodes because this setting is defined on the node pool at the GKE/control-plane level via Google Cloud tooling (console, gcloud, or IaC). Refer to the Manual Steps section for creating a compliant node pool with Secure Boot enabled and migrating workloads.
Automation
#!/usr/bin/env bash
# Report Secure Boot status for all node pools in all GKE clusters in a project.
# Runs with: any machine with gcloud, jq, and kubectl access
set -euo pipefail
PROJECT_ID="<PROJECT_ID>" # e.g. "my-gcp-project"
# Optional: restrict locations, e.g. REGIONS="us-central1 us-east1"
REGIONS="$(gcloud compute regions list --project "${PROJECT_ID}" --format='value(name)')"
ZONES="$(gcloud compute zones list --project "${PROJECT_ID}" --format='value(name)')"
echo "Project: ${PROJECT_ID}"
echo
check_cluster_group() {
local scope_type="$1" # "regions" or "zones"
local scope_list="$2"
for LOC in ${scope_list}; do
# List clusters in this location
CLUSTERS=$(gcloud container clusters list \
--project "${PROJECT_ID}" \
"--${scope_type%?}=${LOC}" \
--format='value(name)' 2>/dev/null || true)
[ -z "${CLUSTERS}" ] && continue
for CLUSTER in ${CLUSTERS}; do
echo "=== Cluster: ${CLUSTER} (${LOC}) ==="
# List node pools
POOLS=$(gcloud container node-pools list \
--cluster "${CLUSTER}" \
"--${scope_type%?}=${LOC}" \
--project "${PROJECT_ID}" \
--format='value(name)' 2>/dev/null || true)
if [ -z "${POOLS}" ]; then
echo " (no node pools found)"
echo
continue
fi
for POOL in ${POOLS}; do
CONFIG_JSON=$(gcloud container node-pools describe "${POOL}" \
--cluster "${CLUSTER}" \
"--${scope_type%?}=${LOC}" \
--project "${PROJECT_ID}" \
--format json 2>/dev/null || echo '{}')
SECURE_BOOT=$(echo "${CONFIG_JSON}" | jq -r '.config.shieldedInstanceConfig.enableSecureBoot // "unset"')
NODE_VERSION=$(echo "${CONFIG_JSON}" | jq -r '.config.imageType + " / " + (.version // "unknown")')
# Output one line per pool in a parseable form
echo " NodePool: ${POOL}"
echo " Location: ${LOC}"
echo " NodeVersion: ${NODE_VERSION}"
echo " SecureBoot: ${SECURE_BOOT}"
if [ "${SECURE_BOOT}" != "true" ]; then
echo " STATUS: NON_COMPLIANT (Secure Boot not enabled)"
else
echo " STATUS: COMPLIANT"
fi
echo
done
echo
done
done
}
# Check regional clusters
check_cluster_group "regions" "${REGIONS}"
# Check zonal clusters
check_cluster_group "zones" "${ZONES}"
Explanation of output indicating a problem:
- For each node pool you will see a block like:
SecureBoot: trueandSTATUS: COMPLIANT→ meets CISGKE 5.5.7.SecureBoot: false,SecureBoot: null, orSecureBoot: unsetandSTATUS: NON_COMPLIANT (Secure Boot not enabled)→ this node pool does not have Secure Boot enabled and needs review.
- Any node pool marked
NON_COMPLIANTshould be treated as a candidate for migration to a new node pool created with--shielded-secure-boot, followed by deletion of the old pool after workloads are moved.