Minimize Admission Of Root Containers
More Info:
Do not generally permit containers to be run as the root user.
Risk Level
Critical
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On any machine with kubectl access, list all namespaces and identify those that should strongly forbid root containers (typically all except system namespaces):
kubectl get nsDecide which namespaces must enforce non-root (e.g., all except: kube-system, kube-public, kube-node-lease, default if needed for legacy workloads).
-
For each target namespace, inspect existing Pod Security/Admission policy mechanisms to see whether they already prevent root containers:
# Pod Security admission labelskubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels}{"\n"}{end}'# PodSecurityPolicy objects (if still in use)kubectl get psp -o wide || true# Gatekeeper/kyverno policies (if used)kubectl get constraints --all-namespaces 2>/dev/null || truekubectl get cpol,pol --all-namespaces 2>/dev/null || trueReview whether any mechanism enforces
runAsNonRoot: trueorrunAsUserranges that exclude UID 0. -
For each policy mechanism in use, examine the detailed rules to confirm they require non-root UIDs:
# Example: PodSecurityPolicy detailskubectl get psp <psp-name> -o yaml# Example: Gatekeeper constraintkubectl get <constraint-kind> <constraint-name> -n <ns> -o yaml# Example: Kyverno policykubectl get cpol <policy-name> -o yamlVerify that they enforce either
MustRunAsNonRoot(or equivalentrunAsNonRoot: true) orMustRunAswith UID ranges that do not include 0 for containers and pods in the target namespaces. -
Where no such enforcement exists for a target namespace, design or update a namespace‑scoped policy to require non‑root containers, using the mechanism your cluster supports. For example, with PodSecurityPolicy still enabled, you might define a PSP that contains:
runAsUser:rule: MustRunAsNonRootor:
runAsUser:rule: MustRunAsranges:- min: 1000max: 65535Then bind this policy (or the equivalent Gatekeeper/Kyverno policy) so that it applies to all service accounts in the target namespace.
-
Before enforcing the new or updated policy, audit existing workloads in each namespace to find pods or workloads that currently run as root and would be blocked:
kubectl get pods -n <ns> -o json | jq -r '.items[] |select((.spec.securityContext.runAsNonRoot == false)or (.spec.securityContext.runAsUser == 0)or ([.spec.containers[], (.spec.initContainers // [])[]] |.[] |(.securityContext.runAsNonRoot == falseor .securityContext.runAsUser == 0))) |.metadata.name'For any listed workloads, review their manifests and application requirements; update them to run as a non‑root UID or formally document and justify an exception.
-
After updating policies and workloads, verify that the policies are active and effective:
- Confirm the policy objects and their bindings/assignments:
kubectl get psp -o yamlkubectl get role,rolebinding,clusterrole,clusterrolebinding -A
- Attempt to deploy a simple pod that runs as root into a target namespace; it should be rejected by admission control. For example:
Confirm that this creation fails and the error message indicates the non‑root requirement from your policy.kubectl run root-test --image=busybox -n <ns> --overrides='{"apiVersion": "v1","kind": "Pod","spec": {"containers": [{"name": "c","image": "busybox","command": ["sh", "-c", "sleep 3600"],"securityContext": {"runAsUser": 0}}]}}'
- Confirm the policy objects and their bindings/assignments:
Using kubectl
Using kubectl
1. List all namespaces (scope of review)
Run on: any machine with kubectl access.
kubectl get ns
You will need to review each namespace listed.
2. Check PodSecurityPolicies (if PSP is enabled)
kubectl get psp
For each PSP, inspect the allowed runAsUser strategies:
kubectl get psp <psp-name> -o yaml
Look under .spec.runAsUser:
-
Compliant examples:
rule: MustRunAsNonRootrule: MustRunAswithrangeswhere allmin/maxare > 0 (no range including UID 0).
-
Problematic indicators:
rule: RunAsAnyrule: MustRunAswith any range including0(for examplemin: 0ormax: 0or a range spanning 0).
Also check how PSPs are bound to namespaces via RBAC:
kubectl get role,rolebinding,clusterrole,clusterrolebinding -A | grep -E 'psp|podsecuritypolicy' -i
A problem exists where a namespace’s service accounts can use a PSP that allows RunAsAny or UID 0.
3. Check Pod Security Standards labels on namespaces (if used)
kubectl get ns --show-labels
Look for labels like pod-security.kubernetes.io/enforce, pod-security.kubernetes.io/audit, pod-security.kubernetes.io/warn.
-
Compliant indicators:
- Namespaces used for general workloads have
enforceset tobaselineorrestricted. - For stronger guarantees against root containers,
restrictedis preferred.
- Namespaces used for general workloads have
-
Problematic indicators:
- Missing
pod-security.kubernetes.io/enforcelabel on application namespaces. enforce=privilegedor no label at all, combined with no other admission mechanism controlling user IDs.
- Missing
Labels alone do not guarantee non-root; they must be interpreted with the Pod Security Standards definition. Namespaces without any restrictive labels need closer manual review.
4. Inspect common policy controllers (Kyverno, Gatekeeper) for runAsUser rules
If you use Kyverno:
kubectl get clusterpolicy,policy -A
kubectl get clusterpolicy -o yaml | grep -n "runAsUser" -n
kubectl get policy -A -o yaml | grep -n "runAsUser" -n
If you use Gatekeeper:
kubectl get k8spspallowprivilegeescalation -A 2>/dev/null
kubectl get k8spsprunasuser -A 2>/dev/null
kubectl get constraints.constraints.gatekeeper.sh -A
kubectl get k8spsprunasuser.constraints.gatekeeper.sh -A -o yaml 2>/dev/null
Review any policies/constraints that reference runAsUser, runAsNonRoot, or Pod security context:
-
Compliant indicators:
- Policies that deny pods where
securityContext.runAsNonRoot=falseorrunAsUser=0. - Policies that require
runAsNonRoot=trueorrunAsUserwithin non-zero ranges.
- Policies that deny pods where
-
Problematic indicators:
- No policies referencing
runAsUserorrunAsNonRoot. - Policies scoped only to a subset of namespaces, leaving important namespaces without protection.
- No policies referencing
5. Spot-check workloads in each namespace for actual root usage
This cannot replace policy, but helps you see current behavior.
List all pods in a namespace:
kubectl get pods -n <namespace>
Inspect a pod spec:
kubectl get pod <pod-name> -n <namespace> -o yaml
Look at:
.spec.securityContext.runAsUser.spec.securityContext.runAsNonRoot.spec.containers[*].securityContext.runAsUser.spec.containers[*].securityContext.runAsNonRoot
Problematic indicators:
runAsUser: 0anywhere.runAsNonRoot: false.- No
runAsUser/runAsNonRootat pod or container level, and you know there is no enforced policy at namespace/cluster level (from steps 2–4); in that case, root containers are possible and not prevented by admission control.
Because this check is MANUAL, you must decide, based on the policy mechanisms (PSP, PSS labels, Kyverno/Gatekeeper, or others) and the namespaces’ purpose, whether:
- The namespace has an admission policy that effectively enforces
MustRunAsNonRootorMustRunAswith UID ranges excluding 0, or - It is currently allowing root containers and needs a stricter policy.
Automation
#!/usr/bin/env bash
# Report namespaces whose pod security policy (or equivalent) allows running as root.
# Run on any machine with kubectl access.
set -euo pipefail
# 1) Snapshot of PodSecurity admission labels (PSa) – modern clusters (1.25+)
echo "=== PodSecurity Admission labels by namespace ==="
kubectl get ns -o json \
| jq -r '
.items[]
| {
name: .metadata.name,
enforce: .metadata.labels."pod-security.kubernetes.io/enforce",
enforce_version: .metadata.labels."pod-security.kubernetes.io/enforce-version"
}
| @tsv' \
| awk -F'\t' 'BEGIN {
printf "%-32s %-12s %-10s\n", "NAMESPACE", "ENFORCE", "VERSION"
print "---------------------------------------------------------------------"
}
{
ns=$1; enforce=$2; ver=$3;
if (enforce == "" ) enforce="-";
if (ver == "" ) ver="-";
printf "%-32s %-12s %-10s\n", ns, enforce, ver
}'
cat <<'EOF'
[INTERPRETATION: PodSecurity Admission]
- Namespaces with ENFORCE of "privileged" or "-" (unset) can admit root containers
unless additional mechanisms (PSP, PSP-equivalent, or admission webhooks) deny them.
- Namespaces with ENFORCE of "restricted" or a hardened custom profile are less likely
to allow root, but you must still review any other policies below.
EOF
# 2) PodSecurityPolicy (PSP) – for clusters that still have PSP enabled
echo "=== PodSecurityPolicies: runAsUser settings ==="
if kubectl get psp >/dev/null 2>&1; then
kubectl get psp -o json \
| jq -r '
.items[]
| {
name: .metadata.name,
rule: ( .spec.runAsUser.rule // "UNSET" ),
ranges: ( .spec.runAsUser.ranges // [] )
}
| @base64' \
| while read -r line; do
obj=$(echo "$line" | base64 -d)
name=$(echo "$obj" | jq -r '.name')
rule=$(echo "$obj" | jq -r '.rule')
ranges=$(echo "$obj" | jq -c '.ranges')
problem=""
if [ "$rule" = "RunAsAny" ] || [ "$rule" = "UNSET" ]; then
problem="YES (RunAsAny/UNSET allows root)"
elif [ "$rule" = "MustRunAsNonRoot" ]; then
problem="OK (MustRunAsNonRoot)"
elif [ "$rule" = "MustRunAs" ]; then
# Check if any range includes UID 0
includes_zero=$(echo "$ranges" | jq 'map(select(.min <= 0 and .max >= 0)) | length')
if [ "$includes_zero" -gt 0 ]; then
problem="YES (MustRunAs ranges include UID 0)"
else
problem="OK (MustRunAs ranges exclude UID 0)"
fi
else
problem="REVIEW (unknown rule)"
fi
printf "%-40s %-18s %s\n" "$name" "$rule" "$problem"
done
cat <<'EOF'
[INTERPRETATION: PodSecurityPolicy]
- "RunAsAny" or "UNSET": PROBLEM – these PSPs allow containers to run as root (UID 0).
- "MustRunAsNonRoot": OK – aligns with the requirement to avoid UID 0.
- "MustRunAs" with any range where min <= 0 <= max: PROBLEM – UID 0 is allowed.
- "MustRunAs" where all ranges exclude 0: OK – meets the benchmark intent.
Next, check which namespaces and service accounts are bound to PROBLEM PSPs
(see ClusterRoleBinding/RoleBinding associations).
EOF
else
echo "No PodSecurityPolicies found (kubectl get psp failed); skipping PSP analysis."
fi
# 3) Namespace-level securityContext defaults (optional signal)
echo "=== Namespace-level Pod securityContext defaults (if any) ==="
kubectl get ns -o json \
| jq -r '
.items[]
| {
name: .metadata.name,
runAsNonRoot: .metadata.annotations."pod-security.kubernetes.io/default-run-as-non-root",
runAsUser: .metadata.annotations."pod-security.kubernetes.io/default-run-as-user"
}
| @tsv' \
| awk -F'\t' 'BEGIN {
printf "%-32s %-22s %-18s\n", "NAMESPACE", "DEFAULT runAsNonRoot", "DEFAULT runAsUser"
print "--------------------------------------------------------------------------------"
}
{
ns=$1; nonroot=$2; uid=$3;
if (nonroot == "" ) nonroot="-";
if (uid == "" ) uid="-";
printf "%-32s %-22s %-18s\n", ns, nonroot, uid
}'
cat <<'EOF'
[INTERPRETATION: Namespace defaults]
- These annotations are NOT standard and may not exist in your cluster; they are shown
only if present as hints. Any default UID of 0 or an explicit default that allows 0
should be treated as a problem and reviewed.
EOF
# 4) Sample of existing Pods that actually run as root (evidence for review)
echo "=== Sample of running Pods that are effectively root (UID 0) ==="
# This inspects securityContext but does NOT exec into containers.
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| {
ns: .metadata.namespace,
pod: .metadata.name,
sc: .spec.securityContext,
containers: .spec.containers
}
| . as $pod
| $pod.containers[]
| {
ns: $pod.ns,
pod: $pod.pod,
cname: .name,
csc: .securityContext,
pod_sc: $pod.sc
}
| {
ns,
pod,
cname,
# Effective runAsNonRoot / runAsUser calculation:
c_runAsNonRoot: .csc.runAsNonRoot,
c_runAsUser: .csc.runAsUser,
pod_runAsNonRoot: .pod_sc.runAsNonRoot,
pod_runAsUser: .pod_sc.runAsUser
}
| select(
# Flag containers that clearly request UID 0, or have *no* non-root guarantee.
( ( .c_runAsUser == 0 ) or ( .pod_runAsUser == 0 ) )
or
(
( (.c_runAsNonRoot // false) == false )
and ( (.pod_runAsNonRoot // false) == false )
)
)
| @tsv' \
| awk -F'\t' 'BEGIN {
printf "%-20s %-40s %-25s\n", "NAMESPACE", "POD", "CONTAINER (REVIEW)"
print "--------------------------------------------------------------------------------------"
}
{
printf "%-20s %-40s %-25s\n", $1, $2, $3
}'
cat <<'EOF'
[INTERPRETATION: Existing Pods]
- Listed containers are *candidates* for running as root because:
* They explicitly set runAsUser: 0 at pod or container level, OR
* Neither pod nor container enforces runAsNonRoot=true.
- This is evidence for human review. Not all listed workloads must be changed
(some system components may legitimately require root), but they should be
justified and documented.
EOF
echo "=== Completed root-admission policy and workload inventory ==="
echo "Review the sections marked PROBLEM or REVIEW and decide per-namespace policy:"
echo "- Ensure policy uses MustRunAsNonRoot OR MustRunAs with UID ranges that exclude 0."
Output indicating a problem (requires review/decision):
- PodSecurity Admission:
- Namespaces with
ENFORCEofprivilegedor-(unset).
- Namespaces with
- PodSecurityPolicy:
- PSP lines ending with
YES (RunAsAny/UNSET allows root)or
YES (MustRunAs ranges include UID 0)orREVIEW (unknown rule).
- PSP lines ending with
- Namespace defaults:
- Any annotation (if present) that sets a default UID of
0or otherwise includes0.
- Any annotation (if present) that sets a default UID of
- Existing Pods:
- Any row in the “Sample of running Pods that are effectively root (UID 0)” table.