Containers Should Run As Non-Root
More Info:​
Verifies runAsNonRoot is set at pod or container level. Running as root inside a container widens the impact of a container escape.
Risk Level​
High
Address​
Security
Compliance Standards​
- Cloudanix Best Practice
Triage and Remediation​
- Remediation
Remediation​
Manual Steps
-
Identify non-compliant pods (run on any machine with kubectl access):
kubectl get pods --all-namespaces -o json | jq -r '[ .items[]| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)| .metadata as $m| (.spec.nodeName // "") as $node| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own| (.spec.securityContext.runAsNonRoot // false) as $podNonRoot| ((.spec.containers // []) + (.spec.initContainers // []))[]| ($podNonRoot or (.securityContext.runAsNonRoot // false)) as $ok| select($ok | not)| "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)+ (if $node == "" then "" else " node=\($node)" end)+ (if $labels == "" then "" else " labels=\($labels)" end)+ (if $own == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)+ " container=\(.name) image=\(.image) runAsNonRoot=\($ok)"+ " is_compliant=false"][]' -
For standalone Pods (not controlled by a higher-level object), export, edit, and re-apply the manifest (run on any machine with kubectl access). Example for one pod:
kubectl -n NAMESPACE get pod POD_NAME -o yaml > /tmp/pod-POD_NAME.yamlEdit
/tmp/pod-POD_NAME.yamland add or adjust at the pod spec level:spec:securityContext:runAsNonRoot: trueor, if you cannot set it at pod level, add it to every container and initContainer:
spec:containers:- name: appimage: your-imagesecurityContext:runAsNonRoot: trueinitContainers:- name: initimage: your-init-imagesecurityContext:runAsNonRoot: trueThen re-create the pod (Pods are immutable):
kubectl -n NAMESPACE delete pod POD_NAMEkubectl -n NAMESPACE apply -f /tmp/pod-POD_NAME.yaml -
For pods owned by a controller (Deployment/ReplicaSet/StatefulSet/DaemonSet/Job/CronJob), edit the controller so all future pods are compliant (run on any machine with kubectl access). Example for a Deployment:
kubectl -n NAMESPACE edit deployment DEPLOYMENT_NAMEIn the editor, under
spec.template.spec, set:spec:template:spec:securityContext:runAsNonRoot: trueIf needed, also set on each container:
spec:template:spec:containers:- name: appimage: your-imagesecurityContext:runAsNonRoot: trueSave and exit; Kubernetes will roll out updated pods.
-
Repeat step 3 for other controllers (StatefulSet, DaemonSet, Job, CronJob) that own non-compliant pods, using
kubectl editon the appropriate resource type and settingspec.template.spec.securityContext.runAsNonRoot: trueor per-containersecurityContext.runAsNonRoot: true. -
If any image fails to start with
runAsNonRoot: true, review that image on your image build system: ensure the container’s default user is non-root (e.g., via aUSERdirective in the Dockerfile) or explicitly set a non-rootrunAsUsertogether withrunAsNonRoot: truein the pod spec, then redeploy. -
Verify compliance (run on any machine with kubectl access):
kubectl get pods --all-namespaces -o json | jq -r '[ .items[]| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)| .metadata as $m| (.spec.nodeName // "") as $node| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own| (.spec.securityContext.runAsNonRoot // false) as $podNonRoot| ((.spec.containers // []) + (.spec.initContainers // []))[]| ($podNonRoot or (.securityContext.runAsNonRoot // false)) as $ok| "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"+ " container=\(.name) image=\(.image) runAsNonRoot=\($ok)"+ " is_compliant=\(if $ok then "true" else "false" end)"] as $rows| if ($rows | map(select(. | contains("is_compliant=false"))) | length) == 0then "is_compliant=true"else $rows[]end'
Using kubectl
On any machine with kubectl access:
- Identify non-compliant Pods
kubectl get pods --all-namespaces -o json | jq -r '
[ .items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (.spec.nodeName // "") as $node
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| (.spec.securityContext.runAsNonRoot // false) as $podNonRoot
| ((.spec.containers // []) + (.spec.initContainers // []))[]
| ($podNonRoot or (.securityContext.runAsNonRoot // false)) as $ok
| select($ok|not)
| "ns=\($m.namespace) name=\($m.name)"
] | unique[]'
- Edit each affected workload manifest and set pod-level
runAsNonRoot: true(preferred). For example, for a Deployment owning the Pod:
kubectl -n NAMESPACE edit deployment DEPLOYMENT_NAME
Under spec.template.spec, ensure:
spec:
template:
spec:
securityContext:
runAsNonRoot: true
If you cannot set it at pod level (e.g., mixed containers), set it on each container instead:
spec:
template:
spec:
containers:
- name: app
securityContext:
runAsNonRoot: true
initContainers:
- name: init
securityContext:
runAsNonRoot: true
Apply the same pattern to other controllers (StatefulSet, DaemonSet, Job, CronJob) using:
kubectl -n NAMESPACE edit statefulset STATEFULSET_NAME
kubectl -n NAMESPACE edit daemonset DAEMONSET_NAME
kubectl -n NAMESPACE edit job JOB_NAME
kubectl -n NAMESPACE edit cronjob CRONJOB_NAME
For standalone Pods managed directly (not recommended in GKE production), edit and re-apply:
kubectl -n NAMESPACE get pod POD_NAME -o yaml > /tmp/pod.yaml
Edit /tmp/pod.yaml to include:
spec:
securityContext:
runAsNonRoot: true
Then delete and recreate:
kubectl -n NAMESPACE delete pod POD_NAME
kubectl -n NAMESPACE apply -f /tmp/pod.yaml
- Verification
Run the benchmark audit command again from any machine with kubectl access:
kubectl get pods --all-namespaces -o json | jq -r '
[ .items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (.spec.nodeName // "") as $node
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| (.spec.securityContext.runAsNonRoot // false) as $podNonRoot
| ((.spec.containers // []) + (.spec.initContainers // []))[]
| ($podNonRoot or (.securityContext.runAsNonRoot // false)) as $ok
| "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $node == "" then "" else " node=\($node)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ (if $own == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
+ " container=\(.name) image=\(.image) runAsNonRoot=\($ok)"
+ " is_compliant=\(if $ok then "true" else "false" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Automation
#!/usr/bin/env bash
# Remediates: Ensure Pods/containers set securityContext.runAsNonRoot: true
# Scope: any machine with kubectl access to the cluster
# Requirements: kubectl, jq, yq (v4+)
set -euo pipefail
# ---- Config ----
# Namespaces to exclude (system namespaces managed by GKE)
EXCLUDED_NS_REGEX='^(kube-system|kube-public|kube-node-lease)$'
# ---- Functions ----
need_bin() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "ERROR: '$1' is required but not found in PATH" >&2
exit 1
fi
}
# Return 0 if the resource kind is namespaced
is_namespaced_kind() {
local kind="$1"
case "$kind" in
Pod|Deployment|ReplicaSet|StatefulSet|DaemonSet|Job|CronJob)
return 0
;;
*)
return 1
;;
esac
}
# Patch a single workload manifest to enforce runAsNonRoot
patch_manifest_run_as_non_root() {
local file="$1"
# 1) ensure pod-level securityContext.runAsNonRoot: true
# 2) ensure every (init)container has securityContext.runAsNonRoot: true
yq eval '
.spec.template.spec |= (
.securityContext //= {} |
.securityContext.runAsNonRoot = true |
(.containers // []) |=
map(.securityContext //= {} | .securityContext.runAsNonRoot = true) |
(.initContainers // []) |=
map(.securityContext //= {} | .securityContext.runAsNonRoot = true)
)
' "$file"
}
# For plain Pod manifests (no template)
patch_pod_manifest_run_as_non_root() {
local file="$1"
yq eval '
.spec |= (
.securityContext //= {} |
.securityContext.runAsNonRoot = true |
(.containers // []) |=
map(.securityContext //= {} | .securityContext.runAsNonRoot = true) |
(.initContainers // []) |=
map(.securityContext //= {} | .securityContext.runAsNonRoot = true)
)
' "$file"
}
# ---- Pre-flight ----
need_bin kubectl
need_bin jq
need_bin yq
# ---- Discover non-compliant Pods ----
echo "Discovering non-compliant Pods (excluding kube-system, kube-public, kube-node-lease)..."
non_compliant_json="$(kubectl get pods --all-namespaces -o json | jq --arg re "$EXCLUDED_NS_REGEX" '
.items[]
| select(.metadata.namespace | test($re) | not)
| . as $pod
| (.spec.securityContext.runAsNonRoot // false) as $podNonRoot
| ((.spec.containers // []) + (.spec.initContainers // []))[]
| ($podNonRoot or (.securityContext.runAsNonRoot // false)) as $ok
| select($ok | not)
| {
pod_namespace: $pod.metadata.namespace,
pod_name: $pod.metadata.name,
container_name: .name,
owner: ([$pod.metadata.ownerReferences[]? | select(.controller)] | first // null)
}
' 2>/dev/null || true)"
if [[ -z "$non_compliant_json" ]]; then
echo "No Pods found."
exit 0
fi
non_compliant_count="$(echo "$non_compliant_json" | jq -s 'length')"
if [[ "$non_compliant_count" -eq 0 ]]; then
echo "All Pods are compliant."
else
echo "Found $non_compliant_count non-compliant container instances."
fi
# ---- Group by owner to identify manageable workloads ----
# We will patch only supported, namespaced controllers with server-side apply.
echo "Identifying parent workloads to patch..."
# Build list of unique (kind, namespace, name) for owners
owners_json="$(
echo "$non_compliant_json" | jq -s '
map(select(.owner != null))
| map({
kind: .owner.kind,
namespace: .pod_namespace,
name: .owner.name
})
| unique
'
)"
# Also collect standalone Pods (no controller) to patch or flag for manual fix
standalone_pods_json="$(
echo "$non_compliant_json" | jq -s '
map(select(.owner == null))
| map({
kind: "Pod",
namespace: .pod_namespace,
name: .pod_name
})
| unique
'
)"
echo "Workloads with non-compliant containers:"
echo "$owners_json" | jq -r '.[] | "\(.kind) \(.namespace)/\(.name)"' 2>/dev/null || true
echo "Standalone Pods with non-compliant containers (may not be safe to patch automatically):"
echo "$standalone_pods_json" | jq -r '.[] | "\(.kind) \(.namespace)/\(.name)"' 2>/dev/null || true
# ---- Patch controllers (Deployments, etc.) via kubectl ----
echo "Patching parent workloads via kubectl..."
echo "$owners_json" | jq -c '.[]' | while read -r owner; do
kind="$(echo "$owner" | jq -r '.kind')"
ns="$(echo "$owner" | jq -r '.namespace')"
name="$(echo "$owner" | jq -r '.name')"
if ! is_namespaced_kind "$kind"; then
echo "Skipping unsupported owner kind: $kind $ns/$name"
continue
fi
echo "Processing $kind $ns/$name ..."
tmpfile="$(mktemp)"
trap 'rm -f "$tmpfile"' EXIT
# Export manifest
if ! kubectl get "$kind" "$name" -n "$ns" -o yaml > "$tmpfile"; then
echo " WARN: could not get $kind $ns/$name, skipping."
rm -f "$tmpfile"
trap - EXIT
continue
fi
# Patch manifest in-place, depending on kind
case "$kind" in
Pod)
patched="$(patch_pod_manifest_run_as_non_root "$tmpfile")"
;;
*)
patched="$(patch_manifest_run_as_non_root "$tmpfile")"
;;
esac
echo "$patched" > "$tmpfile"
# Apply with server-side apply (idempotent)
if ! kubectl apply -f "$tmpfile" --server-side --force-conflicts >/dev/null; then
echo " WARN: failed to apply patched manifest for $kind $ns/$name"
else
echo " Patched $kind $ns/$name"
fi
rm -f "$tmpfile"
trap - EXIT
done
# ---- Standalone Pods ----
# These are often ephemeral or manually managed. We only print guidance.
standalone_count="$(echo "$standalone_pods_json" | jq -s 'length' 2>/dev/null || echo 0)"
if [[ "$standalone_count" -gt 0 ]]; then
echo
echo "NOTE: Standalone Pods listed above are still non-compliant."
echo "To fix them, update their Pod specs or the process that creates them to set:"
echo " spec.securityContext.runAsNonRoot: true"
echo " spec.containers[].securityContext.runAsNonRoot: true"
fi
# ---- Verification ----
echo
echo "Re-running compliance audit to verify remediation..."
kubectl get pods --all-namespaces -o json | jq -r '
[ .items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (.spec.nodeName // "") as $node
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| (.spec.securityContext.runAsNonRoot // false) as $podNonRoot
| ((.spec.containers // []) + (.spec.initContainers // []))[]
| ($podNonRoot or (.securityContext.runAsNonRoot // false)) as $ok
| "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $node == "" then "" else " node=\($node)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ (if $own == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
+ " container=\(.name) image=\(.image) runAsNonRoot=\($ok)"
+ " is_compliant=\(if $ok then "true" else "false" end)"
] as $rows
| if ($rows | map(select(. | contains("is_compliant=false"))) | length) == 0
then "is_compliant=true"
else ($rows[] | select(. | contains("is_compliant=false")))
end
'