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 all noncompliant 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.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=\(if .securityContext.allowPrivilegeEscalation == null then "unset" else .securityContext.allowPrivilegeEscalation end)"+ " 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' -
For Pods with a controller owner (Deployment/DaemonSet/StatefulSet/Job/CronJob), edit the controller manifest
- Pick one line from step 1 with
owner=Deployment/<ns>/<name>/...(or other controller kind) and note its kind and name. - Run on: any machine with kubectl access
# Example for a Deployment; replace <namespace> and <deployment-name> with actual valueskubectl -n <namespace> edit deployment <deployment-name>- In the editor, under each
containers:andinitContainers:entry, ensure asecurityContextwithallowPrivilegeEscalation: false, for example:
spec:template:spec:containers:- name: appimage: your-imagesecurityContext:allowPrivilegeEscalation: falseinitContainers:- name: initimage: your-init-imagesecurityContext:allowPrivilegeEscalation: false- Save and exit; Kubernetes will roll out updated Pods.
- Pick one line from step 1 with
-
For naked Pods without an owner, export and edit the Pod spec, then recreate it
- Pick a line from step 1 where
owner=is empty and note its namespace and name. - Run on: any machine with kubectl access
# Export the current Pod spec without statuskubectl -n <namespace> get pod <pod-name> -o yaml \| sed '/^status:/q' > /tmp/<pod-name>.yaml- Edit the file:
vi /tmp/<pod-name>.yaml- Under
.spec.containers[]and.spec.initContainers[], add or update:
securityContext:allowPrivilegeEscalation: false- Delete the existing Pod and recreate it from the edited manifest:
kubectl -n <namespace> delete pod <pod-name>kubectl -n <namespace> apply -f /tmp/<pod-name>.yaml - Pick a line from step 1 where
-
Repeat edits for all remaining noncompliant controllers and Pods
- Use the output from step 1 as the source of truth.
- For each unique
owner=...entry, repeat step 2. - For each line with no
owner=, repeat step 3.
-
(Optional hardening) Add a default policy to prevent new Pods without this setting
- Run on: any machine with kubectl access
- Example: a
PodSecurityadmission label enforcingrestrictedin a namespace (if not already using Pod Security or Policy Controller):
kubectl label namespace <namespace> pod-security.kubernetes.io/enforce=restricted --overwrite -
Verify remediation
- 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.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=\(if .securityContext.allowPrivilegeEscalation == null then "unset" else .securityContext.allowPrivilegeEscalation end)"+ " is_compliant=\(if $ok then "true" else "false" end)"] as $rows| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'- Confirm that the output is either
is_compliant=trueor contains no lines withis_compliant=false.
Using kubectl
On any machine with kubectl access:
-
Identify non‑compliant Pods (from the audit output) and, for each Pod, determine its controller (Deployment, StatefulSet, DaemonSet, Job, CronJob, etc.) from the
owner=field in the audit output. You must patch the controller, not the live Pod. -
Example: patch a Deployment to set
allowPrivilegeEscalation: falsefor all containers (including initContainers) using a declarative manifest.a. Export the existing Deployment spec:
kubectl get deployment <deployment-name> -n <namespace> -o yaml > /tmp/deployment-<deployment-name>.yamlb. Edit
/tmp/deployment-<deployment-name>.yamland, under everycontainers[]andinitContainers[]entry in.spec.template.spec, ensure:spec:template:spec:containers:- name: <container-name>image: <image>securityContext:allowPrivilegeEscalation: false# ...other fields...initContainers:- name: <init-container-name>image: <image>securityContext:allowPrivilegeEscalation: false# ...other fields...If
securityContextalready exists, just add/updateallowPrivilegeEscalation: falseunder it.c. Apply the updated manifest:
kubectl apply -f /tmp/deployment-<deployment-name>.yamlThis will trigger a rolling update and recreate Pods from this Deployment with the new setting.
-
Repeat the same export/edit/apply pattern for other controllers (DaemonSet, StatefulSet, Job, CronJob) that own non‑compliant Pods:
# DaemonSetkubectl get daemonset <daemonset-name> -n <namespace> -o yaml > /tmp/daemonset-<daemonset-name>.yamlkubectl apply -f /tmp/daemonset-<daemonset-name>.yaml# StatefulSetkubectl get statefulset <statefulset-name> -n <namespace> -o yaml > /tmp/statefulset-<statefulset-name>.yamlkubectl apply -f /tmp/statefulset-<statefulset-name>.yaml# Jobkubectl get job <job-name> -n <namespace> -o yaml > /tmp/job-<job-name>.yamlkubectl apply -f /tmp/job-<job-name>.yaml# CronJob (edit the Pod template under spec.jobTemplate.spec.template.spec.*)kubectl get cronjob <cronjob-name> -n <namespace> -o yaml > /tmp/cronjob-<cronjob-name>.yamlkubectl apply -f /tmp/cronjob-<cronjob-name>.yaml -
For standalone Pods without an owning controller (no
owner=in the audit output), recreate them from a manifest that explicitly setsallowPrivilegeEscalation: falseon every container and init container:kubectl get pod <pod-name> -n <namespace> -o yaml > /tmp/pod-<pod-name>.yamlEdit
/tmp/pod-<pod-name>.yamlto add/update:spec:containers:- name: <container-name>image: <image>securityContext:allowPrivilegeEscalation: falseinitContainers:- name: <init-container-name>image: <image>securityContext:allowPrivilegeEscalation: falseThen delete and recreate:
kubectl delete pod <pod-name> -n <namespace>kubectl apply -f /tmp/pod-<pod-name>.yaml -
Verification (same 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=\(if .securityContext.allowPrivilegeEscalation == null then "unset" else .securityContext.allowPrivilegeEscalation end)"+ " is_compliant=\(if $ok then "true" else "false" end)"] as $rows| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'Confirm all reported containers show
allowPrivilegeEscalation=falseandis_compliant=true(or onlyis_compliant=trueis printed).
Automation
#!/usr/bin/env bash
# Remediate: Set securityContext.allowPrivilegeEscalation=false on all containers in non-system namespaces.
# Requirements: kubectl, jq, yq (v4+). Run on any machine with kubectl access.
set -euo pipefail
BACKUP_DIR="./pod-allowPrivilegeEscalation-backups-$(date +%Y%m%d%H%M%S)"
mkdir -p "${BACKUP_DIR}"
echo "Identifying noncompliant Pods..."
NONCOMPLIANT_FILE="${BACKUP_DIR}/noncompliant-pods.txt"
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)
| . as $pod
| ((.spec.containers // []) + (.spec.initContainers // []))[]
| select(.securityContext.allowPrivilegeEscalation != false)
| "\($pod.metadata.namespace) \($pod.metadata.name)"
' | sort -u > "${NONCOMPLIANT_FILE}" || true
if [[ ! -s "${NONCOMPLIANT_FILE}" ]]; then
echo "No noncompliant Pods found. Nothing to do."
else
echo "Noncompliant Pods:"
cat "${NONCOMPLIANT_FILE}"
fi
# Function: patch a single Pod manifest YAML on stdin
patch_pod_yaml() {
yq eval '
.spec.containers |= (map(
.securityContext.allowPrivilegeEscalation = false
))
|
( .spec.initContainers // [] ) as $ic
| if ($ic | length) > 0 then
.spec.initContainers |= (map(
.securityContext.allowPrivilegeEscalation = false
))
else
.
end
' -
}
while read -r NS NAME; do
[[ -z "${NS}" || -z "${NAME}" ]] && continue
echo "Processing Pod ${NS}/${NAME}..."
# Backup original
kubectl get pod "${NAME}" -n "${NS}" -o yaml > "${BACKUP_DIR}/${NS}__${NAME}.yaml"
# Get owning controller (if any)
OWNER_KIND=""
OWNER_NAME=""
OWNER_APIVERSION=""
read -r OWNER_KIND OWNER_NAME OWNER_APIVERSION < <(
kubectl get pod "${NAME}" -n "${NS}" -o json | jq -r '
.metadata.ownerReferences // []
| map(select(.controller)) | .[0] // empty
| "\(.kind) \(.name) \(.apiVersion)"
' 2>/dev/null || true
)
if [[ -n "${OWNER_KIND}" && -n "${OWNER_NAME}" ]]; then
echo " Pod is controlled by ${OWNER_KIND}/${OWNER_NAME} (${OWNER_APIVERSION}). Patching controller template."
# Determine full resource identifier for kubectl
CASE_INSENSITIVE_KIND=$(echo "${OWNER_KIND}" | tr '[:upper:]' '[:lower:]')
# Fetch controller manifest
CTRL_FILE="${BACKUP_DIR}/controller-${NS}__${CASE_INSENSITIVE_KIND}__${OWNER_NAME}.yaml"
kubectl get "${CASE_INSENSITIVE_KIND}" "${OWNER_NAME}" -n "${NS}" -o yaml > "${CTRL_FILE}"
# Patch pod template containers in controller
yq eval '
.spec.template.spec.containers |= (map(
.securityContext.allowPrivilegeEscalation = false
))
|
( .spec.template.spec.initContainers // [] ) as $ic
| if ($ic | length) > 0 then
.spec.template.spec.initContainers |= (map(
.securityContext.allowPrivilegeEscalation = false
))
else
.
end
' "${CTRL_FILE}" > "${CTRL_FILE}.patched"
kubectl apply -f "${CTRL_FILE}.patched"
echo " Patched controller ${OWNER_KIND}/${NS}/${OWNER_NAME}. Rolling update will replace Pods."
else
echo " Pod has no controller. Patching Pod directly (may be overwritten if recreated externally)."
POD_FILE="${BACKUP_DIR}/pod-${NS}__${NAME}.yaml"
kubectl get pod "${NAME}" -n "${NS}" -o yaml > "${POD_FILE}"
patch_pod_yaml < "${POD_FILE}" > "${POD_FILE}.patched"
kubectl apply -f "${POD_FILE}.patched"
fi
done < "${NONCOMPLIANT_FILE}"
echo "Waiting briefly for rollouts..."
sleep 10
echo "Verification:"
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
"is_compliant=false (remaining noncompliant containers exist)"
end
'