Skip to main content

Minimize The Admission Of Root Containers

More Info:

Containers running as root (UID 0) increase the impact of a container escape. Require workloads to run as non-root.

Risk Level

High

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. List all namespaces and identify those that should host only non-root workloads

    • Run on: any machine with kubectl access
    • Command:
      kubectl get ns
    • Decide which namespaces must be restricted (for example: prod, staging, or all except explicitly “infra/ops” namespaces that may need privileged containers).
  2. Review existing PodSecurity admission / security policies per target namespace

    • Run on: any machine with kubectl access
    • Commands (use what’s relevant to your cluster):
      • For built-in Pod Security Admission labels:
        kubectl get ns --show-labels
        Look for labels like pod-security.kubernetes.io/enforce=restricted (which already forbids root in most cases).
      • For PodSecurityPolicy (legacy clusters):
        kubectl get psp -o yaml
        Review runAsUser fields; confirm rule: MustRunAsNonRoot or rule: MustRunAs with all ranges having min > 0.
      • For common policy engines (example: Gatekeeper):
        kubectl get constraints --all-namespaces
        Review any constraints controlling runAsUser / runAsNonRoot.
  3. Inspect current pod specs to find workloads that run as root or allow root

    • Run on: any machine with kubectl access
    • Command (namespaced, repeat for each important namespace):
      NAMESPACE=default
      kubectl get pods -n "$NAMESPACE" -o json \
      | jq -r '.items[]
      | .metadata.name as $p
      | (.spec.securityContext.runAsUser // "namespace-default") as $podUID
      | (.spec.securityContext.runAsNonRoot // "namespace-default") as $podNonRoot
      | .spec.containers[]
      | {
      pod: $p,
      container: .name,
      cRunAsUser: (.securityContext.runAsUser // $podUID),
      cRunAsNonRoot: (.securityContext.runAsNonRoot // $podNonRoot)
      }' \
      | jq -r 'select(.cRunAsUser == 0 or .cRunAsNonRoot == false or .cRunAsNonRoot == "namespace-default")'
    • Use this output to identify pods that are explicitly root or unconstrained (no non-root requirement and no non-root namespace default).
  4. Design and apply namespace-level policy requiring non-root UIDs

    • Decide whether you can adopt a strict policy (no root anywhere in that namespace) or need exceptions.
    • For Pod Security Admission (recommended where available), raise the namespace to at least restricted (which enforces non-root by default) or confirm it is already at that level:
      NAMESPACE=default
      kubectl label ns "$NAMESPACE" \
      pod-security.kubernetes.io/enforce=restricted \
      pod-security.kubernetes.io/audit=restricted \
      pod-security.kubernetes.io/warn=restricted \
      --overwrite
    • For policy engines (e.g., Gatekeeper/Kyverno), create or update a policy in each target namespace so that containers must set runAsNonRoot: true or runAsUser with UID > 0, and exclude 0 from any allowed ranges (done via their CRDs and admission rules).
  5. Refactor or exempt workloads that cannot yet run as non-root

    • Using the pods discovered in step 3, update their deployment/statefulset/daemonset manifests to set non-root contexts:
      • Example change in the pod template:
        securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        And, if needed, per-container overrides under spec.template.spec.containers[].securityContext.
    • If some workloads absolutely require root, formally document them and either:
      • move them into a separate, tightly controlled namespace without the non-root policy, or
      • configure scoped policy exceptions (policy engine exemptions or narrower admission rules) only for those workloads.
  6. Verify enforcement and absence of new root-running pods

    • Run on: any machine with kubectl access
    • Attempt to create a pod that runs as root in a restricted namespace; it should be rejected:
      cat <<'EOF' >/tmp/root-pod-test.yaml
      apiVersion: v1
      kind: Pod
      metadata:
      name: root-pod-test
      spec:
      securityContext:
      runAsUser: 0
      containers:
      - name: test
      image: busybox
      command: ["sh", "-c", "id -u && sleep 3600"]
      EOF

      NAMESPACE=default
      kubectl apply -n "$NAMESPACE" -f /tmp/root-pod-test.yaml
    • Confirm that admission rejects this pod (error message from PSA or your policy engine).
    • Re-run the inspection from step 3 to ensure no newly created pods are running as root or without a non-root requirement.
Using kubectl

Using kubectl

Run these commands from any machine with kubectl access.

1. List all namespaces to scope your review

kubectl get ns

Use this to plan which namespaces you will check, especially those where user workloads run.


2. Inspect PodSecurity admission / PodSecurityPolicy-like controls per namespace

2.1. Check for Pod Security standards labels (if PodSecurity admission is enabled)
kubectl get ns --show-labels

Look for labels such as:

  • pod-security.kubernetes.io/enforce=baseline or restricted
  • pod-security.kubernetes.io/enforce-version=v1.30 (version may differ)

Potential problems:

  • Namespaces with no pod-security.kubernetes.io/enforce label.
  • Namespaces where pod-security.kubernetes.io/enforce=privileged.
  • Namespaces intended to be restricted that have no PodSecurity labels at all.

These indicate that nothing is preventing root containers from being admitted in those namespaces.


For Gatekeeper (OPA):

kubectl get k8spsps,configs,constraints.constraints.gatekeeper.sh,crd -A 2>/dev/null | grep -i runasnonroot || true
kubectl get constraints.constraints.gatekeeper.sh -A 2>/dev/null | grep -Ei 'runas(non)?root|securitycontext' || true

For Kyverno:

kubectl get cpol,pol -A 2>/dev/null | grep -Ei 'runas(non)?root|securitycontext' || true

Potential problems:

  • No constraint/policy objects that mention runAsNonRoot, runAsUser, or securityContext.
  • Policies exist but are audit or warn only (no enforce mode) where enforcement is desired.

This suggests no cluster-wide or namespace-wide policy enforcing MustRunAsNonRoot-style behavior.


3. Examine existing policies (where present) for MustRunAs / MustRunAsNonRoot semantics

Adjust the kind to match what you use (Gatekeeper/Kyverno/PSP-like CRDs).

Example for a Gatekeeper constraint enforcing non-root:

kubectl get <constraint-kind> -A -o yaml | less

Look for fields in the policy spec equivalent to:

  • runAsNonRoot: true
  • runAsUser strategy using MustRunAs with rule: MustRunAs and ranges excluding UID 0
  • Or explicit deny rules if runAsUser: 0 is set.

Potential problems:

  • No mention of runAsNonRoot or runAsUser in the policy.
  • MustRunAs ranges include 0 (for example min: 0).
  • Policies scoped only to a small subset of namespaces, leaving others unprotected.

4. Spot-check workloads’ securityContext to understand current behavior

This does not replace policy-based control, but helps assess risk.

For a given namespace (replace myns):

kubectl get pods -n myns -o yaml | grep -E "name:|runAsUser:|runAsNonRoot:" -n

Or more structured:

kubectl get pods -n myns -o jsonpath='{range .items[*]}{@.metadata.name}{"\t"}{@.spec.securityContext.runAsNonRoot}{"\t"}{@.spec.securityContext.runAsUser}{"\n"}{end}'

Potential problems:

  • Pods with runAsUser: 0.
  • Pods with no runAsUser and no runAsNonRoot, combined with images that default to root (this needs human review of the images).
  • Absence of any namespace-level policy while such pods exist.

5. Verify absence or presence of PodSecurityPolicy (older clusters only)

If your cluster might still have PSP:

kubectl get podsecuritypolicies.policy 2>/dev/null

If PSPs exist, inspect them:

kubectl get podsecuritypolicies.policy -o yaml | less

In the PSP spec, look for runAsUser:

  • rule: MustRunAsNonRoot or
  • rule: MustRunAs with ranges where all min > 0.

Potential problems:

  • runAsUser.rule is RunAsAny.
  • MustRunAs ranges include UID 0.
  • No PSP bound via RBAC to the service accounts in the reviewed namespaces.

These commands only surface configuration; they do not decide or apply the correct policy. Use the outputs to determine where you must define or tighten policies so that namespaces have an enforced strategy equivalent to MustRunAsNonRoot or MustRunAs with UID ranges that exclude 0.

Automation
#!/usr/bin/env bash
# Report pods and controllers that can run as root (UID 0) or lack non-root restrictions.
# Run on any machine with kubectl access and a current context.

set -euo pipefail

echo "=== Cluster-wide Pod Security Settings Report (focus: root containers) ==="
echo "Timestamp: $(date)"
echo

###############################################################################
# 1. Namespaces WITHOUT any PodSecurityPolicy/PodSecurityAdmission-equivalent
# namespace-level policy label that enforces non-root.
# NOTE: You must adapt this section to your cluster's admission controller:
# - For Pod Security Admission (built-in): uses pod-security.kubernetes.io/*
# - For OPA Gatekeeper/Kyverno/etc.: list and review their constraints separately.
###############################################################################

echo "==[1] Namespaces and Pod Security labels (for built-in Pod Security Admission) =="
kubectl get ns --show-labels | sed 's/,/\n /g'
echo
cat <<'EOF'
Review:
- For clusters using built-in Pod Security Admission:
* Namespaces that do NOT have labels like:
pod-security.kubernetes.io/enforce=baseline
pod-security.kubernetes.io/enforce=restricted
or equivalent, are likely NOT protected by a baseline/restricted policy and
may allow root containers. These are candidates for new policies.
- If you use another admission mechanism (Gatekeeper, Kyverno, etc.), review its
constraints separately; this script only surfaces PSA labels.
EOF
echo

###############################################################################
# 2. Pods with effective root user (runAsUser=0 or defaulting to root) or that
# do not explicitly forbid root (runAsNonRoot != true).
# This is for REVIEW ONLY; it does NOT mean "always unsafe", but these are
# the prime candidates to bring under policy.
###############################################################################

echo "==[2] Pods that can run as root or do not explicitly require non-root =="

# Show:
# - Namespace, Pod, Container
# - Pod-level securityContext.runAsUser / runAsNonRoot
# - Container-level securityContext.runAsUser / runAsNonRoot
# - ServiceAccount
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| . as $pod
| ($pod.spec.containers[]? // []), ($pod.spec.initContainers[]? // [])
| . as $c
| {
ns: $pod.metadata.namespace,
pod: $pod.metadata.name,
kind: (if ($pod.spec.initContainers // []) | index($c) then "initContainer" else "container" end),
cname: .name,
sa: ($pod.spec.serviceAccountName // "default"),
podRunAsUser: ($pod.spec.securityContext.runAsUser // "null"),
podRunAsNonRoot: ($pod.spec.securityContext.runAsNonRoot // "null"),
cRunAsUser: (.securityContext.runAsUser // "null"),
cRunAsNonRoot: (.securityContext.runAsNonRoot // "null")
}
| . as $r
| $r
| select(
# Flag if:
# 1) Explicitly configured to run as UID 0 at pod or container level
($r.podRunAsUser == 0 or $r.cRunAsUser == 0)
or
# 2) Neither pod nor container explicitly has runAsNonRoot=true,
# meaning root is not forbidden and may be used.
(
($r.podRunAsNonRoot != true)
and ($r.cRunAsNonRoot != true)
)
)
| [
.ns,
.pod,
.kind,
.cname,
"sa=" + .sa,
"podRunAsUser=" + (.podRunAsUser|tostring),
"podRunAsNonRoot=" + (.podRunAsNonRoot|tostring),
"cRunAsUser=" + (.cRunAsUser|tostring),
"cRunAsNonRoot=" + (.cRunAsNonRoot|tostring)
]
| @tsv
' | column -t

cat <<'EOF'

Interpretation of section [2] output (POTENTIAL PROBLEMS):

Each line shows:
NAMESPACE POD [container|initContainer] CONTAINER_NAME sa=SERVICEACCOUNT \
podRunAsUser=... podRunAsNonRoot=... cRunAsUser=... cRunAsNonRoot=...

Pay special attention to:
- podRunAsUser=0 or cRunAsUser=0
-> These are explicitly configured to run as root (UID 0). This is a direct
violation of "MustRunAsNonRoot" guidance unless there is a strong,
documented exception.

- podRunAsNonRoot=null AND cRunAsNonRoot=null with podRunAsUser=null AND cRunAsUser=null
-> Neither pod nor containers demand non-root, and no explicit UID is set.
In many images the default user is root, so these are likely root-capable.
These are candidates to:
* Set runAsNonRoot: true (pod and/or container level), or
* Set runAsUser to a non-zero UID.

The benchmark remediation requires you to create a policy for each namespace
ensuring that:
- runAsUser is configured via MustRunAsNonRoot, OR
- MustRunAs is used with a UID range that excludes 0.

This script only surfaces workloads that currently:
- Can run as root, or
- Do not clearly require non-root.
You must then:
- Decide where non-root is feasible,
- Adjust images/manifests accordingly,
- And enforce via your chosen admission policy mechanism.
EOF

Verification after you adjust policies/manifests:

  • Re-run the script on any machine with kubectl access.
  • A healthy state is when:
    • Namespaces have appropriate policy labels (or equivalent policy via another controller).
    • Section [2] returns:
      • No lines with podRunAsUser=0 or cRunAsUser=0.
      • As few as possible lines where runAsNonRoot is not true; any remaining should be documented exceptions.