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
HighAddress
SecurityCompliance Standards
- Cloudanix Best Practice
Triage and Remediation
- Remediation
Remediation
Manual Steps
Manual Steps
-
Identify the non-compliant pod(s) (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 | length) == 0 then "is_compliant=true" else $rows[] end' \ | grep 'is_compliant=false' -
If the pod is controlled by a higher-level object (Deployment, DaemonSet, etc.), get that manifest instead of editing the pod directly (run on any machine with kubectl access, adjust KIND, NAMESPACE, NAME as shown in the audit output
owner=field):kubectl get deployment YOUR_DEPLOYMENT_NAME -n YOUR_NAMESPACE -o yaml > /tmp/workload.yaml -
Edit the manifest to set
runAsNonRoot: trueat the pod levelsecurityContext(preferred) (edit/tmp/workload.yamlon the same machine):If you cannot use pod-level context (for example, only some containers must be non-root), set it per container instead:spec: template: spec: securityContext: runAsNonRoot: true containers: - name: YOUR_CONTAINER image: your-image # container-specific securityContext is optional if pod-level is setspec: template: spec: containers: - name: YOUR_CONTAINER image: your-image securityContext: runAsNonRoot: true -
Apply the updated manifest (run on any machine with kubectl access):
kubectl apply -f /tmp/workload.yaml -
For standalone Pods not managed by a controller, export, modify, and recreate them (run on any machine with kubectl access, replace NAMESPACE and POD_NAME):
kubectl get pod POD_NAME -n NAMESPACE -o yaml --export=false > /tmp/pod.yaml # Edit /tmp/pod.yaml similarly to step 3 under .spec.securityContext or .spec.containers[].securityContext # Remove status section to avoid server-side rejection: yq -i 'del(.status)' /tmp/pod.yaml kubectl delete pod POD_NAME -n NAMESPACE kubectl apply -f /tmp/pod.yaml -
Verify all containers now run as non-root (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.securityContext.runAsNonRoot // false) as $podNonRoot | ((.spec.containers // []) + (.spec.initContainers // []))[] | ($podNonRoot or (.securityContext.runAsNonRoot // false)) as $ok | "runAsNonRoot=\($ok) is_compliant=\(if $ok then "true" else "false" end)" ] as $rows | if ($rows | map(select(test("is_compliant=false"))) | length) == 0 then "is_compliant=true" else $rows[] end'
Using kubectl
Using kubectl
On any machine with kubectl access:
- Identify the non-compliant pod (example name/namespace used below; substitute your own from the audit output):
kubectl get pod myapp-pod -n myapp-namespace -o yaml > /tmp/myapp-pod.yaml
- Edit the manifest to add a pod-level
securityContext.runAsNonRoot: true. In/tmp/myapp-pod.yaml, underspec:, add:
spec:
securityContext:
runAsNonRoot: true
containers:
- name: myapp-container
image: myrepo/myimage:tag
# existing fields...
- Delete and recreate the pod from the edited manifest (works only for pods not managed by a controller like Deployment/ReplicaSet/DaemonSet; for those, edit the controller instead—see Manual Steps section):
kubectl delete pod myapp-pod -n myapp-namespace
kubectl apply -f /tmp/myapp-pod.yaml
- Verification (same scope as the audit):
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
Automation
#!/usr/bin/env bash
#
# Enforce securityContext.runAsNonRoot=true on all non-system Pods in an EKS cluster.
# Scope: any machine with kubectl access and jq installed.
# Safe to re-run: patches only Pods/Workloads that lack runAsNonRoot=true.
#
# NOTE:
# - This patches higher-level controllers (Deployments, StatefulSets, DaemonSets, ReplicaSets, Jobs, CronJobs)
# when possible so that recreated Pods inherit the setting.
# - Standalone Pods are patched directly.
# - Only user namespaces (excluding kube-system, kube-public, kube-node-lease) are touched.
set -euo pipefail
# Fail fast if required tools are missing
for bin in kubectl jq; do
if ! command -v "${bin}" >/dev/null 2>&1; then
echo "ERROR: ${bin} is required but not installed or not in PATH." >&2
exit 1
fi
done
echo "Discovering non-compliant Pods (runAsNonRoot != true)..."
# Capture the raw JSON once to avoid race conditions and multiple API calls
POD_JSON="$(kubectl get pods --all-namespaces -o json)"
# Function: list non-compliant pod records as JSON lines with key metadata
list_non_compliant_pods() {
echo "${POD_JSON}" | jq -c '
.items[]
| select(.metadata.namespace as $n
| ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| . as $pod
| .spec as $spec
| .metadata as $m
| (.spec.securityContext.runAsNonRoot // false) as $podNonRoot
| ((.spec.containers // []) + (.spec.initContainers // [])) as $allContainers
| any($allContainers[]?; .securityContext.runAsNonRoot // false | not) as $anyMissing
| ($podNonRoot | not or $anyMissing) as $isNonCompliant
| select($isNonCompliant)
| {
namespace: $m.namespace,
name: $m.name,
uid: $m.uid,
owner: (
([ ($m.ownerReferences // [])[] | select(.controller) ] | first) // null
)
}'
}
NON_COMPLIANT="$(list_non_compliant_pods || true)"
if [[ -z "${NON_COMPLIANT}" ]]; then
echo "All user Pods are already compliant (runAsNonRoot=true)."
else
echo "Non-compliant Pods found; attempting to patch owning controllers where possible..."
# Helper: patch a workload template at spec.template.spec.securityContext.runAsNonRoot
patch_workload_template() {
local kind ns name
kind="$1"; ns="$2"; name="$3"
# Check current workload object
if ! kubectl get "${kind}" "${name}" -n "${ns}" -o json >/tmp/workload.json 2>/dev/null; then
echo " - WARNING: ${kind}/${ns}/${name} not found; skipping."
return
fi
# Determine if already compliant
if jq -e '
.spec.template.spec.securityContext.runAsNonRoot == true
' /tmp/workload.json >/dev/null 2>&1; then
echo " - ${kind}/${ns}/${name} already has runAsNonRoot=true; skipping."
return
fi
echo " - Patching ${kind}/${ns}/${name} to set spec.template.spec.securityContext.runAsNonRoot=true"
# Build a strategic merge patch that preserves other fields
kubectl patch "${kind}" "${name}" -n "${ns}" \
--type merge \
-p '{
"spec": {
"template": {
"spec": {
"securityContext": {
"runAsNonRoot": true
}
}
}
}
}' >/dev/null
}
# Helper: patch a standalone Pod (no controller owner) at spec.securityContext.runAsNonRoot
patch_standalone_pod() {
local ns name
ns="$1"; name="$2"
# Check current pod object
if ! kubectl get pod "${name}" -n "${ns}" -o json >/tmp/pod.json 2>/dev/null; then
echo " - WARNING: Pod/${ns}/${name} not found; skipping."
return
fi
if jq -e '.spec.securityContext.runAsNonRoot == true' /tmp/pod.json >/dev/null 2>&1; then
echo " - Pod/${ns}/${name} already has runAsNonRoot=true; skipping."
return
fi
echo " - Patching Pod/${ns}/${name} to set spec.securityContext.runAsNonRoot=true"
kubectl patch pod "${name}" -n "${ns}" \
--type merge \
-p '{
"spec": {
"securityContext": {
"runAsNonRoot": true
}
}
}' >/dev/null
}
# Track which workload objects we have already patched to keep the script idempotent and efficient
declare -A PATCHED_WORKLOADS=()
# Iterate over non-compliant pods
while IFS= read -r line; do
ns="$(echo "${line}" | jq -r '.namespace')"
name="$(echo "${line}" | jq -r '.name')"
ownerKind="$(echo "${line}" | jq -r '.owner.kind // ""')"
ownerName="$(echo "${line}" | jq -r '.owner.name // ""')"
if [[ -n "${ownerKind}" && -n "${ownerName}" ]]; then
# Standardize ownerKind to known workload Kinds we can patch
case "${ownerKind}" in
Deployment|StatefulSet|DaemonSet|ReplicaSet|Job|CronJob)
key="${ownerKind}:${ns}:${ownerName}"
if [[ -z "${PATCHED_WORKLOADS[${key}]+x}" ]]; then
PATCHED_WORKLOADS["${key}"]=1
patch_workload_template "${ownerKind}" "${ns}" "${ownerName}"
else
echo " - ${ownerKind}/${ns}/${ownerName} already patched in this run; skipping."
fi
;;
*)
echo " - Owner ${ownerKind}/${ns}/${ownerName} is not a patchable workload type; patching Pod directly."
patch_standalone_pod "${ns}" "${name}"
;;
esac
else
echo " - Pod/${ns}/${name} has no controller owner; patching Pod directly."
patch_standalone_pod "${ns}" "${name}"
fi
done <<< "${NON_COMPLIANT}"
fi
echo
echo "Waiting for Pods to be updated and recreated where necessary..."
# Optional small wait; can be adjusted or removed
sleep 5
echo "Re-running compliance check to verify runAsNonRoot is now true on all non-excluded 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
| "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[] | select(test("is_compliant=false$")) ] | length ) == 0
then "is_compliant=true"
else $rows[] end
'

