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

    • 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)"
      ][]'
  2. For Pods managed by higher-level controllers, patch the controller manifests

    • For each noncompliant Pod line with owner=Deployment/…, ReplicaSet/…, StatefulSet/…, or DaemonSet/…, note the owner kind, namespace, and name.
    • On any machine with kubectl access, edit the owning object (example for a Deployment; adapt kind, namespace, and name as needed):
      kubectl -n <namespace> edit deployment <name>
    • In each spec.template.spec.containers[] and spec.template.spec.initContainers[] entry, ensure:
      securityContext:
      allowPrivilegeEscalation: false
      (add securityContext if missing; if present, add or set allowPrivilegeEscalation: false).
  3. For Pods created directly (no controller owner), edit and recreate them

    • For each noncompliant Pod line with no owner= field:
      kubectl -n <namespace> get pod <pod-name> -o yaml > /tmp/pod-<namespace>-<pod-name>.yaml
    • Edit the file locally:
      sed -i '/^\s*resourceVersion:/d;/^\s*uid:/d;/^\s*selfLink:/d;/^\s*managedFields:/d;/^\s*status:/d' /tmp/pod-<namespace>-<pod-name>.yaml
    • Open the file and, under spec.containers[] and spec.initContainers[] as needed, set:
      securityContext:
      allowPrivilegeEscalation: false
    • Delete and recreate the Pod:
      kubectl -n <namespace> delete pod <pod-name>
      kubectl -n <namespace> apply -f /tmp/pod-<namespace>-<pod-name>.yaml
  4. If manifests are managed via Git/IaC, update source files

    • Locate the YAML/Helm/Kustomize definitions for the noncompliant workloads.
    • In each container and initContainer spec, set:
      securityContext:
      allowPrivilegeEscalation: false
    • Commit and apply/sync through your normal pipeline so the change persists.
  5. Consider namespace-wide policy to prevent regression (optional but recommended)

    • On any machine with kubectl access, create or update a PodSecurity admission label on namespaces where you want to enforce restricted settings, for example:
      kubectl label namespace <namespace> pod-security.kubernetes.io/enforce=restricted --overwrite
    • Review workload compatibility before enforcing to avoid inadvertent disruptions.
  6. Verify remediation

    • After workloads are updated and reconciled, re-run the audit 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 the output is is_compliant=true or that all listed containers show allowPrivilegeEscalation=false is_compliant=true.
Using kubectl

On any machine with kubectl access:

  1. Identify non‑compliant Pods and their owners (if any):
kubectl get pods --all-namespaces -o wide

If the Pod is controlled by a higher‑level object (Deployment, DaemonSet, Job, etc.), edit that controller; otherwise edit the Pod directly.

  1. Patch a standalone Pod to disallow privilege escalation (all containers and initContainers):
kubectl patch pod POD_NAME \
-n POD_NAMESPACE \
--type='json' \
-p='[
{"op": "add", "path": "/spec/securityContext", "value": {}},
{"op": "add", "path": "/spec/containers/0/securityContext", "value": {}}
]' 2>/dev/null || true

kubectl get pod POD_NAME -n POD_NAMESPACE -o json \
| jq '
.spec.containers |= map(
.securityContext.allowPrivilegeEscalation = false
)
| .spec.initContainers |= (// []) | . |= map(
.securityContext.allowPrivilegeEscalation = false
)' \
| kubectl apply -f -
  1. Patch a Deployment so all its containers disallow privilege escalation:
kubectl get deployment DEPLOYMENT_NAME -n DEPLOYMENT_NAMESPACE -o json \
| jq '
.spec.template.spec.containers |= map(
.securityContext.allowPrivilegeEscalation = false
)
| .spec.template.spec.initContainers |= (// []) | . |= map(
.securityContext.allowPrivilegeEscalation = false
)' \
| kubectl apply -f -

Repeat similarly for other controllers, replacing deployment with daemonset, statefulset, job, or cronjob as appropriate.

  1. Verification (runs 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
#
# Remediation: Ensure all containers disallow privilege escalation
# Benchmark: CBP C1.4
#
# Requirements:
# - kubectl configured with access to the cluster
# - jq installed
#
# Behavior:
# - Scans all non-system namespaces for Pods whose containers/initContainers
# do NOT have securityContext.allowPrivilegeEscalation=false
# - Patches their owning workload (Deployment/StatefulSet/DaemonSet/Job/CronJob/Pod)
# to set allowPrivilegeEscalation=false on every container and initContainer
# - Safe to re-run (idempotent)
# - Recreates Pods via normal controller behavior
#
# Run location:
# - Any machine with kubectl access to the cluster

set -euo pipefail

# -------- Config --------
# Namespaces to ignore (system namespaces)
IGNORE_NAMESPACES_REGEX='^(kube-system|kube-public|kube-node-lease)$'

# -------- Helper functions --------
log() { printf '[%s] %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"; }

require_bin() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "ERROR: '$1' not found in PATH" >&2
exit 1
fi
}

# jq filter used to set allowPrivilegeEscalation=false on all containers
jq_patch_filter='
.spec.template.spec as $spec
| if $spec == null then . else
.spec.template.spec =
(
$spec
| if (.containers // null) != null then
.containers |= map(
.securityContext =
((.securityContext // {}) + {allowPrivilegeEscalation:false})
)
else . end
| if (.initContainers // null) != null then
.initContainers |= map(
.securityContext =
((.securityContext // {}) + {allowPrivilegeEscalation:false})
)
else . end
)
end
'

jq_pod_patch_filter='
.spec as $spec
| if $spec == null then . else
.spec =
(
$spec
| if (.containers // null) != null then
.containers |= map(
.securityContext =
((.securityContext // {}) + {allowPrivilegeEscalation:false})
)
else . end
| if (.initContainers // null) != null then
.initContainers |= map(
.securityContext =
((.securityContext // {}) + {allowPrivilegeEscalation:false})
)
else . end
)
end
'

# -------- Pre-flight checks --------
require_bin kubectl
require_bin jq

# Verify cluster access
kubectl version --short >/dev/null 2>&1 || {
echo "ERROR: kubectl cannot reach the cluster" >&2
exit 1
}

# -------- Discover non-compliant pods and their owners --------
log "Discovering non-compliant Pods and their owners (excluding kube-system, kube-public, kube-node-lease)..."

mapfile -t NONCOMPLIANT_LINES < <(
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| select(.metadata.namespace | test("'"$IGNORE_NAMESPACES_REGEX"'") | not)
| . as $pod
| (.spec.containers // [] + .spec.initContainers // []) as $cs
| [ $cs[]
| (.securityContext.allowPrivilegeEscalation == false) as $ok
| select($ok | not)
] as $bad
| select(($bad | length) > 0)
| .metadata as $m
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| "ns=\($m.namespace) kind=\(if $own==null then "Pod" else $own.kind end) name=\(if $own==null then $m.name else $own.name end)"
' \
| sort -u
)

if [ "${#NONCOMPLIANT_LINES[@]}" -eq 0 ]; then
log "No non-compliant Pods found; cluster is already compliant."
else
log "Found ${#NONCOMPLIANT_LINES[@]} owning resources with non-compliant Pods."
fi

# -------- Patch owners --------
for line in "${NONCOMPLIANT_LINES[@]}"; do
ns=$(sed -E 's/^ns=([^ ]+).*/\1/' <<< "$line")
kind=$(sed -E 's/.* kind=([^ ]+).*/\1/' <<< "$line")
name=$(sed -E 's/.* name=([^ ]+).*/\1/' <<< "$line")

# Normalize common controller kinds to kubectl resource types
case "$kind" in
Deployment) res="deployment" ;;
StatefulSet) res="statefulset" ;;
DaemonSet) res="daemonset" ;;
Job) res="job" ;;
CronJob) res="cronjob" ;;
Pod) res="pod" ;;
*) res=$(tr '[:upper:]' '[:lower:]' <<< "$kind") ;;
esac

log "Processing $res/$ns/$name ..."

# Fetch object
if ! kubectl get "$res" "$name" -n "$ns" -o json >/tmp/aescalate-obj.json 2>/dev/null; then
log " WARN: Unable to fetch $res/$ns/$name; skipping."
continue
fi

# Apply appropriate jq patch
if [ "$res" = "pod" ]; then
jq "$jq_pod_patch_filter" /tmp/aescalate-obj.json > /tmp/aescalate-obj-patched.json
else
jq "$jq_patch_filter" /tmp/aescalate-obj.json > /tmp/aescalate-obj-patched.json
fi

# If no change, skip
if diff -q /tmp/aescalate-obj.json /tmp/aescalate-obj-patched.json >/dev/null 2>&1; then
log " No changes needed; already configured."
continue
fi

# Apply patch with server-side apply to be idempotent and schema-safe
if ! kubectl apply -f /tmp/aescalate-obj-patched.json >/dev/null 2>&1; then
log " ERROR: Failed to apply patch to $res/$ns/$name"
continue
fi

log " Patched $res/$ns/$name to set securityContext.allowPrivilegeEscalation=false on all containers."
done

rm -f /tmp/aescalate-obj.json /tmp/aescalate-obj-patched.json 2>/dev/null || true

# -------- Verification --------
log "Waiting briefly for controllers to reconcile pods..."
sleep 10

log "Verifying compliance with benchmark audit command..."

audit_output=$(
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'
)

if grep -q '^is_compliant=true$' <<< "$audit_output"; then
log "Verification succeeded: all checked Pods are compliant (allowPrivilegeEscalation=false)."
else
log "Verification found remaining non-compliant containers:"
printf '%s\n' "$audit_output"
exit 2
fi

exit 0