Containers Should Disallow Privilege Escalation
More Info:
Verifies allowPrivilegeEscalation is false on every container. It defaults to true, letting a process gain more privileges than its parent.
Risk Level
High
Address
Security
Compliance Standards
- Cloudanix Best Practice
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Identify noncompliant Pods (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.containers // []) + (.spec.initContainers // []))[]| (.securityContext.allowPrivilegeEscalation == false) as $ok| select($ok | not)| "ns=\($m.namespace) pod=\($m.name) container=\(.name) image=\(.image)"][]' -
For a stand-alone Pod (no higher-level controller), edit its spec to set
allowPrivilegeEscalation: false(any machine with kubectl access):kubectl -n <NAMESPACE> edit pod <POD_NAME>Under each container (and
initContainersif present), add or change:securityContext:allowPrivilegeEscalation: falseSave and exit to apply the change (the Pod may be recreated depending on your setup).
-
For a Pod managed by a controller (Deployment, DaemonSet, StatefulSet, Job, CronJob), find the controller (any machine with kubectl access):
kubectl -n <NAMESPACE> get pod <POD_NAME> -o jsonpath='{.metadata.ownerReferences[0].kind}{" "}{.metadata.ownerReferences[0].name}{"\n"}' -
Edit the controller’s Pod template to set
allowPrivilegeEscalation: falsefor every container and initContainer (any machine with kubectl access). Example for a Deployment:kubectl -n <NAMESPACE> edit deployment <DEPLOYMENT_NAME>In
.spec.template.spec.containers[](and.spec.template.spec.initContainers[]if present), ensure:securityContext:allowPrivilegeEscalation: falseSave and exit; this will roll out new Pods with the updated setting.
-
For controllers of other kinds, use their specific edit commands (any machine with kubectl access) and apply the same
securityContextchange:# DaemonSetkubectl -n <NAMESPACE> edit daemonset <DAEMONSET_NAME># StatefulSetkubectl -n <NAMESPACE> edit statefulset <STATEFULSET_NAME># Jobkubectl -n <NAMESPACE> edit job <JOB_NAME># CronJob (edit the job template)kubectl -n <NAMESPACE> edit cronjob <CRONJOB_NAME>Add
securityContext.allowPrivilegeEscalation: falseto every container and initContainer spec in the pod template. -
Verify all Pods are now compliant (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.containers // []) + (.spec.initContainers // []))[]| (.securityContext.allowPrivilegeEscalation == 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)"+ " allowPrivilegeEscalation=\(.securityContext.allowPrivilegeEscalation // "unset")"+ " is_compliant=\(if $ok then "true" else "false" end)"] as $rows| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'Confirm that either
is_compliant=trueis printed or all listed containers showallowPrivilegeEscalation=false is_compliant=true.
Using kubectl
On any machine with kubectl access:
- Identify non‑compliant Pods (example using the provided 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.containers // []) + (.spec.initContainers // []))[]
| select(.securityContext.allowPrivilegeEscalation != false)
| "ns=\($m.namespace) pod=\($m.name) container=\(.name)"
][]'
- For each workload controller (Deployment, StatefulSet, DaemonSet, Job, CronJob, etc.) that creates the reported Pods, patch the Pod template to set
allowPrivilegeEscalation: falseon all containers.
Example: Deployment
kubectl -n NAMESPACE edit deployment DEPLOYMENT_NAME
In .spec.template.spec.containers[].securityContext (and .initContainers[] if present), ensure:
securityContext:
allowPrivilegeEscalation: false
Save and exit; Kubernetes will roll out updated Pods.
If you prefer a one‑shot patch (single container example):
kubectl -n NAMESPACE patch deployment DEPLOYMENT_NAME \
--type='json' \
-p='[
{
"op": "add",
"path": "/spec/template/spec/containers/0/securityContext",
"value": { "allowPrivilegeEscalation": false }
}
]'
Repeat with the appropriate resource kind/name and container index for each non‑compliant controller (StatefulSet, DaemonSet, Job, CronJob).
- Do not edit Pods that are directly managed by controllers; instead always edit the owning controller. For standalone Pods you manage directly:
kubectl -n NAMESPACE get pod POD_NAME -o yaml > /tmp/pod-POD_NAME.yaml
Edit /tmp/pod-POD_NAME.yaml so each container has:
securityContext:
allowPrivilegeEscalation: false
Then recreate:
kubectl -n NAMESPACE delete pod POD_NAME
kubectl -n NAMESPACE apply -f /tmp/pod-POD_NAME.yaml
- Verification (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.containers // []) + (.spec.initContainers // []))[]
| (.securityContext.allowPrivilegeEscalation == false) as $ok
| select($ok | not)
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Automation
#!/usr/bin/env bash
#
# Enforce securityContext.allowPrivilegeEscalation=false on all containers
# in non-system namespaces by patching owning controllers, not Pods.
#
# Requirements:
# - Run on any machine with kubectl access and current-context set
# - kubectl, jq, and yq (https://github.com/mikefarah/yq) must be installed
#
# Idempotent: re-running will keep allowPrivilegeEscalation=false and not break.
set -euo pipefail
# Fail fast if required tools are missing
for bin in kubectl jq yq; do
if ! command -v "$bin" >/dev/null 2>&1; then
echo "ERROR: '$bin' is required but not found in PATH" >&2
exit 1
fi
done
echo "=== Discovering non-system namespaces with non-compliant Pods ==="
# Get all Pod records as JSON, excluding core system namespaces
PODS_JSON="$(kubectl get pods --all-namespaces -o json)"
# Identify owning controllers (kind, name, namespace, apiVersion) for non-compliant containers
# We skip kube-system, kube-public, kube-node-lease as per the audit.
CONTROLLERS_JSON="$(
echo "$PODS_JSON" | jq -r '
[
.items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| select($own != null) # only pods with a controller
| .spec as $spec
| ($spec.containers // [] + $spec.initContainers // []) as $allc
| select( any($allc[]?; (.securityContext.allowPrivilegeEscalation // true) != false) )
| {
apiVersion: $own.apiVersion,
kind: $own.kind,
name: $own.name,
namespace: $m.namespace
}
]
| unique
'
)"
if [[ -z "$CONTROLLERS_JSON" || "$CONTROLLERS_JSON" == "[]" ]]; then
echo "No non-compliant controller-owned Pods found outside core system namespaces."
else
echo "=== Patching controllers to set allowPrivilegeEscalation=false ==="
echo "$CONTROLLERS_JSON" | jq -c '.[]' | while read -r ctrl; do
apiVersion=$(echo "$ctrl" | jq -r '.apiVersion')
kind=$(echo "$ctrl" | jq -r '.kind')
name=$(echo "$ctrl" | jq -r '.name')
namespace=$(echo "$ctrl" | jq -r '.namespace')
echo "Processing ${kind}/${namespace}/${name}"
# Fetch full controller YAML
tmpfile="$(mktemp)"
kubectl get "$kind" "$name" -n "$namespace" -o yaml > "$tmpfile"
# Patch containers and initContainers: ensure securityContext.allowPrivilegeEscalation=false
# while preserving any existing securityContext fields.
yq -i '
.spec.template.spec.containers //= [] |
.spec.template.spec.initContainers //= [] |
(.spec.template.spec.containers[] |= (
.securityContext //= {} |
.securityContext.allowPrivilegeEscalation = false
)) |
(.spec.template.spec.initContainers[] |= (
.securityContext //= {} |
.securityContext.allowPrivilegeEscalation = false
))
' "$tmpfile"
# Apply the patched controller
kubectl apply -n "$namespace" -f "$tmpfile" >/dev/null
rm -f "$tmpfile"
done
fi
echo "=== Patching standalone Pods (no controller owner) ==="
# Handle Pods that are not owned by a controller (directly created Pods).
STANDALONE_PODS_JSON="$(
echo "$PODS_JSON" | jq -r '
[
.items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| select($own == null)
| .spec as $spec
| ($spec.containers // [] + $spec.initContainers // []) as $allc
| select( any($allc[]?; (.securityContext.allowPrivilegeEscalation // true) != false) )
| { name: $m.name, namespace: $m.namespace }
]
| unique
'
)"
if [[ -z "$STANDALONE_PODS_JSON" || "$STANDALONE_PODS_JSON" == "[]" ]]; then
echo "No non-compliant standalone Pods found outside core system namespaces."
else
echo "$STANDALONE_PODS_JSON" | jq -c '.[]' | while read -r pod; do
pod_name=$(echo "$pod" | jq -r '.name')
pod_ns=$(echo "$pod" | jq -r '.namespace')
echo "Patching Pod/${pod_ns}/${pod_name}"
tmpfile="$(mktemp)"
kubectl get pod "$pod_name" -n "$pod_ns" -o yaml > "$tmpfile"
# Note: editing a running Pod spec directly will recreate it if it is not controlled
# by a higher-level object; for an existing Pod, kubectl replace will delete/recreate.
# This has operational impact: the Pod will be restarted.
yq -i '
.spec.containers //= [] |
.spec.initContainers //= [] |
(.spec.containers[] |= (
.securityContext //= {} |
.securityContext.allowPrivilegeEscalation = false
)) |
(.spec.initContainers[] |= (
.securityContext //= {} |
.securityContext.allowPrivilegeEscalation = false
))
' "$tmpfile"
kubectl replace -n "$pod_ns" -f "$tmpfile" >/dev/null
rm -f "$tmpfile"
done
fi
echo "=== Verification (re-running audit) ==="
# Re-run the provided audit to confirm compliance
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.containers // []) + (.spec.initContainers // []))[]
| (.securityContext.allowPrivilegeEscalation == 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)"
+ " allowPrivilegeEscalation=\(.securityContext.allowPrivilegeEscalation // "unset")"
+ " 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[]
end
'
echo "=== Done ==="