Skip to main content

Apply Security Context To Your Pods And Containers

More Info:

Apply Security Context to Your Pods and Containers.

Risk Level

Medium

Address

Security

Compliance Standards

  • APRA CPS 234 (Australia)
  • BSI C5 (Germany)
  • Brazil LGPD
  • CCPA / CPRA (California)
  • CIS Critical Security Controls v8
  • CIS OKE
  • 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. List pods and identify ones missing securityContext

    • Run on: any machine with kubectl access
    kubectl get pods --all-namespaces -o json \
    | jq -r '
    .items[]
    | {
    ns: .metadata.namespace,
    pod: .metadata.name,
    podSC: .spec.securityContext,
    containers: [ .spec.containers[] | {name, sc: .securityContext} ]
    }
    ' > /tmp/pod-securitycontexts.json

    Review /tmp/pod-securitycontexts.json for pods or containers where podSC or sc is null or missing.

  2. Prioritize high‑risk namespaces and workloads

    • Run on: any machine with kubectl access
    # List namespaces
    kubectl get ns

    # Show workloads in a target namespace (example: default)
    kubectl get deploy,sts,ds,job,cronjob -n default -o wide

    Focus review on: internet‑facing apps, workloads handling sensitive data, and any pod using hostNetwork, hostPID, hostIPC, or hostPath volumes.

  3. Inspect detailed security context and privilege usage

    • Run on: any machine with kubectl access
    # Replace <ns> and <pod> with values from step 1
    kubectl get pod <pod> -n <ns> -o yaml > /tmp/<ns>-<pod>.yaml

    In each YAML, check under spec and spec.containers[*] for:

    • securityContext.runAsNonRoot: true or runAsUser non‑0
    • securityContext.allowPrivilegeEscalation: false
    • securityContext.capabilities.drop: ["ALL"] (or at least ALL dropped)
    • securityContext.privileged: false (or absent)
    • securityContext.readOnlyRootFilesystem: true where feasible
      Flag workloads that lack these or explicitly violate them.
  4. Decide required hardening vs. justified exceptions
    For each flagged workload:

    • Confirm with the app owner whether root/privileged access, host* usage, or extra capabilities are functionally required.
    • If not strictly required, plan to add a restrictive securityContext (non‑root, no privilege escalation, capabilities dropped, no host namespaces).
    • If required (e.g., node agents, CNI, logging/monitoring), document the justification and plan to confine them to a dedicated namespace (often kube-system) and specific service accounts.
  5. Apply or tighten securityContext in manifests

    • Run on: any machine with kubectl access
      For a typical deployment needing hardening, edit its manifest (from Git/IaC if applicable, otherwise live) to include something like:
    spec:
    template:
    spec:
    securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 2000
    containers:
    - name: <container-name>
    image: <image>
    securityContext:
    allowPrivilegeEscalation: false
    privileged: false
    capabilities:
    drop: ["ALL"]
    readOnlyRootFilesystem: true

    Apply the updated manifest:

    kubectl apply -f <updated-manifest>.yaml

    Repeat for other workloads, ensuring privileged/host‑level workloads are scoped to restricted namespaces/service accounts only.

  6. Verify improved security context coverage

    • Run on: any machine with kubectl access
    kubectl get pods --all-namespaces -o json \
    | jq -r '
    .items[]
    | select(
    (.spec.securityContext // {} | has("runAsNonRoot") | not)
    or ( .spec.containers[]?.securityContext // {} | has("allowPrivilegeEscalation") | not )
    )
    | "\(.metadata.namespace)/\(.metadata.name)"
    ' || true

    The goal is to reduce or eliminate the listed pods over time, with any remaining ones being documented, justified exceptions with tightly scoped access.

Using kubectl
# 1) List all pods and their service accounts (for triage)
# Run on: any machine with kubectl access
kubectl get pods -A -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,SERVICE_ACCOUNT:.spec.serviceAccountName

# Focus review on pods using default service accounts or unexpected SAs

A concern is pods using default or overly broad service accounts for workloads that might request privileged settings.

# 2) Inspect securityContext at pod and container level
# Run for a specific pod you want to review
NAMESPACE=your-namespace
POD=your-pod-name

kubectl get pod "$POD" -n "$NAMESPACE" -o yaml | sed -n '/securityContext:/,/image:/p'

Problems to look for in the YAML:

  • securityContext.privileged: true (at pod or container level)
  • securityContext.allowPrivilegeEscalation: true or missing when the pod has other risky settings
  • securityContext.runAsUser: 0 or runAsNonRoot: false (or missing when the image clearly runs as root)
  • capabilities.add including powerful capabilities (e.g. SYS_ADMIN, NET_ADMIN, DAC_*)
  • hostNetwork: true, hostPID: true, or hostIPC: true where not strictly needed
  • readOnlyRootFilesystem: false when the container does not require writes to the root FS
# 3) List pods that request host access or privileged settings (broad scan)
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.hostNetwork}{"\t"}{.spec.hostPID}{"\t"}{.spec.hostIPC}{"\n"}{end}' \
| awk '$3=="true" || $4=="true" || $5=="true"'

kubectl get pods -A -o json | \
jq -r '.items[] |
. as $pod |
(.spec.containers[]? // empty) as $c |
select(
($c.securityContext.privileged == true) or
($c.securityContext.allowPrivilegeEscalation == true)
) |
"\($pod.metadata.namespace)\t\($pod.metadata.name)\t\($c.name)"'

Any pod listed here should be manually justified (e.g., node agent, CNI, storage plugin) and ideally restricted to a dedicated namespace and service account.

# 4) Check for securityContext use in all pods (to find those missing it)
kubectl get pods -A -o json | \
jq -r '.items[] |
select(
(.spec.securityContext // {} == {}) and
([(.spec.containers[]?.securityContext)] | map(select(. != null)) | length == 0)
) |
"\(.metadata.namespace)\t\(.metadata.name)"'

Pods listed by this command have no pod-level or container-level securityContext defined and should be reviewed; they may rely entirely on defaults and not match your intended restrictions.

# 5) Review PodSecurity (PSA) labels on namespaces (if using built‑in PodSecurity)
kubectl get ns --show-labels

Concerns:

  • Namespaces running untrusted workloads labeled with pod-security.kubernetes.io/enforce=privileged or missing enforce labels entirely.
  • System or infrastructure namespaces (e.g. kube-system) may legitimately be privileged but should be tightly access-controlled.
# 6) Review bindings that can create privileged pods
kubectl get clusterrolebindings,rolebindings -A -o yaml | grep -E 'name: cluster-admin|system:masters' -B3 -A6

Concern: Broad bindings (especially to groups like system:authenticated) that allow many identities to create or modify pods with privileged security contexts.

Use these outputs to decide:

  • Which pods must be allowed privileged/host-level access (usually in tightly controlled namespaces/SAs).
  • Which pods should be brought into compliance by adding restrictive securityContext settings in their Deployment/DaemonSet/Job manifests, aligned with the benchmark’s “restricted” model.
Automation
#!/usr/bin/env bash
# Purpose: Cluster-wide report of pods/containers missing recommended securityContext hardening.
# Run on: any machine with kubectl access and current context set.

set -o errexit
set -o pipefail
set -o nounset

# Basic sanity check
kubectl version --short >/dev/null

echo "Collecting pod securityContext report for all namespaces..."
echo

# Header explanation
cat <<'HDR'
This report flags pods/containers that:
- Run privileged, or allow privilege escalation, or add capabilities
- Use hostNetwork/hostPID/hostIPC
- Run as root (explicitly or implicitly)
- Do NOT set securityContext at all (pod and container)

Any line with 'ISSUE=' contains something to review. Empty ISSUE means no obvious problem, but
you still must review manually for business risk and compliance.

Columns:
NS Namespace
POD Pod name
CTR Container name
TYPE pod|container
ISSUE Comma-separated flags describing potential problems

HDR
echo

# Function to generate JSON with derived flags
generate_json() {
kubectl get pods --all-namespaces -o json | jq -c '
.items[]
| {
ns: .metadata.namespace,
pod: .metadata.name,
pod_sc: .spec.securityContext,
hostNetwork: (.spec.hostNetwork // false),
hostPID: (.spec.hostPID // false),
hostIPC: (.spec.hostIPC // false),
containers: (
(.spec.containers // []) + (.spec.initContainers // [])
)
}
| .containers[]
| {
ns,
pod,
ctr: .name,
type: "container",
pod_sc,
hostNetwork,
hostPID,
hostIPC,
ctr_sc: (.securityContext // {}),
image: .image
}
'
}

# Produce a concise, greppable table
generate_json | jq -r '
def bool(v): if v then "true" else "false" end;

. as $r
| .ctr_sc as $c
| .pod_sc as $p

# Derived booleans
| .pod_has_sc = ($p != null)
| .ctr_has_sc = ($c != {})

# Effective privilege flags (pod-level not evaluated for all here, manual review still needed)
| .privileged = ($c.privileged // false)
| .allow_priv_escalation = ($c.allowPrivilegeEscalation // true) # default true
| .adds_caps = ( ($c.capabilities.add // []) | length > 0 )
| .drops_all_caps = (
($c.capabilities.drop // [])
| map(select(. == "ALL"))
| length > 0
)

# Simplified runAsNonRoot / runAsUser detection
| .run_as_non_root = (
if ($c.runAsNonRoot // null) != null then
($c.runAsNonRoot)
elif ($p.runAsNonRoot // null) != null then
($p.runAsNonRoot)
else
false
end
)
| .run_as_user = (
if ($c.runAsUser // null) != null then
($c.runAsUser)
elif ($p.runAsUser // null) != null then
($p.runAsUser)
else
null
end
)

# Build human-readable ISSUE flags
| .issues = (
[
(if .pod_has_sc | not and .ctr_has_sc | not then "NO_SECURITY_CONTEXT" else empty end),
(if .privileged then "PRIVILEGED" else empty end),
(if .allow_priv_escalation then "ALLOW_PRIV_ESCALATION" else empty end),
(if .adds_caps and (.drops_all_caps | not) then "ADDS_CAPS" else empty end),
(if .hostNetwork then "HOST_NETWORK" else empty end),
(if .hostPID then "HOST_PID" else empty end),
(if .hostIPC then "HOST_IPC" else empty end),
(if (.run_as_user == 0) then "RUNS_AS_ROOT_USER_0" else empty end),
(if (.run_as_user == null and .run_as_non_root | not) then "POSSIBLY_ROOT_DEFAULT" else empty end)
]
| map(select(. != ""))
| join(",")
)
| [
.ns,
.pod,
.ctr,
"container",
.issues
]
| @tsv
' | awk 'BEGIN{printf "%-25s %-45s %-30s %-12s %s\n","NS","POD","CTR","TYPE","ISSUE"}
{
printf "%-25s %-45s %-30s %-12s %s\n",$1,$2,$3,$4,$5
}'

echo
echo "Summary of pods/containers with potential issues:"
echo

# Summarize by issue type
generate_json | jq -r '
. as $r
| .ctr_sc as $c
| .pod_sc as $p
| .pod_has_sc = ($p != null)
| .ctr_has_sc = ($c != {})
| .privileged = ($c.privileged // false)
| .allow_priv_escalation = ($c.allowPrivilegeEscalation // true)
| .adds_caps = ( ($c.capabilities.add // []) | length > 0 )
| .drops_all_caps = (
($c.capabilities.drop // [])
| map(select(. == "ALL"))
| length > 0
)
| .run_as_non_root = (
if ($c.runAsNonRoot // null) != null then
($c.runAsNonRoot)
elif ($p.runAsNonRoot // null) != null then
($p.runAsNonRoot)
else
false
end
)
| .run_as_user = (
if ($c.runAsUser // null) != null then
($c.runAsUser)
elif ($p.runAsUser // null) != null then
($p.runAsUser)
else
null
end
)
| .hostNetwork = (.hostNetwork // false)
| .hostPID = (.hostPID // false)
| .hostIPC = (.hostIPC // false)
| .issues = (
[
(if .pod_has_sc | not and .ctr_has_sc | not then "NO_SECURITY_CONTEXT" else empty end),
(if .privileged then "PRIVILEGED" else empty end),
(if .allow_priv_escalation then "ALLOW_PRIV_ESCALATION" else empty end),
(if .adds_caps and (.drops_all_caps | not) then "ADDS_CAPS" else empty end),
(if .hostNetwork then "HOST_NETWORK" else empty end),
(if .hostPID then "HOST_PID" else empty end),
(if .hostIPC then "HOST_IPC" else empty end),
(if (.run_as_user == 0) then "RUNS_AS_ROOT_USER_0" else empty end),
(if (.run_as_user == null and .run_as_non_root | not) then "POSSIBLY_ROOT_DEFAULT" else empty end)
]
| map(select(. != ""))
)
| select((.issues | length) > 0)
| .issues[]
' | sort | uniq -c | sort -nr

cat <<'EXPLAIN'

How to interpret problematic output:

- ISSUE=NO_SECURITY_CONTEXT
Pod and container both lack securityContext; review and add:
- runAsNonRoot: true
- allowPrivilegeEscalation: false
- capabilities.drop: ["ALL"]
- readOnlyRootFilesystem: true (where possible)
- fsGroup/supplementalGroups non-root ranges

- ISSUE=PRIVILEGED
Container has securityContext.privileged=true; only allow for tightly controlled
system workloads (e.g., in kube-system) with strong RBAC and justification.

- ISSUE=ALLOW_PRIV_ESCALATION
Container allows privilege escalation (default true). For non-privileged
workloads, set allowPrivilegeEscalation: false.

- ISSUE=ADDS_CAPS
Container adds Linux capabilities; make sure only minimal, necessary caps are added
and that ALL is dropped first for defense in depth.

- ISSUE=HOST_NETWORK / HOST_PID / HOST_IPC
Pod uses host namespaces. Allow only for infrastructure agents that truly require it,
and restrict via RBAC / namespace.

- ISSUE=RUNS_AS_ROOT_USER_0
Container explicitly runs as UID 0; prefer non-root UIDs and update images/manifests.

- ISSUE=POSSIBLY_ROOT_DEFAULT
No explicit runAsUser/runAsNonRoot; container may run as root depending on image
and admission controls. Review the image and add explicit non-root settings.

This script does NOT mutate anything. Use it repeatedly to measure progress
as you tighten manifests and admission policies (e.g., PodSecurity or other controls).
EXPLAIN

Additional Reading: