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
Remediation​
Manual Steps
-
Identify untrusted / risky workloads and namespaces
- On any machine with
kubectlaccess, list namespaces and deployments to find workloads that run untrusted code, multi-tenant apps, user-supplied images, or high-risk services:kubectl get nskubectl get deploy --all-namespaces -o wide - Document which namespaces/workloads should be isolated with GKE Sandbox.
- On any machine with
-
Check current Sandbox (gVisor) configuration on all node pools
- On any machine with
gcloudaccess, 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.
- On any machine with
-
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).
- If no node pool has
-
Create a GKE Sandbox node pool (if needed)
- On any machine with
gcloudaccess, 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
- On any machine with
-
Schedule untrusted workloads onto GKE Sandbox nodes
- On any machine with
kubectlaccess, 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-untrustedtolerations:- 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.
- On any machine with
-
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
kubectlaccess, 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-labelskubectl describe node NODE_NAME | grep -i taints -A2
- Re-run the audit for the sandbox node pool(s):
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