Skip to main content

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​

Manual Steps
  1. 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.shieldedInstanceConfig

    Record which pools have "enableSecureBoot": true and which do not or where shieldedInstanceConfig is null.

  2. 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-labels

    Correlate nodes’ cloud.google.com/gke-nodepool label with running pods:

    kubectl get pods -A -o wide --sort-by=.spec.nodeName

    Determine which namespaces/workloads must be migrated to Secure Boot–enabled pools (e.g., sensitive/regulated workloads).

  3. 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-boot

    Adjust flags (e.g., --node-locations, --disk-size, --enable-autoscaling) to match your existing pool’s configuration.

  4. 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 pool
    kubectl 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-data

    Confirm pods are now running on nodes in the Secure Boot–enabled pool:

    kubectl get pods -A -o wide --sort-by=.spec.nodeName
  5. 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.

  6. 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; do
    echo "Node pool: ${POOL_NAME}"
    gcloud container node-pools describe "${POOL_NAME}" \
    --cluster "${CLUSTER_NAME}" \
    --location "${LOCATION}" \
    --project "${PROJECT_ID}" \
    --format json | jq .config.shieldedInstanceConfig
    done

    Confirm 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: true and STATUS: COMPLIANT → meets CISGKE 5.5.7.
    • SecureBoot: false, SecureBoot: null, or SecureBoot: unset and STATUS: NON_COMPLIANT (Secure Boot not enabled) → this node pool does not have Secure Boot enabled and needs review.
  • Any node pool marked NON_COMPLIANT should 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.

Additional Reading:​