Skip to main content

Consider GKE Sandbox For Running Untrusted Workloads

More Info:​

Use GKE Sandbox (gVisor) to isolate untrusted workloads from the host kernel, reducing the impact of container escapes. This adds a strong isolation boundary for risky workloads.

Risk Level​

Medium

Address​

Security

Compliance Standards​

  • CIS GKE

Triage and Remediation​

Remediation​

Manual Steps
  1. Identify untrusted / risky workloads and namespaces

    • On any machine with kubectl access, list namespaces and deployments to find workloads that run untrusted code, multi-tenant apps, user-supplied images, or high-risk services:
      kubectl get ns
      kubectl get deploy --all-namespaces -o wide
    • Document which namespaces/workloads should be isolated with GKE Sandbox.
  2. Check current Sandbox (gVisor) configuration on all node pools

    • On any machine with gcloud access, list node pools for the cluster:
      gcloud container node-pools list \
      --cluster CLUSTER_NAME \
      --location LOCATION \
      --project PROJECT_ID
    • For each node pool, inspect sandbox configuration:
      gcloud container node-pools describe NODE_POOL_NAME \
      --cluster CLUSTER_NAME \
      --location LOCATION \
      --project PROJECT_ID \
      --format json | jq '.config.sandboxConfig'
    • type: "GVISOR" indicates the node pool is configured for GKE Sandbox.
  3. Decide whether to introduce or expand GKE Sandbox

    • If no node pool has "type": "GVISOR" and you have identified untrusted workloads, decide to create at least one dedicated GKE Sandbox node pool.
    • If some node pools use GKE Sandbox, decide whether to migrate additional untrusted workloads to those or to new sandboxed pools.
    • Consider operational impacts: performance overhead, supported OS/image (cos_containerd), and compatibility of privileged / hostPath workloads (these generally cannot use gVisor).
  4. Create a GKE Sandbox node pool (if needed)

    • On any machine with gcloud access, create a new sandboxed node pool following the benchmark remediation:
      gcloud container node-pools create NODE_POOL_NAME \
      --location LOCATION \
      --cluster CLUSTER_NAME \
      --project PROJECT_ID \
      --image-type=cos_containerd \
      --sandbox="type=gvisor"
    • Optionally, add labels/taints to target only untrusted workloads, for example:
      gcloud container node-pools create NODE_POOL_NAME \
      --location LOCATION \
      --cluster CLUSTER_NAME \
      --project PROJECT_ID \
      --image-type=cos_containerd \
      --sandbox="type=gvisor" \
      --node-labels=workload=isolation-untrusted \
      --node-taints=workload=isolation-untrusted:NoSchedule
  5. Schedule untrusted workloads onto GKE Sandbox nodes

    • On any machine with kubectl access, update the Pod/Deployment specs of untrusted workloads to use node selectors and/or tolerations matching the sandbox node pool, for example:
      spec:
      nodeSelector:
      workload: isolation-untrusted
      tolerations:
      - key: "workload"
      operator: "Equal"
      value: "isolation-untrusted"
      effect: "NoSchedule"
    • Apply updated manifests:
      kubectl apply -f UPDATED_MANIFEST.yaml
    • Ensure no privileged or incompatible workloads are targeted to gVisor nodes.
  6. Verify GKE Sandbox is enabled and in use

    • Re-run the audit for the sandbox node pool(s):
      gcloud container node-pools describe NODE_POOL_NAME \
      --cluster CLUSTER_NAME \
      --location LOCATION \
      --project PROJECT_ID \
      --format json | jq '.config.sandboxConfig'
    • On any machine with kubectl access, confirm untrusted Pods are running on nodes from the sandbox pool:
      kubectl get pods -A -o wide | grep NODE_POOL_NAME
    • Optionally, list nodes and confirm labels/taints are correctly set:
      kubectl get nodes --show-labels
      kubectl describe node NODE_NAME | grep -i taints -A2
Using kubectl

kubectl cannot enable GKE Sandbox, because this setting is configured at the managed control-plane / node pool level via the Google Cloud Console, gcloud CLI, or IaC, not through Kubernetes API objects. Refer to the Manual Steps section for how to create or update node pools with GKE Sandbox (gVisor) enabled.

Automation
#!/usr/bin/env bash
#
# Report GKE Sandbox (gVisor) status for all node pools in all clusters in a project.
# REQUIREMENTS:
# - gcloud CLI authenticated and configured
# - jq installed
#
# USAGE:
# PROJECT_ID="my-project" ./check_gke_sandbox.sh
#

set -euo pipefail

PROJECT_ID="${PROJECT_ID:-}"

if [[ -z "${PROJECT_ID}" ]]; then
echo "ERROR: Set PROJECT_ID environment variable." >&2
exit 1
fi

echo "Project: ${PROJECT_ID}"
echo "Time: $(date -Iseconds)"
echo

# List all clusters (both zonal and regional)
clusters_json="$(gcloud container clusters list \
--project "${PROJECT_ID}" \
--format='json(name,location,endpoint,resourceLabels,loggingService,monitoringService)')"

if [[ "$(echo "${clusters_json}" | jq 'length')" -eq 0 ]]; then
echo "No GKE clusters found in project ${PROJECT_ID}."
exit 0
fi

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')"

echo "================================================================================"
echo "Cluster: ${CLUSTER_NAME}"
echo "Location: ${LOCATION}"
echo

# List node pools for this cluster
node_pools_json="$(gcloud container node-pools list \
--cluster "${CLUSTER_NAME}" \
--location "${LOCATION}" \
--project "${PROJECT_ID}" \
--format='json(name,config.imageType,config.sandboxConfig)')"

if [[ "$(echo "${node_pools_json}" | jq 'length')" -eq 0 ]]; then
echo " No node pools found."
echo
continue
fi

echo " Node pools:"
echo "${node_pools_json}" | jq -r '.[] | @base64' | while read -r np_b64; do
_npjq() { echo "${np_b64}" | base64 --decode | jq -r "${1}"; }

NP_NAME="$(_npjq '.name')"
IMAGE_TYPE="$(_npjq '.config.imageType // "UNKNOWN"')"
SANDBOX_TYPE="$(_npjq '.config.sandboxConfig.type // "NONE"')"

# Simple classification
if [[ "${SANDBOX_TYPE}" == "GVISOR" ]]; then
STATUS="SANDBOX_ENABLED"
else
STATUS="SANDBOX_DISABLED"
fi

echo " - nodePool: ${NP_NAME}"
echo " imageType: ${IMAGE_TYPE}"
echo " sandboxType: ${SANDBOX_TYPE}"
echo " status: ${STATUS}"
done

echo
done

cat <<'EOF'

INTERPRETING RESULTS
--------------------
For each node pool:

- sandboxType == "GVISOR" and status == "SANDBOX_ENABLED"
=> GKE Sandbox (gVisor) is enabled for that node pool.

- sandboxType == "NONE" or sandboxType is empty and status == "SANDBOX_DISABLED"
=> GKE Sandbox is NOT enabled for that node pool.

WHAT INDICATES A PROBLEM
------------------------
- If you have workloads that are untrusted, multi-tenant, or high-risk and they
are scheduled on node pools where:
- sandboxType is "NONE" (or absent), i.e., SANDBOX_DISABLED, and
- imageType is NOT explicitly using sandbox (no gVisor),
then this is a potential finding against CIS GKE 5.10.3.

Next steps must be decided manually:
- Identify which workloads are untrusted/risky.
- Ensure those workloads are scheduled onto node pools with sandboxType "GVISOR".
- Consider creating dedicated gVisor-enabled node pools and using node selectors
/ taints / tolerations to isolate untrusted workloads onto them.
EOF