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 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.containers // []) + (.spec.initContainers // []))[]
    | (.securityContext.allowPrivilegeEscalation == false) as $ok
    | select($ok | not)
    | "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=false"
    ][]'
  2. For a Pod managed by a higher-level controller (Deployment/StatefulSet/DaemonSet, etc.), edit the controller manifest to set allowPrivilegeEscalation: false on every container (run on any machine with kubectl access; repeat per owning resource):

    kubectl -n NAMESPACE edit DEPLOYMENT/NAME

    In each .spec.template.spec.containers[] and .spec.template.spec.initContainers[] entry, ensure:

    securityContext:
    allowPrivilegeEscalation: false

    If securityContext exists, just add allowPrivilegeEscalation: false under it.

  3. For a standalone Pod (no controller owner listed in step 1 output), export, modify, and re-create it (run on any machine with kubectl access; replace NAMESPACE and POD_NAME):

    kubectl -n NAMESPACE get pod POD_NAME -o yaml > /tmp/pod-POD_NAME.yaml

    Edit /tmp/pod-POD_NAME.yaml and, for every entry in spec.containers[] and spec.initContainers[], set:

    securityContext:
    allowPrivilegeEscalation: false

    Then delete metadata.resourceVersion, metadata.uid, metadata.creationTimestamp, metadata.managedFields, and status sections from the file. Apply the fixed Pod:

    kubectl -n NAMESPACE delete pod POD_NAME
    kubectl apply -f /tmp/pod-POD_NAME.yaml
  4. If your AKS cluster is managed via GitOps or IaC (e.g., manifests in a Git repo, Bicep/Terraform/ARM), update the source manifests instead of using kubectl edit, ensuring all container and initContainer specs include:

    securityContext:
    allowPrivilegeEscalation: false

    Commit and deploy according to your existing pipeline so changes persist.

  5. Consider operational impact before saving each change: updating a Pod template in a controller triggers rollout of new Pods; standalone Pods will be deleted and recreated, briefly interrupting workloads.

  6. Verify compliance (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'

    Ensure the output is exactly is_compliant=true.

Using kubectl

On any machine with kubectl access:

  1. Identify non‑compliant Pods and their owners (for context only):
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'
  1. Patch an existing Pod’s containers to set allowPrivilegeEscalation: false (for Pods you manage directly, knowing this will recreate them via their controller, not edit live static Pods):
NAMESPACE=default
POD=my-pod

kubectl get pod "${POD}" -n "${NAMESPACE}" -o yaml \
| yq '(.spec.containers[].securityContext //={}) |= . + {"allowPrivilegeEscalation": false}
| (.spec.initContainers[]? //.securityContext //={}) |= . + {"allowPrivilegeEscalation": false}' \
| kubectl apply -f -
  1. Preferred: edit the owning workload manifest and re‑apply (Deployment, DaemonSet, StatefulSet, Job, etc.). Example for a Deployment (apply from any machine with kubectl):
cat << 'EOF' > deployment-secure.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-container
image: my-image:tag
securityContext:
allowPrivilegeEscalation: false
initContainers:
- name: my-init
image: my-init-image:tag
securityContext:
allowPrivilegeEscalation: false
EOF

kubectl apply -f deployment-secure.yaml
  1. Verification (run 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.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'
Automation
#!/usr/bin/env bash
# Remediate: Set securityContext.allowPrivilegeEscalation=false on all pod & init containers
# Scope: Any machine with kubectl access to the AKS cluster
set -euo pipefail

# 1) Pre-flight: ensure kubectl & jq are available and we can talk to the cluster
if ! command -v kubectl >/dev/null 2>&1; then
echo "kubectl not found in PATH" >&2
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "jq not found in PATH" >&2
exit 1
fi

kubectl_version=$(kubectl version --short 2>/dev/null || true)
if [ -z "$kubectl_version" ]; then
echo "kubectl cannot reach a cluster (check KUBECONFIG / context)" >&2
exit 1
fi

echo "Using kubectl context:"
kubectl config current-context

# 2) Find all non-excluded pods that have any container/initContainer with allowPrivilegeEscalation!=false
echo "Discovering non-compliant pods..."
non_compliant_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)
| . as $pod
| ((.spec.containers // []) + (.spec.initContainers // [])) as $all
| any($all[]?;
(.securityContext.allowPrivilegeEscalation // "unset") != false) as $has_bad
| select($has_bad)
| "\(.metadata.namespace) \(.metadata.name)"
'
)

if [ -z "$non_compliant_pods" ]; then
echo "No non-compliant pods found."
else
echo "Non-compliant pods (namespace name):"
echo "$non_compliant_pods"
fi

# 3) Warn about controllers and mutate their templates instead of pods
echo
echo "NOTE:"
echo "- Pods created by Deployments/DaemonSets/StatefulSets/Jobs/CronJobs should be fixed by editing the controller spec."
echo "- This script will NOT patch controllers automatically; review suggested edits and apply them to your manifests or via kubectl edit."
echo

# Function: show suggested patch for a single Pod spec (for reference when editing controllers)
suggest_pod_patch() {
ns="$1"
pod="$2"
echo "==== Suggested securityContext patch for Pod $ns/$pod ===="
kubectl get pod "$pod" -n "$ns" -o json | jq '
{
metadata: { name: .metadata.name, namespace: .metadata.namespace },
spec: {
containers: ((.spec.containers // []) | map(
.securityContext = (.securityContext // {}) + {allowPrivilegeEscalation: false}
)),
initContainers: ((.spec.initContainers // []) | map(
.securityContext = (.securityContext // {}) + {allowPrivilegeEscalation: false}
))
}
}
'
echo
}

if [ -n "$non_compliant_pods" ]; then
while read -r ns name; do
[ -z "$ns" ] && continue
suggest_pod_patch "$ns" "$name"
done <<< "$non_compliant_pods"
fi

# 4) Optional: interactive patch of standalone Pods (those without a controller ownerRef)
echo "Attempting to automatically patch standalone Pods (no controller ownerReference)..."

while read -r ns name; do
[ -z "$ns" ] && continue

owner_kind=$(kubectl get pod "$name" -n "$ns" -o json |
jq -r '([(.metadata.ownerReferences // [])[] | select(.controller) | .kind] | first) // "NONE"')

if [ "$owner_kind" != "NONE" ]; then
echo "Skipping $ns/$name (owned by $owner_kind) - patch the controller instead."
continue
fi

echo "Patching standalone Pod $ns/$name ..."
# Idempotent JSON patch: ensures allowPrivilegeEscalation=false on all containers/initContainers
patch='
{
"spec": {
"containers": [],
"initContainers": []
}
}
'
# Build patch dynamically from current Pod to preserve other fields
pod_json=$(kubectl get pod "$name" -n "$ns" -o json)

containers_patch=$(echo "$pod_json" | jq -c '
(.spec.containers // []) | map(
.securityContext = (.securityContext // {}) + {allowPrivilegeEscalation: false}
)
')
init_containers_patch=$(echo "$pod_json" | jq -c '
(.spec.initContainers // []) | map(
.securityContext = (.securityContext // {}) + {allowPrivilegeEscalation: false}
)
')

final_patch=$(jq -n \
--argjson c "$containers_patch" \
--argjson i "$init_containers_patch" \
'{spec: {containers: $c, initContainers: $i}}')

kubectl patch pod "$name" -n "$ns" --type merge -p "$final_patch"

done <<< "$non_compliant_pods"

# 5) Verification: re-run the audit to confirm all pods are compliant
echo
echo "Re-running compliance check..."
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'