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
Remediation
Manual Steps
-
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).
-
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:
Look for labels likekubectl get ns --show-labels
pod-security.kubernetes.io/enforce=restricted(which already forbids root in most cases). - For PodSecurityPolicy (legacy clusters):
Reviewkubectl get psp -o yaml
runAsUserfields; confirmrule: MustRunAsNonRootorrule: MustRunAswith allrangeshavingmin > 0. - For common policy engines (example: Gatekeeper):
Review any constraints controllingkubectl get constraints --all-namespaces
runAsUser/runAsNonRoot.
- For built-in Pod Security Admission labels:
-
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=defaultkubectl 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).
-
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=defaultkubectl 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: trueorrunAsUserwith UID > 0, and exclude 0 from any allowed ranges (done via their CRDs and admission rules).
-
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:
And, if needed, per-container overrides undersecurityContext:runAsNonRoot: truerunAsUser: 1000
spec.template.spec.containers[].securityContext.
- Example change in the pod template:
- 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.
- Using the pods discovered in step 3, update their deployment/statefulset/daemonset manifests to set non-root contexts:
-
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.yamlapiVersion: v1kind: Podmetadata:name: root-pod-testspec:securityContext:runAsUser: 0containers:- name: testimage: busyboxcommand: ["sh", "-c", "id -u && sleep 3600"]EOFNAMESPACE=defaultkubectl 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=baselineorrestrictedpod-security.kubernetes.io/enforce-version=v1.30(version may differ)
Potential problems:
- Namespaces with no
pod-security.kubernetes.io/enforcelabel. - 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.
2.2. If using Gatekeeper/Kyverno or similar, list policies related to runAsNonRoot
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, orsecurityContext. - Policies exist but are
auditorwarnonly (noenforcemode) 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: truerunAsUserstrategy usingMustRunAswithrule: MustRunAsandrangesexcluding UID 0- Or explicit deny rules if
runAsUser: 0is set.
Potential problems:
- No mention of
runAsNonRootorrunAsUserin the policy. MustRunAsranges include0(for examplemin: 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
runAsUserand norunAsNonRoot, 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: MustRunAsNonRootorrule: MustRunAswithrangeswhere allmin> 0.
Potential problems:
runAsUser.ruleisRunAsAny.MustRunAsranges 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=0orcRunAsUser=0. - As few as possible lines where
runAsNonRootis nottrue; any remaining should be documented exceptions.
- No lines with