Minimize Admission 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 EKS
- 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
-
Identify where root is allowed today
- On any machine with kubectl access:
kubectl get psp --all-namespaces -o yamlkubectl get clusterroles.rbac.authorization.k8s.io,roles.rbac.authorization.k8s.io \--all-namespaces -o yamlkubectl get clusterrolebindings.rbac.authorization.k8s.io,rolebindings.rbac.authorization.k8s.io \--all-namespaces -o yaml
- Review any existing PodSecurityPolicies (if still in use) and RBAC bindings to see which subjects (users, groups, service accounts) are allowed to use PSPs or policies that let containers run as root.
- On any machine with kubectl access:
-
Review current admission / security posture per namespace
- On any machine with kubectl access:
kubectl get nskubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels}{"\n"}{end}'kubectl get psp -o yaml 2>/dev/nullkubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.securityContext.runAsUser}{"\t"}{range .spec.containers[*]}{.name}{"="}{.securityContext.runAsUser}{";"}{end}{"\n"}{end}'
- Use these outputs to map which namespaces are enforcing non-root (via PSP, Pod Security Admission labels, or other policies) and which are not.
- On any machine with kubectl access:
-
Identify workloads currently running as root or likely to run as root
- On any machine with kubectl access:
kubectl get pods -A -o widekubectl get pods -A -o json | jq -r '.items[] |.metadata.namespace as $ns |.metadata.name as $pod |.spec.initContainers[]? as $c |[$ns, $pod, "init", $c.name,($c.securityContext.runAsUser // "inherit"),($c.securityContext.runAsNonRoot // "inherit")] | @tsv'kubectl get pods -A -o json | jq -r '.items[] |.metadata.namespace as $ns |.metadata.name as $pod |.spec.containers[] as $c |[$ns, $pod, "container", $c.name,($c.securityContext.runAsUser // "inherit"),($c.securityContext.runAsNonRoot // "inherit")] | @tsv'
- Cross-check with image documentation or Dockerfiles for key workloads to determine whether they expect to run as root.
- On any machine with kubectl access:
-
Decide namespace policy: where to forbid root and where (if anywhere) to allow it
- Using the evidence above, classify namespaces into:
- Strict: must not allow root containers (most application namespaces).
- Exception: temporarily or permanently require root (e.g., system add-ons, legacy apps, node-level agents).
- For each exception namespace, document:
- Business/technical justification for root.
- Owner and review date.
- Plan (if any) to migrate to non-root.
- Using the evidence above, classify namespaces into:
-
Implement or tighten admission controls to enforce non-root
- On any machine with kubectl access, for namespaces that should be strict, configure or adjust admission policy according to your environment:
- If you still use PodSecurityPolicy, ensure PSPs used by those namespaces have:
or:spec:runAsUser:rule: MustRunAsNonRootspec:runAsUser:rule: MustRunAsranges:- min: 1000max: 65535
- Update RBAC so only appropriate subjects can use any PSP that permits root.
- If using Pod Security Admission or a 3rd-party policy engine (OPA/Gatekeeper, Kyverno, etc.), configure equivalent policies that deny pods whose containers can run as UID 0, except in documented exception namespaces.
- If you still use PodSecurityPolicy, ensure PSPs used by those namespaces have:
- On any machine with kubectl access, for namespaces that should be strict, configure or adjust admission policy according to your environment:
-
Re-verify and monitor
- On any machine with kubectl access, re-check that policies and workloads align with your decisions:
kubectl get psp -o yaml 2>/dev/nullkubectl get pods -A -o json | jq -r '.items[] |.metadata.namespace as $ns |.metadata.name as $pod |.spec.containers[] as $c |[$ns, $pod, $c.name,($c.securityContext.runAsUser // "inherit"),($c.securityContext.runAsNonRoot // "inherit")] | @tsv'
- Attempt to deploy a test pod running as root into a strict namespace and confirm it is rejected by admission controls.
- On any machine with kubectl access, re-check that policies and workloads align with your decisions:
Using kubectl
Using kubectl
Run these commands from any machine with kubectl access.
1. List namespaces and existing PodSecurityPolicies (if PSPs are enabled)
kubectl get ns
kubectl get podsecuritypolicies.policy
Problem indication:
- No PodSecurityPolicies exist, or
- There is no PSP clearly intended to restrict
runAsUseraway from root.
2. Inspect PodSecurityPolicies for root-user allowances
kubectl get podsecuritypolicies.policy -o yaml
In the output, examine each PSP’s spec.runAsUser:
You are looking for entries like:
spec:
runAsUser:
rule: RunAsAny
or:
spec:
runAsUser:
rule: MustRunAs
ranges:
- min: 0
max: 0
or any ranges that include 0.
Problem indication:
rule: RunAsAny, orrule: MustRunAswith any range that includes UID0, orrunAsUsermissing entirely (treated as allowing root, depending on other policies).
A compliant PSP for this control will have:
spec:
runAsUser:
rule: MustRunAsNonRoot
or:
spec:
runAsUser:
rule: MustRunAs
ranges:
- min: 10000
max: 20000 # example non-root range, must not include 0
3. Check which PSPs are actually usable in each namespace
First, list service accounts and their PSP-related bindings:
kubectl get serviceaccounts --all-namespaces
kubectl get clusterrolebindings.rbac.authorization.k8s.io
kubectl get rolebindings.rbac.authorization.k8s.io --all-namespaces
Then, for bindings that grant use on PSPs, get details:
kubectl get clusterrole -o yaml | grep -A5 "podsecuritypolicies.policy"
kubectl get role -A -o yaml | grep -A5 "podsecuritypolicies.policy"
Problem indication:
- Workload service accounts (or
system:serviceaccounts:<ns>) are bound to PSPs that:- have
runAsUser.rule: RunAsAny, or - allow UID ranges including
0, - or there is no binding to any restrictive PSP, so only permissive/default PSPs apply.
- have
4. Spot workloads currently configured to run as root (for human review)
This does not prove admission policy, but highlights where root may be in use.
kubectl get pods --all-namespaces -o yaml | \
grep -E "runAsUser:|runAsNonRoot:" -n
Problem indication:
- Pods or containers explicitly configured with
runAsUser: 0. - Pods/containers without
runAsNonRoot: trueand no policy preventing root, combined with permissive PSPs from steps 2–3.
5. Verification after any policy changes
Re-run:
kubectl get podsecuritypolicies.policy -o yaml
Confirm:
- Every PSP intended for general workloads has
.spec.runAsUser.ruleset toMustRunAsNonRootorMustRunAswith UID ranges that do not include0. - RBAC bindings from step 3 ensure that regular workload service accounts are not able to
useany PSP that allowsRunAsAnyor UID0.
Human judgement is required:
- Decide which namespaces/workloads, if any, are allowed to run as root.
- Ensure that PSPs and RBAC bindings reflect that decision, and that unrestricted PSPs are not broadly bound.
Automation
#!/usr/bin/env bash
# Run on: any machine with kubectl access and current context set to the target cluster
# Purpose: Report namespaces and workloads that can admit root containers (no enforced non-root policy)
set -euo pipefail
echo "=== Cluster-wide admission/root-container posture report ==="
echo "Context: $(kubectl config current-context)"
echo
############################################
# 1) PodSecurity (built-in admission) NS labels
############################################
echo "== 1) Namespaces and PodSecurity labels (privileged/baseline/restricted) =="
kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{range $k,$v := .metadata.labels}{$k}={"$v"}{";"}{end}{"\n"}{end}' \
| column -t
echo
cat <<'EOF'
INTERPRETATION (PodSecurity):
- Namespaces WITHOUT any pod-security.kubernetes.io/* labels:
-> PROBLEM: No PodSecurity standard level is enforced; pods may run as root unless restricted elsewhere.
- Namespaces labeled with:
pod-security.kubernetes.io/enforce: privileged
-> PROBLEM: Effectively no PodSecurity restrictions; root containers are allowed.
- Namespaces labeled with:
pod-security.kubernetes.io/enforce: baseline
-> REVIEW: Baseline allows many images that default to root; you must rely on other policies (PSP, PSP-equivalent, or admission).
- Namespaces labeled with:
pod-security.kubernetes.io/enforce: restricted
-> BETTER: Restricted profile *tends* to require non-root, but still review workload-level settings below.
EOF
echo
############################################
# 2) PodSecurityPolicy (if present)
############################################
echo "== 2) PodSecurityPolicies and runAsUser rules =="
if kubectl get psp >/dev/null 2>&1; then
kubectl get psp -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.runAsUser.rule}{"\t"}{.spec.runAsUser.ranges}{"\n"}{end}' \
| column -t
else
echo "No PodSecurityPolicy resources detected (PSP likely disabled or removed)."
fi
echo
cat <<'EOF'
INTERPRETATION (PSP):
- PSP with spec.runAsUser.rule = RunAsAny:
-> PROBLEM: Does not prevent root containers.
- PSP with spec.runAsUser.rule = MustRunAsNonRoot:
-> GOOD: Prevents root UID 0 from being used.
- PSP with spec.runAsUser.rule = MustRunAs with ranges that include 0:
-> PROBLEM: Explicitly allows UID 0.
- PSP with spec.runAsUser.rule = MustRunAs with ranges that EXCLUDE 0:
-> GOOD: Only non-root UIDs are allowed.
NOTE: You must also check which PSPs are bound via RBAC to which subjects/namespaces.
EOF
echo
############################################
# 3) Workload-level securityContext that may allow / force root
############################################
echo "== 3) Workloads with securityContext that may allow or force root (per namespace) =="
NAMESPACES=$(kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{" "}{end}')
for ns in $NAMESPACES; do
echo "--- Namespace: ${ns} ---"
# Pods owned by controllers (for effective spec review)
kubectl get pods -n "$ns" -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.securityContext.runAsUser}{"\t"}{.spec.securityContext.runAsNonRoot}{"\t"}{range .spec.containers[*]}{.name}{"="}{.securityContext.runAsUser}","{.securityContext.runAsNonRoot}{";"}{end}{"\n"}{end}' \
2>/dev/null | sed '1iPOD\tPOD.runAsUser\tPOD.runAsNonRoot\tCONTAINER(name=runAsUser,runAsNonRoot)' | column -t
echo
done
cat <<'EOF'
INTERPRETATION (Workloads):
For each namespace row:
- If POD or CONTAINER runAsUser is 0:
-> PROBLEM: Explicitly configured to run as root.
- If runAsUser is empty AND runAsNonRoot is empty:
-> RISK: UID defaults to image/user; often root (0) if image not hardened.
- If runAsNonRoot is true (pod or container) and runAsUser is NOT 0:
-> BETTER: Admission should reject containers running as root.
NOTE: This only shows spec hints. Actual admission behavior depends on PodSecurity, PSP, and any external admission controllers.
EOF
echo
############################################
# 4) Quick summary of potential problem namespaces
############################################
echo "== 4) Summary: Namespaces likely to admit root containers (heuristic) =="
echo "Namespaces with NO PodSecurity labels:"
kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{" "}{range $k,$v := .metadata.labels}{$k}{"="}{$v}{" "}{end}{"\n"}{end}' \
| awk '!/pod-security.kubernetes.io\/enforce=|pod-security.kubernetes.io\/audit=|pod-security.kubernetes.io\/warn=/' \
| awk '{print $1}' | sort -u
echo
echo "Namespaces explicitly labeled as privileged:"
kubectl get ns -l 'pod-security.kubernetes.io/enforce=privileged' --no-headers -o custom-columns='NAMESPACE:.metadata.name' || true
echo
echo "Namespaces with pods explicitly setting runAsUser: 0 (root):"
for ns in $NAMESPACES; do
if kubectl get pods -n "$ns" >/dev/null 2>&1; then
ROOTPODS=$(kubectl get pods -n "$ns" -o json | \
jq -r '.items[]
| select(
(.spec.securityContext.runAsUser == 0)
or ([(.spec.containers[]?.securityContext.runAsUser)] | any(.==0))
)
| .metadata.name' 2>/dev/null || true)
if [ -n "$ROOTPODS" ]; then
echo "Namespace: $ns"
echo "$ROOTPODS" | sed 's/^/ pod: /'
fi
fi
done
cat <<'EOF'
HOW TO USE THIS REPORT:
- Any namespace listed above, or with:
* pod-security.kubernetes.io/enforce=privileged, OR
* workloads explicitly setting runAsUser: 0,
indicates a PROBLEM for this control and requires manual review and policy tightening.
- This script does NOT change the cluster; it only surfaces where root containers are allowed or used so you can make informed decisions.
EOF