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
Remediation
Manual Steps
-
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 nsDecide which namespaces must disallow root containers (e.g.
prod,staging,default, etc.). -
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). -
If your cluster still uses PodSecurityPolicy (PSP), list and inspect all PSPs to see whether they restrict root via
spec.runAsUser.rule:kubectl get pspkubectl get psp -o yaml | grep -A10 -n "runAsUser"Confirm whether any PSP has
spec.runAsUser.rule: MustRunAsNonRootorMustRunAswith UID ranges that exclude0, and which ServiceAccounts / namespaces are bound to them (via RBACRole/ClusterRoleand bindings). -
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" -A5From these bindings, determine which PSPs apply to each ServiceAccount and whether they enforce non‑root execution as required.
-
Based on the above, decide one of the following per namespace:
- Tighten or create a PSP with
spec.runAsUser.rule: MustRunAsNonRootorMustRunAswith UID ranges not including0, 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
restrictedlevel, OPA Gatekeeper, Kyverno), ensuring their rules/constraints preventrunAsUser: 0orallowPrivilegeEscalation: trueand root images where appropriate.
- Tighten or create a PSP with
-
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: v1kind: Podmetadata:name: test-root-podspec:containers:- name: cimage: nginx:stablesecurityContext:runAsUser: 0EOF
- Confirm it is denied (no
Runningpod should appear):kubectl get pod -n <namespace> test-root-pod
- Try to create a pod that explicitly runs as root (this should now be rejected):
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.rulemissing or set toRunAsAny
→ Indicates pods using this PSP may run as root.- If
.spec.runAsUser.rule: MustRunAs, check.spec.runAsUser.ranges
→ If any range includes0(for examplemin: 0or0-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:
runAsUseris0→ the pod explicitly requests root.runAsNonRootis 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:
runAsUseris0→ explicit root container.- Both pod-level and container-level
runAsNonRootunset/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: 0at pod or container level → potentially problematic.securityContext.runAsNonRoot: trueand norunAsUser: 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