Skip to main content

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

Manual Steps
  1. 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 ns

    Decide which namespaces must enforce non-root (e.g., all except: kube-system, kube-public, kube-node-lease, default if needed for legacy workloads).

  2. For each target namespace, inspect existing Pod Security/Admission policy mechanisms to see whether they already prevent root containers:

    # Pod Security admission labels
    kubectl 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 || true
    kubectl get cpol,pol --all-namespaces 2>/dev/null || true

    Review whether any mechanism enforces runAsNonRoot: true or runAsUser ranges that exclude UID 0.

  3. For each policy mechanism in use, examine the detailed rules to confirm they require non-root UIDs:

    # Example: PodSecurityPolicy details
    kubectl get psp <psp-name> -o yaml

    # Example: Gatekeeper constraint
    kubectl get <constraint-kind> <constraint-name> -n <ns> -o yaml

    # Example: Kyverno policy
    kubectl get cpol <policy-name> -o yaml

    Verify that they enforce either MustRunAsNonRoot (or equivalent runAsNonRoot: true) or MustRunAs with UID ranges that do not include 0 for containers and pods in the target namespaces.

  4. 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: MustRunAsNonRoot

    or:

    runAsUser:
    rule: MustRunAs
    ranges:
    - min: 1000
    max: 65535

    Then bind this policy (or the equivalent Gatekeeper/Kyverno policy) so that it applies to all service accounts in the target namespace.

  5. 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 == false
    or .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.

  6. 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 yaml
      kubectl 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:
      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 that this creation fails and the error message indicates the non‑root requirement from your policy.
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: MustRunAsNonRoot
    • rule: MustRunAs with ranges where all min/max are > 0 (no range including UID 0).
  • Problematic indicators:

    • rule: RunAsAny
    • rule: MustRunAs with any range including 0 (for example min: 0 or max: 0 or 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 enforce set to baseline or restricted.
    • For stronger guarantees against root containers, restricted is preferred.
  • Problematic indicators:

    • Missing pod-security.kubernetes.io/enforce label on application namespaces.
    • enforce=privileged or no label at all, combined with no other admission mechanism controlling user IDs.

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=false or runAsUser=0.
    • Policies that require runAsNonRoot=true or runAsUser within non-zero ranges.
  • Problematic indicators:

    • No policies referencing runAsUser or runAsNonRoot.
    • Policies scoped only to a subset of namespaces, leaving important namespaces without protection.

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: 0 anywhere.
  • runAsNonRoot: false.
  • No runAsUser/runAsNonRoot at 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 MustRunAsNonRoot or MustRunAs with 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 ENFORCE of privileged or - (unset).
  • PodSecurityPolicy:
    • PSP lines ending with YES (RunAsAny/UNSET allows root) or
      YES (MustRunAs ranges include UID 0) or REVIEW (unknown rule).
  • Namespace defaults:
    • Any annotation (if present) that sets a default UID of 0 or otherwise includes 0.
  • Existing Pods:
    • Any row in the “Sample of running Pods that are effectively root (UID 0)” table.

Additional Reading: