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
-
On any machine with kubectl access, list non-compliant pods using the audit command and capture the output for reference:
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' \| grep 'is_compliant=false' || true -
For each non-compliant pod that is managed by a higher-level controller (Deployment, StatefulSet, DaemonSet, Job, CronJob), identify its owner and edit that resource to set
runAsNonRoot: trueat the pod level. Example for a Deployment owner in namespacemy-namespacenamedmy-app(replace with actual values fromowner=in the audit output):kubectl -n my-namespace edit deployment my-appIn the opened manifest, under
spec.template.spec, add or update:spec:template:spec:securityContext:runAsNonRoot: trueSave and exit to apply the change.
-
If you prefer to patch instead of interactive edit, on any machine with kubectl access run a JSON patch for each affected controller. Example for the same Deployment (adjust kind/name/namespace per resource):
kubectl -n my-namespace patch deployment my-app \--type merge \-p '{"spec": {"template": {"spec": {"securityContext": {"runAsNonRoot": true}}}}}' -
For non-compliant pods that are not controlled by a higher-level resource (no
owner=field in the audit output), retrieve the current pod manifest and recreate it withrunAsNonRoot: trueset. On any machine with kubectl access:kubectl -n my-namespace get pod my-pod -o yaml > /tmp/my-pod.yamlEdit
/tmp/my-pod.yamland underspecadd:spec:securityContext:runAsNonRoot: trueThen delete and recreate the pod (it will not be automatically recreated because it has no controller):
kubectl -n my-namespace delete pod my-podkubectl -n my-namespace apply -f /tmp/my-pod.yaml -
If any container must explicitly override the pod-level setting, ensure each container and initContainer that should be non-root has
securityContext.runAsNonRoot: truedefined. In the relevant controller or pod manifest, under each container:spec:template:spec:containers:- name: my-containerimage: my-imagesecurityContext:runAsNonRoot: trueApply the updated manifest using:
kubectl -n my-namespace apply -f <updated-manifest>.yaml -
After changes have rolled out and pods are running with updated specs, verify compliance from any machine with kubectl access by re-running the audit command and confirming no
is_compliant=falselines remain: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' \| grep 'is_compliant=false' || echo "All checked pods are compliant (runAsNonRoot=true)."
Using kubectl
On any machine with kubectl access:
-
Identify non-compliant pods (from the audit output you already have) and note their controllers (Deployment/StatefulSet/DaemonSet/Job/CronJob) if present in the
owner=field. Always fix the controller, not the live Pod. -
Edit the owning controller manifest and add
runAsNonRoot: trueundersecurityContextat the pod level (preferred) or per container.
Example: Deployment (pod-level securityContext)
kubectl -n <namespace> get deploy <name> -o yaml > /tmp/deploy-nonroot.yaml
Edit /tmp/deploy-nonroot.yaml and under spec.template.spec add:
spec:
template:
spec:
securityContext:
runAsNonRoot: true
containers:
- name: <container-name>
image: <image>
# existing fields...
Apply:
kubectl apply -f /tmp/deploy-nonroot.yaml
Example: Deployment (container-level securityContext if you cannot set pod-level)
spec:
template:
spec:
containers:
- name: <container-name>
image: <image>
securityContext:
runAsNonRoot: true
- Repeat the same pattern for other controller types:
StatefulSet:
kubectl -n <namespace> get statefulset <name> -o yaml > /tmp/sts-nonroot.yaml
# edit as above, then:
kubectl apply -f /tmp/sts-nonroot.yaml
DaemonSet:
kubectl -n <namespace> get daemonset <name> -o yaml > /tmp/ds-nonroot.yaml
# edit as above, then:
kubectl apply -f /tmp/ds-nonroot.yaml
Job:
kubectl -n <namespace> get job <name> -o yaml > /tmp/job-nonroot.yaml
# edit under spec.template.spec, then:
kubectl apply -f /tmp/job-nonroot.yaml
CronJob:
kubectl -n <namespace> get cronjob <name> -o yaml > /tmp/cronjob-nonroot.yaml
# edit under spec.jobTemplate.spec.template.spec, then:
kubectl apply -f /tmp/cronjob-nonroot.yaml
- For standalone Pods (no
owner=in audit output), recreate them with a manifest that includesrunAsNonRoot: true:
kubectl -n <namespace> get pod <pod-name> -o yaml > /tmp/pod-nonroot.yaml
Edit /tmp/pod-nonroot.yaml to add:
spec:
securityContext:
runAsNonRoot: true
or per container:
spec:
containers:
- name: <container-name>
image: <image>
securityContext:
runAsNonRoot: true
Delete fields that must not be reused (status, metadata.uid, metadata.resourceVersion, metadata.creationTimestamp, metadata.managedFields), then:
kubectl delete pod -n <namespace> <pod-name>
kubectl apply -f /tmp/pod-nonroot.yaml
- Verification (on any machine with kubectl):
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
| select($ok | not)
] | if (length)==0 then "is_compliant=true" else . end'
Automation
#!/usr/bin/env bash
#
# Enforce runAsNonRoot=true on all non-system Pods in an AKS cluster
# Scope: any machine with kubectl access
#
# Behavior:
# - Skips kube-system, kube-public, kube-node-lease
# - Patches only Pods/containers that lack runAsNonRoot=true
# - Adds pod.spec.securityContext.runAsNonRoot=true when possible
# - If Pod already has a pod-level runAsUser or runAsGroup, you must
# review manually – this script will not change user IDs.
# - Safe to re-run (idempotent): patches only where needed.
set -euo pipefail
# Ensure dependencies
command -v kubectl >/dev/null 2>&1 || {
echo "kubectl not found in PATH" >&2
exit 1
}
command -v jq >/dev/null 2>&1 || {
echo "jq not found in PATH" >&2
exit 1
}
echo "Discovering non-compliant Pods (this may take a moment)..."
NON_COMPLIANT_JSON=$(
kubectl get pods --all-namespaces -o json | jq -c '
.items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
as $pod
| ($pod.spec.securityContext.runAsNonRoot // false) as $podNonRoot
| (($pod.spec.containers // []) + ($pod.spec.initContainers // [])) as $allContainers
| if ($allContainers | length) == 0 then empty else
# Container-level compliance check
($allContainers
| map(($podNonRoot or (.securityContext.runAsNonRoot // false)))
| all) as $all_ok
| if $all_ok and $podNonRoot then
empty # fully compliant
else
$pod # needs attention
end
end
'
)
if [[ -z "${NON_COMPLIANT_JSON}" ]]; then
echo "All relevant Pods are already compliant (runAsNonRoot=true)."
exit 0
fi
echo "Patching Pods to set spec.securityContext.runAsNonRoot=true where missing..."
# Helper: patch a single Pod to set pod-level runAsNonRoot=true
patch_pod_run_as_non_root() {
local ns="$1"
local name="$2"
# Get current pod.spec.securityContext (may be null)
local current_sc
current_sc="$(kubectl -n "${ns}" get pod "${name}" -o json | jq -c '.spec.securityContext')"
if [[ "${current_sc}" == "null" ]]; then
# No pod-level securityContext; create one with runAsNonRoot=true
kubectl -n "${ns}" patch pod "${name}" \
--type merge \
-p '{"spec":{"securityContext":{"runAsNonRoot":true}}}' >/dev/null
else
# Merge in runAsNonRoot=true preserving other fields
# jq is used client-side to prepare a merge patch
local patched
patched="$(jq -c '. + {"runAsNonRoot":true}' <<< "${current_sc}")"
# Apply as a merge patch to spec.securityContext
kubectl -n "${ns}" patch pod "${name}" \
--type merge \
-p "{\"spec\":{\"securityContext\":${patched}}}" >/dev/null
fi
}
# Iterate over non-compliant Pods and patch them
while IFS= read -r pod_json; do
ns=$(jq -r '.metadata.namespace' <<< "${pod_json}")
name=$(jq -r '.metadata.name' <<< "${pod_json}")
echo "Processing Pod ${ns}/${name}..."
# Inspect existing pod-level securityContext for manual review needs
pod_sc="$(jq -c '.spec.securityContext // {}' <<< "${pod_json}")"
has_run_as_user="$(jq -r 'has("runAsUser")' <<< "${pod_sc}")"
has_run_as_group="$(jq -r 'has("runAsGroup")' <<< "${pod_sc}")"
if [[ "${has_run_as_user}" == "true" || "${has_run_as_group}" == "true" ]]; then
echo " WARNING: Pod ${ns}/${name} has pod-level runAsUser/runAsGroup."
echo " Review this Pod manually to ensure the user is non-root."
echo " Skipping automatic patch for this Pod."
continue
fi
patch_pod_run_as_non_root "${ns}" "${name}"
echo " Patched: set spec.securityContext.runAsNonRoot=true"
done <<< "${NON_COMPLIANT_JSON}"
echo
echo "Re-running compliance audit to verify..."
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
| "ns=\($m.namespace) name=\($m.name) container=\(.name) runAsNonRoot=\($ok)"
+ " is_compliant=\(if $ok then "true" else "false" end)"
] as $rows
| if ($rows | all(contains("is_compliant=true"))) then
"is_compliant=true"
else
$rows[]
end
'
echo "Automation run completed."