Skip to main content

Minimize The 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

  • APRA CPS 234 (Australia)
  • BSI C5 (Germany)
  • Brazil LGPD
  • CCPA / CPRA (California)
  • CIS Critical Security Controls v8
  • CIS GKE
  • CMMC 2.0
  • CSA Cloud Controls Matrix v4
  • DPDPA
  • Digital Operational Resilience Act (EU)
  • Essential 8
  • ISO/IEC 27017
  • ISO/IEC 27018
  • ISO/IEC 27701
  • KSA PDPL
  • MAS Technology Risk Management (Singapore)
  • MITRE ATT&CK (Cloud)
  • NIS2 Directive
  • NIST SP 800-171
  • NYDFS 23 NYCRR 500
  • SWIFT Customer Security Controls Framework
  • Sarbanes-Oxley IT General Controls
  • UK NCSC Cyber Assessment Framework

Triage and Remediation

Remediation

Manual Steps
  1. On any machine with kubectl access, list all namespaces and identify those that should be protected from root containers (typically all except system namespaces you explicitly exempt):

    kubectl get ns

    Decide which namespaces must disallow root containers (e.g. prod, staging, default, etc.).

  2. For each selected namespace, review existing Pod Security admission labels (if using built‑in Pod Security Standards) to understand current posture:

    kubectl get ns <namespace> -o jsonpath='{.metadata.labels}' | jq .

    Note any pod-security.kubernetes.io/* labels that might already restrict root (e.g. restricted).

  3. If your cluster still uses PodSecurityPolicy (PSP), list and inspect all PSPs to see whether they restrict root via spec.runAsUser.rule:

    kubectl get psp
    kubectl get psp -o yaml | grep -A10 -n "runAsUser"

    Confirm whether any PSP has spec.runAsUser.rule: MustRunAsNonRoot or MustRunAs with UID ranges that exclude 0, and which ServiceAccounts / namespaces are bound to them (via RBAC Role/ClusterRole and bindings).

  4. For each protected namespace, identify ServiceAccounts used by workloads and see what PSP (if any) they can use:

    kubectl get sa -n <namespace>
    kubectl get role,rolebinding,clusterrole,clusterrolebinding -n <namespace> -o yaml | grep -n "podsecuritypolicies" -A5

    From these bindings, determine which PSPs apply to each ServiceAccount and whether they enforce non‑root execution as required.

  5. Based on the above, decide one of the following per namespace:

    • Tighten or create a PSP with spec.runAsUser.rule: MustRunAsNonRoot or MustRunAs with UID ranges not including 0, and bind it only to the ServiceAccounts in that namespace that should never run root.
    • If PSP is not used or is deprecated in your environment, implement an equivalent policy via your chosen admission controller (e.g. Pod Security Standards restricted level, OPA Gatekeeper, Kyverno), ensuring their rules/constraints prevent runAsUser: 0 or allowPrivilegeEscalation: true and root images where appropriate.
  6. After changes, deploy or update a representative workload in each affected namespace and verify admission behavior:

    • Try to create a pod that explicitly runs as root (this should now be rejected):
      kubectl apply -n <namespace> -f - <<'EOF'
      apiVersion: v1
      kind: Pod
      metadata:
      name: test-root-pod
      spec:
      containers:
      - name: c
      image: nginx:stable
      securityContext:
      runAsUser: 0
      EOF
    • Confirm it is denied (no Running pod should appear):
      kubectl get pod -n <namespace> test-root-pod
Using kubectl
# 1) List all namespaces
# Run on: any machine with kubectl access
kubectl get ns -o name

Review each namespace’s policies and workloads.


1. Check for PodSecurityPolicy (if enabled)

# List all PodSecurityPolicies
kubectl get psp -o yaml

What to look for in each PSP:

  • .spec.runAsUser.rule missing or set to RunAsAny
    → Indicates pods using this PSP may run as root.
  • If .spec.runAsUser.rule: MustRunAs, check .spec.runAsUser.ranges
    → If any range includes 0 (for example min: 0 or 0-65535), root is allowed.
  • If .spec.runAsUser.rule: MustRunAsNonRoot
    → This PSP is aligned with the recommendation.

If you have PSPs that allow root, identify which namespaces/service accounts use them:

# Show RBAC bindings that reference PSPs
kubectl get clusterrole,role -A -o yaml | grep -n "use" -n -A5 -B5
kubectl get clusterrolebinding,rolebinding -A -o yaml | grep -n "podsecuritypolicies" -A5 -B5

Bindings that allow use of a PSP which permits root are potential problems, especially in non-admin namespaces.


2. Check Pod-level securityContext (current workloads)

# List pods with their runAsUser / runAsNonRoot at pod level
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.securityContext.runAsUser}{"\t"}{.spec.securityContext.runAsNonRoot}{"\n"}{end}' \
| sort

Problem indicators:

  • runAsUser is 0 → the pod explicitly requests root.
  • runAsNonRoot is empty/false and nothing else prevents root → pod may run as root via container image defaults or container-level settings.

3. Check container-level securityContext (current workloads)

# Show per-container runAsUser / runAsNonRoot
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .spec.containers[*]}{.name}{"\t"}{.securityContext.runAsUser}{"\t"}{.securityContext.runAsNonRoot}{"\n"}{end}{"\n"}{end}' \
| sort

Problem indicators per container:

  • runAsUser is 0 → explicit root container.
  • Both pod-level and container-level runAsNonRoot unset/false and no admission control enforcing non-root → container likely allowed to run as root.

4. Inspect deployments/statefulsets for templates that allow root

# Deployments
kubectl get deploy -A -o yaml | grep -n "securityContext" -n -A5 -B5

# StatefulSets
kubectl get statefulset -A -o yaml | grep -n "securityContext" -n -A5 -B5

# DaemonSets
kubectl get daemonset -A -o yaml | grep -n "securityContext" -n -A5 -B5

Within each pod template:

  • securityContext.runAsUser: 0 at pod or container level → potentially problematic.
  • securityContext.runAsNonRoot: true and no runAsUser: 0 → aligned with recommendation.
  • No securityContext at all → admission policy decides; if none, workloads may run as root.

5. For each namespace, summarize root usage

For a quick per-namespace view:

# Pods that explicitly set runAsUser: 0 at pod or container level
kubectl get pods -A -o json \
| jq -r '
.items[]
| {
ns: .metadata.namespace,
pod: .metadata.name,
podRunAsUser: .spec.securityContext.runAsUser,
containers: [ .spec.containers[]
| {name, runAsUser: (.securityContext.runAsUser // null)}
]
}
| select(
.podRunAsUser == 0
or ([.containers[].runAsUser] | map(.==0) | any)
)
| "\(.ns)\t\(.pod)"
' | sort | uniq

Any listed pod is explicitly configured to run as root. Those namespaces and workloads need human review to decide whether root is justified (e.g., node agents, CNI components) or should be changed.

Automation
#!/usr/bin/env bash
# Report namespaces and workloads that may allow or run root containers.
# Run on any machine with kubectl access and current KUBECONFIG context.

set -euo pipefail

echo "=== Cluster-wide admission / security policy overview ==="

echo
echo "1) Namespaces without any PodSecurity admission label (may allow root by default)"
kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{range $k,$v := .metadata.labels}{if and (or (eq $k "pod-security.kubernetes.io/enforce") (eq $k "pod-security.kubernetes.io/audit") (eq $k "pod-security.kubernetes.io/warn"))}{printf "%s=%s " $k $v}{end}{end}{"\n"}{end}' \
| awk 'NF==1 {print $1}' \
| sed 's/^/ - /' || true

cat <<'EOF'

Explanation:
- Any namespace listed above has no Pod Security Admission labels; by default, pods
in these namespaces may be able to run as root unless restricted by another
mechanism (e.g., Gatekeeper, legacy PSP).

EOF

echo
echo "2) Namespaces labeled with PodSecurity levels that may allow root containers"
echo " (labels with level < baseline or unset version are suspicious)"
kubectl get ns -o json --show-managed-fields=false \
| jq -r '
.items[]
| {
name: .metadata.name,
enforce: .metadata.labels["pod-security.kubernetes.io/enforce"],
audit: .metadata.labels["pod-security.kubernetes.io/audit"],
warn: .metadata.labels["pod-security.kubernetes.io/warn"]
}
| select(
(.enforce == null or .enforce == "privileged" or .enforce == "restricted" | not)
or (.audit != null and .audit == "privileged")
or (.warn != null and .warn == "privileged")
)
| " - " + .name
+ " (enforce=" + ( .enforce // "none" )
+ ", audit=" + ( .audit // "none" )
+ ", warn=" + ( .warn // "none" ) + ")"
' || true

cat <<'EOF'

Explanation:
- Namespaces listed above either:
* have no 'enforce' level, or
* are enforced/audited/warned at 'privileged', or
* have non-standard/empty levels.
- These namespaces should be reviewed; policies here may permit root containers.

EOF

echo
echo "3) Pods with explicit root or no runAsNonRoot / runAsUser (namespaces & workloads)"
echo " (scans Deployments, StatefulSets, DaemonSets, Jobs, CronJobs, and standalone Pods)"

# Helper: list all workload types we care about
WORKLOADS=(
"pods"
"deployments.apps"
"statefulsets.apps"
"daemonsets.apps"
"jobs.batch"
"cronjobs.batch"
)

for kind in "${WORKLOADS[@]}"; do
echo
echo "=== Kind: ${kind} ==="
# We treat both pod-level and container-level securityContext.
# This flags:
# - runAsUser: 0 at pod or container level
# - runAsNonRoot: false or unset when user could default to 0
kubectl get "${kind}" -A -o json --show-managed-fields=false 2>/dev/null \
| jq -r --arg KIND "${kind}" '
.items[]
| . as $obj
| .spec.template // .spec // . as $spec # CronJob/Job vs Pod template vs Pod
| .metadata.namespace as $ns
| .metadata.name as $name
| ($obj.kind // $KIND) as $k
| ($spec.spec // .spec) as $ps
| (
# Pod-level securityContext
$ps.securityContext as $psc
|
# Build a list of containers (init + app)
( ($ps.initContainers // []) + ($ps.containers // []) ) as $containers
|
[ $containers[]
| . as $c
| .name as $cname
| .securityContext as $csc
|
# Determine effective runAsUser and runAsNonRoot
# Container-level overrides pod-level.
(
if $csc.runAsUser != null then $csc.runAsUser
elif $psc.runAsUser != null then $psc.runAsUser
else null
end
) as $effUser
|
(
if $csc.runAsNonRoot != null then $csc.runAsNonRoot
elif $psc.runAsNonRoot != null then $psc.runAsNonRoot
else null
end
) as $effNonRoot
|
{
cname: $cname,
effUser: $effUser,
effNonRoot: $effNonRoot,
# Flag if explicitly root
isRootUser: ($effUser != null and ($effUser|tonumber) == 0),
# Flag if potentially root (no non-root guarantee)
maybeRoot: (
($effUser == null) and
(
$effNonRoot == null or $effNonRoot == false
)
)
}
] as $analysis
|
select(
any($analysis[]; .isRootUser == true or .maybeRoot == true)
)
|
" - " + $ns + "\t" + $k + "/" + $name + "\n" +
( $analysis[]
| select(.isRootUser or .maybeRoot)
| " container=" + .cname
+ " effUser=" + ( ( .effUser | tostring ) // "null" )
+ " effNonRoot=" + ( ( .effNonRoot | tostring ) // "null" )
+ (if .isRootUser then " <-- EXPLICIT ROOT" else "" end)
+ (if (.maybeRoot and ( .isRootUser | not )) then " <-- NO NON-ROOT GUARANTEE" else "" end)
)
)
' 2>/dev/null || true
done

cat <<'EOF'

Explanation:
- Lines with "EXPLICIT ROOT":
* Pod/container has runAsUser: 0 effective -> very likely running as root.
* These are strong candidates for remediation.
- Lines with "NO NON-ROOT GUARANTEE":
* No effective runAsUser and no runAsNonRoot: true.
* Container may still run as non-root (if the image default user is non-root),
but the policy does not prevent root; review needed.

EOF

echo
echo "4) Optional: Gatekeeper / Kyverno policies that restrict root (if present)"
echo
echo "Gatekeeper (constraints):"
kubectl get constraints.constraints.gatekeeper.sh -A 2>/dev/null || echo " (none or Gatekeeper not installed)"

echo
echo "Kyverno (ClusterPolicies and Policies):"
kubectl get clusterpolicies.kyverno.io -A 2>/dev/null || echo " (no ClusterPolicies or Kyverno not installed)"
kubectl get policies.kyverno.io -A 2>/dev/null || echo " (no namespace Policies or Kyverno not installed)"

cat <<'EOF'

Explanation:
- If you have Gatekeeper or Kyverno installed, review the policies to see if they
enforce non-root containers (e.g., disallow runAsUser: 0 or require runAsNonRoot: true).

EOF

echo
echo "=== Review guidance summary ==="
cat <<'EOF'
Problem indicators you should pay attention to:

1) Namespaces:
- Listed in section (1): no PodSecurity labels -> may permit root containers.
- Listed in section (2) with enforce/audit/warn at 'privileged' or unset.

2) Workloads (section 3):
- Any container marked "EXPLICIT ROOT":
* Effective runAsUser is 0 (from pod or container securityContext).
- Any container marked "NO NON-ROOT GUARANTEE":
* No effective runAsUser and runAsNonRoot is null/false.
* Requires manual decision:
- If the image defaults to a root user, this is a clear issue.
- If the image defaults to non-root, decide whether to codify that via
runAsNonRoot: true / non-root UID ranges.

Use this report to:
- Prioritize namespaces/workloads for hardening.
- Design or adjust Pod Security Admission labels or other policies to minimize
the admission of root containers without breaking workloads.

EOF

Additional Reading: