Skip to main content

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​

Manual Steps
  1. 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'
  2. 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 values
    kubectl -n <namespace> edit deployment <deployment-name>
    • In the editor, under each containers: and initContainers: entry, ensure a securityContext with allowPrivilegeEscalation: false, for example:
    spec:
    template:
    spec:
    containers:
    - name: app
    image: your-image
    securityContext:
    allowPrivilegeEscalation: false
    initContainers:
    - name: init
    image: your-init-image
    securityContext:
    allowPrivilegeEscalation: false
    • Save and exit; Kubernetes will roll out updated Pods.
  3. 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 status
    kubectl -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
  4. 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.
  5. (Optional hardening) Add a default policy to prevent new Pods without this setting

    • Run on: any machine with kubectl access
    • Example: a PodSecurity admission label enforcing restricted in a namespace (if not already using Pod Security or Policy Controller):
    kubectl label namespace <namespace> pod-security.kubernetes.io/enforce=restricted --overwrite
  6. 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=true or contains no lines with is_compliant=false.
Using kubectl

On any machine with kubectl access:

  1. 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.

  2. Example: patch a Deployment to set allowPrivilegeEscalation: false for 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>.yaml

    b. Edit /tmp/deployment-<deployment-name>.yaml and, under every containers[] and initContainers[] 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 securityContext already exists, just add/update allowPrivilegeEscalation: false under it.

    c. Apply the updated manifest:

    kubectl apply -f /tmp/deployment-<deployment-name>.yaml

    This will trigger a rolling update and recreate Pods from this Deployment with the new setting.

  3. Repeat the same export/edit/apply pattern for other controllers (DaemonSet, StatefulSet, Job, CronJob) that own non‑compliant Pods:

    # DaemonSet
    kubectl get daemonset <daemonset-name> -n <namespace> -o yaml > /tmp/daemonset-<daemonset-name>.yaml
    kubectl apply -f /tmp/daemonset-<daemonset-name>.yaml

    # StatefulSet
    kubectl get statefulset <statefulset-name> -n <namespace> -o yaml > /tmp/statefulset-<statefulset-name>.yaml
    kubectl apply -f /tmp/statefulset-<statefulset-name>.yaml

    # Job
    kubectl get job <job-name> -n <namespace> -o yaml > /tmp/job-<job-name>.yaml
    kubectl 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>.yaml
    kubectl apply -f /tmp/cronjob-<cronjob-name>.yaml
  4. For standalone Pods without an owning controller (no owner= in the audit output), recreate them from a manifest that explicitly sets allowPrivilegeEscalation: false on every container and init container:

    kubectl get pod <pod-name> -n <namespace> -o yaml > /tmp/pod-<pod-name>.yaml

    Edit /tmp/pod-<pod-name>.yaml to add/update:

    spec:
    containers:
    - name: <container-name>
    image: <image>
    securityContext:
    allowPrivilegeEscalation: false
    initContainers:
    - name: <init-container-name>
    image: <image>
    securityContext:
    allowPrivilegeEscalation: false

    Then delete and recreate:

    kubectl delete pod <pod-name> -n <namespace>
    kubectl apply -f /tmp/pod-<pod-name>.yaml
  5. 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=false and is_compliant=true (or only is_compliant=true is 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
'