Minimize Admission Of Privileged Containers
More Info:
Do not generally permit containers to be run with the securityContext.privileged flag set to true.
Risk Level
Critical
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Identify all namespaces with user workloads (run on any machine with kubectl access):
kubectl get nsDecide which namespaces should disallow privileged containers (typically all except core system namespaces like kube-system, kube-public, kube-node-lease, kube-admin equivalents).
-
Create a baseline policy that denies privileged containers (run once on any machine with kubectl access; use Pod Security Admission via labels as the default, plus optional PodSecurityPolicy or AdmissionPolicy if supported in your cluster). Example using Pod Security Admission “restricted” level:
# Label each target namespace to enforce restricted policyfor ns in NAMESPACE1 NAMESPACE2 NAMESPACE3; dokubectl label namespace "$ns" \pod-security.kubernetes.io/enforce=restricted \pod-security.kubernetes.io/enforce-version=latest \pod-security.kubernetes.io/audit=restricted \pod-security.kubernetes.io/warn=restricted \--overwritedoneReplace
NAMESPACE1 NAMESPACE2 NAMESPACE3with your chosen namespaces. -
(If PodSecurityPolicy is still in use in your cluster) Create a PSP that disallows privileged containers and a ClusterRole to use it (run on any machine with kubectl access):
cat << 'EOF' | kubectl apply -f -apiVersion: policy/v1beta1kind: PodSecurityPolicymetadata:name: restricted-no-privilegedspec:privileged: falseallowPrivilegeEscalation: falsehostPID: falsehostIPC: falsehostNetwork: falserunAsUser:rule: 'MustRunAsNonRoot'seLinux:rule: 'RunAsAny'fsGroup:rule: 'RunAsAny'supplementalGroups:rule: 'RunAsAny'volumes:- 'configMap'- 'downwardAPI'- 'emptyDir'- 'persistentVolumeClaim'- 'projected'- 'secret'---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata:name: use-restricted-no-privileged-psprules:- apiGroups: ['policy']resources: ['podsecuritypolicies']verbs: ['use']resourceNames: ['restricted-no-privileged']EOF -
(If PSP is in use) Bind the PSP to the service accounts that run user workloads in each target namespace (run on any machine with kubectl access):
# Example: bind to all service accounts in a namespacefor ns in NAMESPACE1 NAMESPACE2 NAMESPACE3; dokubectl create rolebinding "use-restricted-no-privileged-psp" \--clusterrole=use-restricted-no-privileged-psp \--group=system:serviceaccounts:"$ns" \--namespace="$ns" \--dry-run=client -o yaml | kubectl apply -f -done -
Manually review and update existing workloads that currently request privileged containers to either:
- Remove
securityContext.privileged: true, or - Move them into carefully controlled namespaces with different admission policy and an explicit exception process.
To locate and inspect offending pods (run on any machine with kubectl access):
# List non-compliant containerskubectl get pods --all-namespaces -o custom-columns=POD_NAME:.metadata.name,POD_NAMESPACE:.metadata.namespace --no-headers | while read -r pod_name pod_namespacedokubectl get pod "${pod_name}" --namespace "${pod_namespace}" -o json | jq -c '.spec.containers[]' | while read -r containerdocontainer_name=$(echo ${container} | jq -r '.name')container_privileged=$(echo ${container} | jq -r '.securityContext.privileged' | sed -e 's/null/notset/g')if [ "${container_privileged}" = "true" ]; thenecho "NON-COMPLIANT: pod=${pod_name} ns=${pod_namespace} container=${container_name}"fidonedoneFor each non-compliant Deployment/DaemonSet/StatefulSet/etc., edit the manifest to remove
securityContext.privileged: trueand redeploy:kubectl -n TARGET_NAMESPACE edit deployment TARGET_DEPLOYMENTRemove or set
privileged: falseunder the container’ssecurityContext, save, and let the controller recreate pods. - Remove
-
Verification (run on any machine with kubectl access):
kubectl get pods --all-namespaces -o custom-columns=POD_NAME:.metadata.name,POD_NAMESPACE:.metadata.namespace --no-headers | while read -r pod_name pod_namespacedokubectl get pod "${pod_name}" --namespace "${pod_namespace}" -o json | jq -c '.spec.containers[]' | while read -r containerdocontainer_name=$(echo ${container} | jq -r '.name')container_privileged=$(echo ${container} | jq -r '.securityContext.privileged' | sed -e 's/null/notset/g')if [ "${container_privileged}" = "false" ] || [ "${container_privileged}" = "notset" ] ; thenecho "***pod_name: ${pod_name} container_name: ${container_name} pod_namespace: ${pod_namespace} is_container_privileged: ${container_privileged} is_compliant: true"elseecho "***pod_name: ${pod_name} container_name: ${container_name} pod_namespace: ${pod_namespace} is_container_privileged: ${container_privileged} is_compliant: false"fidonedoneConfirm that no lines show
is_compliant: falsefor namespaces where privileged containers should be minimized.
Using kubectl
# Apply once per workload namespace (edit metadata.name as needed)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged-containers
spec:
validationFailureAction: Enforce
background: true
rules:
- name: validate-privileged
match:
any:
- resources:
kinds:
- Pod
- Deployment
- ReplicaSet
- DaemonSet
- StatefulSet
- Job
- CronJob
validate:
message: "Privileged containers are not allowed. Do not set securityContext.privileged: true."
pattern:
spec:
containers:
- =(securityContext):
=(privileged): "false"
=(initContainers):
- =(securityContext):
=(privileged): "false"
# On any machine with kubectl access: create the policy
kubectl apply -f disallow-privileged-containers.yaml
# Optional: dry-run creation of a privileged pod to confirm it is blocked
kubectl run test-privileged \
--image=busybox \
--restart=Never \
--overrides='{"apiVersion":"v1","kind":"Pod","metadata":{"name":"test-privileged"}, "spec":{"containers":[{"name":"c","image":"busybox","securityContext":{"privileged":true},"command":["sh","-c","sleep 3600"]}]}}' \
--dry-run=server -o yaml
Verification (same style as audit, on any machine with kubectl access):
kubectl get pods --all-namespaces -o custom-columns=POD_NAME:.metadata.name,POD_NAMESPACE:.metadata.namespace --no-headers | while read -r pod_name pod_namespace
do
kubectl get pod "${pod_name}" --namespace "${pod_namespace}" -o json | jq -c '.spec.containers[]' | while read -r container
do
container_name=$(echo ${container} | jq -r '.name')
container_privileged=$(echo ${container} | jq -r '.securityContext.privileged' | sed -e 's/null/notset/g')
if [ "${container_privileged}" = "false" ] || [ "${container_privileged}" = "notset" ] ; then
echo "***pod_name: ${pod_name} container_name: ${container_name} pod_namespace: ${pod_namespace} is_container_privileged: ${container_privileged} is_compliant: true"
else
echo "***pod_name: ${pod_name} container_name: ${container_name} pod_namespace: ${pod_namespace} is_container_privileged: ${container_privileged} is_compliant: false"
fi
done
done
Automation
#!/usr/bin/env bash
set -euo pipefail
# Purpose:
# - For each namespace with user workloads, create/patch a PodSecurityPolicy-like
# restriction using Pod Security Admission labels to forbid privileged pods.
# - This is MANUAL in CIS: script helps you detect + optionally enforce.
#
# Requirements:
# - Run on any machine with kubectl and jq configured for the target cluster.
# - Kubernetes v1.25+ recommended (PodSecurity Admission GA).
#
# OPERATIONAL IMPACT:
# - After enforcement, any new pod (or updated existing pod) with
# securityContext.privileged=true will be rejected in restricted namespaces.
# - Existing running privileged pods are NOT evicted automatically.
#---------------------------- Configurable options ----------------------------#
# Namespaces to exclude from enforcement (space-separated, exact names).
# Add control-plane / system namespaces and any namespace where privileged
# containers are explicitly required and approved via risk acceptance.
EXCLUDED_NAMESPACES=(
"kube-system"
"kube-public"
"kube-node-lease"
"default" # remove this if you want default namespace restricted too
"kube-tools" # example; remove or change as needed
)
# Pod Security Admission level to enforce:
# Valid: privileged | baseline | restricted
# Use "restricted" to strongly minimize privileged / insecure workloads.
PSA_ENFORCEMENT_LEVEL="restricted"
# If true, only report non-compliance but do NOT change any namespaces.
DRY_RUN=${DRY_RUN:-"false"} # set DRY_RUN=true in environment to only audit
#---------------------------- Helper functions ----------------------------#
contains() {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
log() { printf '[INFO] %s\n' "$*" >&2; }
warn() { printf '[WARN] %s\n' "$*" >&2; }
err() { printf '[ERR ] %s\n' "$*" >&2; }
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
err "Required command '$1' not found in PATH"
exit 1
}
}
#---------------------------- Pre-flight checks -----------------------------#
require_cmd kubectl
require_cmd jq
# Verify cluster access
if ! kubectl version --request-timeout='5s' >/dev/null 2>&1; then
err "kubectl cannot reach the cluster or is not authorized"
exit 1
fi
#---------------------------- Detect privileged pods ------------------------#
log "Scanning for containers with securityContext.privileged=true ..."
NON_COMPLIANT_FOUND=0
kubectl get pods --all-namespaces -o custom-columns=POD_NAME:.metadata.name,POD_NAMESPACE:.metadata.namespace --no-headers \
| while read -r pod_name pod_namespace; do
kubectl get pod "${pod_name}" --namespace "${pod_namespace}" -o json \
| jq -c '.spec.containers[]' \
| while read -r container; do
container_name=$(echo "${container}" | jq -r '.name')
container_privileged=$(echo "${container}" | jq -r '.securityContext.privileged' | sed -e 's/null/notset/g')
if [ "${container_privileged}" = "true" ]; then
printf 'NON_COMPLIANT pod_name=%s container_name=%s pod_namespace=%s privileged=%s\n' \
"${pod_name}" "${container_name}" "${pod_namespace}" "${container_privileged}"
NON_COMPLIANT_FOUND=1
fi
done
done
# Note: NON_COMPLIANT_FOUND is in a subshell above and will not propagate here;
# we instead rely on a verification run later. This block is for operator output.
log "Initial privileged container scan complete."
#---------------------------- Enforce namespace policies --------------------#
log "Discovering namespaces to label with Pod Security Admission..."
NAMESPACES=$(kubectl get ns -o jsonpath='{.items[*].metadata.name}')
APPLIED_COUNT=0
for ns in ${NAMESPACES}; do
if contains "${ns}" "${EXCLUDED_NAMESPACES[@]}"; then
log "Skipping excluded namespace: ${ns}"
continue
fi
# Read current labels
current_enforce=$(kubectl get ns "${ns}" -o jsonpath='{.metadata.labels.pod-security\.kubernetes\.io/enforce}' 2>/dev/null || true)
# Decide whether to patch
if [ "${current_enforce}" = "${PSA_ENFORCEMENT_LEVEL}" ]; then
log "Namespace ${ns} already has enforce=${PSA_ENFORCEMENT_LEVEL}; no change."
continue
fi
if [ "${DRY_RUN}" = "true" ]; then
warn "DRY RUN: would set pod-security.kubernetes.io/enforce=${PSA_ENFORCEMENT_LEVEL} on namespace ${ns}"
continue
fi
log "Patching namespace ${ns} to enforce Pod Security level: ${PSA_ENFORCEMENT_LEVEL}"
kubectl label namespace "${ns}" \
"pod-security.kubernetes.io/enforce=${PSA_ENFORCEMENT_LEVEL}" \
"pod-security.kubernetes.io/enforce-version=latest" \
--overwrite
APPLIED_COUNT=$((APPLIED_COUNT + 1))
done
log "Namespace labeling complete. Namespaces updated: ${APPLIED_COUNT}"
#---------------------------- Verification ----------------------------------#
log "Verifying that privileged containers are either absent or blocked by policy."
# 1) Show current privileged containers (if any remain).
log "Re-running privileged container audit:"
kubectl get pods --all-namespaces -o custom-columns=POD_NAME:.metadata.name,POD_NAMESPACE:.metadata.namespace --no-headers \
| while read -r pod_name pod_namespace; do
kubectl get pod "${pod_name}" --namespace "${pod_namespace}" -o json \
| jq -c '.spec.containers[]' \
| while read -r container; do
container_name=$(echo "${container}" | jq -r '.name')
container_privileged=$(echo "${container}" | jq -r '.securityContext.privileged' | sed -e 's/null/notset/g')
if [ "${container_privileged}" = "false" ] || [ "${container_privileged}" = "notset" ]; then
printf 'COMPLIANT pod_name=%s container_name=%s pod_namespace=%s privileged=%s\n' \
"${pod_name}" "${container_name}" "${pod_namespace}" "${container_privileged}"
else
printf 'NON_COMPLIANT pod_name=%s container_name=%s pod_namespace=%s privileged=%s\n' \
"${pod_name}" "${container_name}" "${container_name}" "${container_privileged}"
fi
done
done
# 2) Show namespaces and their Pod Security enforce level for review.
log "Current Pod Security enforce labels per namespace:"
kubectl get ns -o custom-columns=NAME:.metadata.name,ENFORCE:'".metadata.labels.pod-security\.kubernetes\.io/enforce"' --show-labels
log "Automation completed. Review NON_COMPLIANT lines above and handle any remaining privileged workloads per policy (exceptions, redesign, or migration to excluded namespaces)."