Skip to main content

Containers Should Not Run In Privileged Mode

More Info:​

Verifies no container sets securityContext.privileged=true. A privileged container can compromise the node and every other pod scheduled on it.

Risk Level​

Critical

Address​

Security

Compliance Standards​

  • Cloudanix Best Practice

Triage and Remediation​

Remediation​

Manual Steps
  1. Identify all privileged 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.privileged // false) as $priv
    | select($priv == true)
    | "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) privileged=\($priv)"
    ][]'
  2. For a privileged pod that is controlled by a higher-level object (e.g., Deployment/DaemonSet/StatefulSet), edit the controller (run on any machine with kubectl access). Example for a Deployment:

    kubectl -n NAMESPACE get pod POD_NAME -o jsonpath='{.metadata.ownerReferences[0].kind}{"\n"}{.metadata.ownerReferences[0].name}{"\n"}'
    # Suppose kind is Deployment and name is APP_DEPLOYMENT
    kubectl -n NAMESPACE edit deployment APP_DEPLOYMENT

    In the editor, locate each affected container or initContainer and remove or change:

    securityContext:
    privileged: true

    If specific kernel capabilities are needed, add only those:

    securityContext:
    capabilities:
    add:
    - NET_ADMIN
    - SYS_TIME
  3. For a privileged pod that is not controlled by any owner (standalone Pod), edit or recreate its manifest (run on any machine with kubectl access):

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

    Edit /tmp/pod-POD_NAME.yaml and, for each container or initContainer, remove:

    securityContext:
    privileged: true

    or replace with a minimal capabilities block as needed:

    securityContext:
    capabilities:
    add:
    - NET_ADMIN

    Then reapply:

    kubectl -n NAMESPACE delete pod POD_NAME
    kubectl -n NAMESPACE apply -f /tmp/pod-POD_NAME.yaml
  4. If multiple workloads share the same pattern, update their source manifests or Helm charts (run where your manifests/Helm live) so that future deployments do not reintroduce:

    securityContext:
    privileged: true

    and instead use only required securityContext.capabilities.add entries.

  5. Wait for updated workloads to roll out (run on any machine with kubectl access):

    kubectl -n NAMESPACE rollout status deployment/APP_DEPLOYMENT
    # Repeat for other controllers such as daemonset/STATEFULSET as appropriate
  6. Verify no remaining privileged containers (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.privileged // false) as $priv
    | "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) privileged=\($priv)"
    + " is_compliant=\(if $priv then "false" else "true" end)"
    ] as $rows
    | if ($rows | map(select(. | contains("privileged=true")) ) | length) == 0
    then "is_compliant=true"
    else $rows[]
    end'
Using kubectl

On any machine with kubectl access:

  1. Identify all privileged containers (for context before changing anything):
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.privileged == true)
| "ns=\($m.namespace) pod=\($m.name) container=\(.name)"
][]'
  1. For each affected pod that is managed by a higher-level controller (Deployment, StatefulSet, DaemonSet, Job, CronJob), edit the controller manifest so that new pods are created without privileged: true.

Example for a Deployment (replace names/namespace as needed):

kubectl -n my-namespace edit deploy my-deployment

In the editor, in each affected container (and initContainer) under spec.template.spec.containers (or initContainers), remove or change:

securityContext:
privileged: true

If specific kernel capabilities are required, replace with only the needed ones, for example:

securityContext:
capabilities:
add:
- NET_ADMIN

Save and exit; Kubernetes will roll out updated pods automatically.

  1. For pods created directly (not via a controller), export, modify, and re-apply:
kubectl -n my-namespace get pod my-pod -o yaml --export > /tmp/my-pod-fixed.yaml

Edit /tmp/my-pod-fixed.yaml:

  • Remove metadata.creationTimestamp, metadata.resourceVersion, metadata.uid, metadata.selfLink, metadata.managedFields, status sections if present.
  • Under each affected container/initContainer, remove securityContext.privileged: true or replace with a minimal securityContext.capabilities.add block as required.

Then delete and recreate the pod:

kubectl -n my-namespace delete pod my-pod
kubectl apply -f /tmp/my-pod-fixed.yaml
  1. 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.containers // []) + (.spec.initContainers // []))[]
| (.securityContext.privileged // false) as $priv
| "ns=\($m.namespace) pod=\($m.name) container=\(.name) privileged=\($priv)"
] as $rows
| if ([$rows[] | select(. | test("privileged=true"))] | length) == 0
then "is_compliant=true"
else $rows[]
end'
Automation
#!/usr/bin/env bash
#
# Remediate privileged containers in an OKE cluster by:
# - Detecting Pods with securityContext.privileged=true (excluding system namespaces)
# - Identifying their owning workload (Deployment/DaemonSet/StatefulSet/Job/CronJob/ReplicaSet)
# - Patching the owning workload template to remove privileged=true
# - Falling back to patching standalone Pods
#
# Requirements (run on any machine with kubectl access):
# - kubectl
# - jq
#
# Idempotent: safe to re-run; repeated patches are no-ops.

set -euo pipefail

echo "Scanning for privileged containers (excluding kube-system, kube-public, kube-node-lease)..."

# Capture audit output once
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
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| ((.spec.containers // []) + (.spec.initContainers // []))[]
| (.securityContext.privileged // false) as $priv
| "ns=\($m.namespace) pod=\($m.name) container=\(.name) owner_kind=\($own.kind // "") owner_name=\($own.name // "") privileged=\($priv)"
][]' \
)"

# Filter only privileged=true lines
PRIV_LINES="$(printf '%s\n' "$AUDIT_OUTPUT" | awk '$NF=="privileged=true"')"

if [ -z "$PRIV_LINES" ]; then
echo "No privileged containers found. Cluster is compliant."
exit 0
fi

echo "Found privileged containers:"
printf '%s\n' "$PRIV_LINES"

# Helper: patch a workload template to drop securityContext.privileged
patch_workload() {
local ns="$1"
local kind="$2"
local name="$3"

echo "Patching $kind/$ns/$name to remove container securityContext.privileged..."

# We must handle both containers and initContainers in the pod template.
# The patch deletes the privileged field where present; if absent, no-op.
kubectl -n "$ns" patch "$kind" "$name" --type='merge' -p '{
"spec": {
"template": {
"spec": {
"containers": [
]
}
}
}
}' >/dev/null 2>&1 || true

# Use strategic JSON patch to clear privileged from all containers and initContainers
kubectl -n "$ns" patch "$kind" "$name" --type='json' -p '[
{
"op": "remove",
"path": "/spec/template/spec/containers/0/securityContext/privileged"
}
]' >/dev/null 2>&1 || true

kubectl -n "$ns" patch "$kind" "$name" --type='json' -p '[
{
"op": "remove",
"path": "/spec/template/spec/initContainers/0/securityContext/privileged"
}
]' >/dev/null 2>&1 || true
}

# Helper: patch a standalone Pod (no controller owner)
patch_pod() {
local ns="$1"
local pod="$2"

echo "Patching Pod/$ns/$pod to remove container securityContext.privileged..."

kubectl -n "$ns" patch pod "$pod" --type='json' -p '[
{
"op": "remove",
"path": "/spec/containers/0/securityContext/privileged"
}
]' >/dev/null 2>&1 || true

kubectl -n "$ns" patch pod "$pod" --type='json' -p '[
{
"op": "remove",
"path": "/spec/initContainers/0/securityContext/privileged"
}
]' >/dev/null 2>&1 || true
}

# Derive unique workload owners and standalone pods from the audit output
echo
echo "Determining owning workloads..."

# Process each privileged record
printf '%s\n' "$PRIV_LINES" | while read -r line; do
ns="$(printf '%s\n' "$line" | sed -E 's/.*ns=([^ ]+).*/\1/')"
pod="$(printf '%s\n' "$line" | sed -E 's/.*pod=([^ ]+).*/\1/')"
owner_kind="$(printf '%s\n' "$line" | sed -E 's/.*owner_kind=([^ ]*).*/\1/')"
owner_name="$(printf '%s\n' "$line" | sed -E 's/.*owner_name=([^ ]*).*/\1/')"

if [ -n "$owner_kind" ] && [ -n "$owner_name" ]; then
# Normalize known owner kinds to top-level controllers
# Pods owned by ReplicaSets -> patch the Deployment (if any)
case "$owner_kind" in
ReplicaSet)
# Try to find the Deployment owning this ReplicaSet
rs_json="$(kubectl -n "$ns" get rs "$owner_name" -o json 2>/dev/null || true)"
if [ -n "$rs_json" ]; then
dep_name="$(printf '%s\n' "$rs_json" | jq -r '([.metadata.ownerReferences[]? | select(.controller==true and .kind=="Deployment")] | first // empty).name')"
if [ -n "$dep_name" ]; then
owner_kind="Deployment"
owner_name="$dep_name"
fi
fi
;;
esac

echo "$ns $owner_kind $owner_name"
else
echo "$ns Pod $pod"
fi
done | sort -u | while read -r ns kind name; do
if [ "$kind" = "Pod" ]; then
patch_pod "$ns" "$name"
else
patch_workload "$ns" "$kind" "$name"
fi
done

echo
echo "Waiting for updated workloads to roll out (if applicable)..."
sleep 10

echo "Re-running privileged container check to verify remediation..."
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.privileged // false) as $priv
| select($priv == true)
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else "is_compliant=false" end
'

echo "Done."