Skip to main content

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

Manual Steps
  1. Identify where root is allowed today

    • On any machine with kubectl access:
      kubectl get psp --all-namespaces -o yaml
      kubectl get clusterroles.rbac.authorization.k8s.io,roles.rbac.authorization.k8s.io \
      --all-namespaces -o yaml
      kubectl 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.
  2. Review current admission / security posture per namespace

    • On any machine with kubectl access:
      kubectl get ns
      kubectl get ns -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels}{"\n"}{end}'
      kubectl get psp -o yaml 2>/dev/null
      kubectl 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.
  3. Identify workloads currently running as root or likely to run as root

    • On any machine with kubectl access:
      kubectl get pods -A -o wide
      kubectl 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.
  4. 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.
  5. 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:
        spec:
        runAsUser:
        rule: MustRunAsNonRoot
        or:
        spec:
        runAsUser:
        rule: MustRunAs
        ranges:
        - min: 1000
        max: 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.
  6. 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/null
      kubectl 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.
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 runAsUser away 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, or
  • rule: MustRunAs with any range that includes UID 0, or
  • runAsUser missing 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.

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: true and 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.rule set to MustRunAsNonRoot or MustRunAs with UID ranges that do not include 0.
  • RBAC bindings from step 3 ensure that regular workload service accounts are not able to use any PSP that allows RunAsAny or UID 0.

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

Additional Reading: