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
Remediation
Manual Steps
-
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)"][]' -
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_DEPLOYMENTkubectl -n NAMESPACE edit deployment APP_DEPLOYMENTIn the editor, locate each affected container or initContainer and remove or change:
securityContext:privileged: trueIf specific kernel capabilities are needed, add only those:
securityContext:capabilities:add:- NET_ADMIN- SYS_TIME -
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.yamlEdit
/tmp/pod-POD_NAME.yamland, for each container or initContainer, remove:securityContext:privileged: trueor replace with a minimal capabilities block as needed:
securityContext:capabilities:add:- NET_ADMINThen reapply:
kubectl -n NAMESPACE delete pod POD_NAMEkubectl -n NAMESPACE apply -f /tmp/pod-POD_NAME.yaml -
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: trueand instead use only required
securityContext.capabilities.addentries. -
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 -
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) == 0then "is_compliant=true"else $rows[]end'
Using kubectl
On any machine with kubectl access:
- 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)"
][]'
- 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.
- 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,statussections if present. - Under each affected container/initContainer, remove
securityContext.privileged: trueor replace with a minimalsecurityContext.capabilities.addblock as required.
Then delete and recreate the pod:
kubectl -n my-namespace delete pod my-pod
kubectl apply -f /tmp/my-pod-fixed.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.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."