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 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. List pods and identify those 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,
    cSC: ([.spec.containers[].securityContext] // []),
    iSC: ([.spec.initContainers[].securityContext] // [])
    }
    | select(.podSC == null and ( .cSC | all(. == null) ) and ( .iSC | all(. == null) ))
    | [.ns, .pod] | @tsv
    ' | column -t
    • This lists pods with no pod-level or container-level securityContext defined.
  2. Inspect security context details for a specific pod

    • Run on: any machine with kubectl access
    NAMESPACE=default
    POD_NAME=example-pod
    kubectl get pod "$POD_NAME" -n "$NAMESPACE" -o yaml \
    | yq '.spec | {securityContext, containers: [.containers[].name, .containers[].securityContext], initContainers: [.initContainers[].name, .initContainers[].securityContext]}'
    • Review which settings (runAsUser, runAsNonRoot, readOnlyRootFilesystem, capabilities, privileged, hostNetwork, hostPID, hostIPC, seccompProfile, etc.) are missing or overly permissive.
  3. Review workload owners (Deployments, etc.) for missing or weak security contexts

    • Run on: any machine with kubectl access
    kubectl get deploy,sts,ds,job,cronjob -A -o yaml \
    | yq '
    .items[]
    | {
    kind: .kind,
    ns: .metadata.namespace,
    name: .metadata.name,
    podSC: .spec.template.spec.securityContext,
    containers: [.spec.template.spec.containers[].securityContext],
    initContainers: [.spec.template.spec.initContainers[].securityContext]
    }
    '
    • Identify templates that lack securityContext or set privileged: true, allow privilege escalation, broad capabilities, or host namespaces/paths.
  4. Decide required security context based on app needs and org policy

    • For each workload identified in steps 1–3, review with the app owner whether it can:
      • Run as non-root (runAsNonRoot: true, runAsUser: non-0 UID, runAsGroup)
      • Use a read-only root filesystem
      • Drop all capabilities and add back only specific required ones
      • Avoid privileged: true and avoid hostPath, hostNetwork, hostPID, hostIPC where possible
    • Refer to your org’s baseline and the CIS Google Container-Optimized OS Benchmark to choose appropriate defaults.
  5. Update manifests to add or tighten securityContext

    • Run on: any machine with kubectl access
    • Export current manifest, edit locally, then apply:
    NAMESPACE=default
    WORKLOAD=example-deployment
    kubectl get deploy "$WORKLOAD" -n "$NAMESPACE" -o yaml > /tmp/"$WORKLOAD".yaml
    • In /tmp/"$WORKLOAD".yaml, under .spec.template.spec add or adjust, for example:
    securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 2000
    containers:
    - name: app
    securityContext:
    allowPrivilegeEscalation: false
    readOnlyRootFilesystem: true
    privileged: false
    capabilities:
    drop: ["ALL"]
    • Then apply:
    kubectl apply -f /tmp/"$WORKLOAD".yaml
  6. Re-verify that pods now have appropriate security contexts

    • Run on: any machine with kubectl access
    kubectl get pods --all-namespaces -o json \
    | jq -r '
    .items[]
    | select(.spec.securityContext == null
    and ([.spec.containers[].securityContext] // [] | all(. == null))
    and ([.spec.initContainers[].securityContext] // [] | all(. == null)))
    | [.metadata.namespace, .metadata.name] | @tsv
    ' | column -t
    • Confirm that critical workloads are no longer listed, and spot-check updated pods with:
    kubectl get pod "$POD_NAME" -n "$NAMESPACE" -o yaml | yq '.spec'
Using kubectl
# 1) List all pods and their security context usage (namespaces, pod & container level)
# Run on: any machine with kubectl access
kubectl get pods --all-namespaces -o custom-columns=\
NAMESPACE:.metadata.namespace,\
POD:.metadata.name,\
POD_SC:.spec.securityContext,\
CONTAINERS_SC:.spec.containers[*].securityContext

Problem indication:

  • POD_SC is <none> for most pods, and
  • CONTAINERS_SC is <none> or {} (empty) for most containers.
    This shows security context is largely not defined.

# 2) Inspect full spec for specific pods (e.g., application namespaces)
# Replace <namespace> and <pod-name> with actual values from your cluster
kubectl -n <namespace> get pod <pod-name> -o yaml

In the output, focus on:

  • .spec.securityContext (pod-level)
  • .spec.containers[*].securityContext
  • .spec.initContainers[*].securityContext (if present)

Problem indication:

  • Missing securityContext blocks entirely.
  • Present but empty securityContext {} with no meaningful fields.
  • Risky settings, for example:
    • runAsUser: 0 or runAsNonRoot: false
    • privileged: true
    • allowPrivilegeEscalation: true
    • Broad capabilities.add (e.g., SYS_ADMIN, NET_ADMIN).

# 3) Identify containers running as root or lacking non-root guarantees
kubectl get pods --all-namespaces -o jsonpath='
{range .items[*]}
{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}
{range .spec.containers[*]}
{"runAsUser="}{.securityContext.runAsUser}{" "}
{"runAsNonRoot="}{.securityContext.runAsNonRoot}{" "}
{"privileged="}{.securityContext.privileged}{" "}
{"allowPrivilegeEscalation="}{.securityContext.allowPrivilegeEscalation}{"; "}
{end}{"\n"}
{end}'

Problem indication (requires human review):

  • runAsUser= is empty and runAsNonRoot= is empty/false (likely root).
  • privileged=true or allowPrivilegeEscalation=true without a strong justification.

# 4) Show pods with any privileged container
kubectl get pods --all-namespaces -o json | \
jq -r '
.items[]
| {ns: .metadata.namespace, pod: .metadata.name, containers: .spec.containers}
| select(.containers[]?.securityContext?.privileged == true)
| "\(.ns)\t\(.pod)"
'

Problem indication:

  • Any application or multi-tenant workload appears in this list without a clear, documented need to be privileged.

# 5) Show pods where allowPrivilegeEscalation is true or unset
kubectl get pods --all-namespaces -o json | \
jq -r '
.items[]
| {ns: .metadata.namespace, pod: .metadata.name, containers: .spec.containers}
| select(
[.containers[]? | .securityContext.allowPrivilegeEscalation]
| any(. == true or . == null)
)
| "\(.ns)\t\(.pod)"
'

Problem indication:

  • Application pods listed here generally need stricter securityContext (e.g., allowPrivilegeEscalation: false) unless there is a specific requirement.

These commands only surface current state. A human must decide, per workload, which security context fields to add or tighten based on application needs and your organization’s security policy.

Automation
#!/usr/bin/env bash
#
# Report pods and containers that lack recommended securityContext settings.
# Run on any machine with kubectl access and current context set.
#
# This is a read-only assessment. It does NOT change the cluster.

set -euo pipefail

# Fail fast if kubectl is not available or cannot reach the cluster
kubectl version --short >/dev/null 2>&1 || {
echo "ERROR: kubectl not available or cannot reach the cluster" >&2
exit 1
}

echo "Collecting pod securityContext information from all namespaces..."
echo

# Headers
printf "%-32s %-24s %-40s %-12s %-7s %-7s %-9s %-11s %-11s %-11s %-9s %-9s\n" \
"NAMESPACE" "POD" "CONTAINER" "TYPE" "UID" "GID" "PRIVILEGED" "HOSTPID" "HOSTIPC" "HOSTNET" "ROFS" "DROPCAPS"

# Explanation of columns (for later review)
cat <<'EOF'

COLUMN MEANING (a blank/empty value is what you need to review):
UID : runAsUser (effective user ID)
GID : runAsGroup or fsGroup (effective group ID)
PRIVILEGED : container.securityContext.privileged
HOSTPID : pod.spec.hostPID
HOSTIPC : pod.spec.hostIPC
HOSTNET : pod.spec.hostNetwork
ROFS : container.securityContext.readOnlyRootFilesystem
DROPCAPS : whether NET_RAW (and other caps) are dropped

Rows where UID/GID/ROFS/DROPCAPS are empty or PRIVILEGED/HOST* are "true"
indicate items to review against your security policy.

EOF

# Use a single JSON query and format in bash for readability at scale
kubectl get pods -A -o json \
| jq -r '
.items[]
| . as $pod
| (
# pod-level fields
.metadata.namespace as $ns
| .metadata.name as $podName
| (.spec.securityContext // {}) as $psc
| (.spec.hostPID // false) as $hostPID
| (.spec.hostIPC // false) as $hostIPC
| (.spec.hostNetwork // false) as $hostNet
| ($psc.runAsUser // empty) as $podUid
| ($psc.runAsGroup // empty) as $podGid
| ($psc.fsGroup // empty) as $podFsGroup
| .spec.containers[]
| .name as $cname
| (.securityContext // {}) as $csc
| ($csc.runAsUser // $podUid // empty) as $uid
| ($csc.runAsGroup // $podGid // empty) as $gid
| ($psc.fsGroup // $podFsGroup // empty) as $fsgid
| ($csc.privileged // false) as $priv
| ($csc.readOnlyRootFilesystem // empty) as $rofs
| (($csc.capabilities.drop // []) | map(select(. == "NET_RAW")) | length > 0) as $dropNetRaw
| (($csc.capabilities.drop // []) | length > 0) as $dropAnyCap
| [
$ns,
$podName,
$cname,
"main",
(if $uid|tostring == "null" then "" else ($uid|tostring) end),
(if $gid|tostring == "null" then "" else ($gid|tostring) end),
(if $priv then "true" else "false" end),
(if $hostPID then "true" else "false" end),
(if $hostIPC then "true" else "false" end),
(if $hostNet then "true" else "false" end),
(if $rofs|tostring == "null" then "" else ($rofs|tostring) end),
(if $dropAnyCap then (if $dropNetRaw then "yes+NET_RAW" else "yes" end) else "" end)
]
)
| @tsv
' \
| while IFS=$'\t' read -r ns pod cname type uid gid priv hostpid hostipc hostnet rofs dropcaps; do
printf "%-32s %-24s %-40s %-12s %-7s %-7s %-9s %-11s %-11s %-11s %-9s %-9s\n" \
"$ns" "$pod" "$cname" "$type" "$uid" "$gid" "$priv" "$hostpid" "$hostipc" "$hostnet" "$rofs" "$dropcaps"
done

cat <<'EOF'

HOW TO INTERPRET POTENTIAL PROBLEMS:

Review rows where:
- UID and/or GID are empty:
No explicit runAsUser/runAsGroup configured at pod or container level.
- ROFS is empty or "false":
Root filesystem is writable for the container.
- DROPCAPS is empty:
No Linux capabilities explicitly dropped (e.g., NET_RAW).
- PRIVILEGED is "true":
Privileged container – only allow if strongly justified.
- HOSTPID / HOSTIPC / HOSTNET are "true":
Pod shares host namespaces – only allow if strongly justified.

Use this report to:
1. Identify high‑risk pods/containers.
2. Review their manifests/Helm charts against your security standards.
3. Add or tighten securityContext where appropriate (pod and/or container level).

Note: This script only reports state; applying securityContext changes must be done
manually through your normal deployment workflow (manifests, Helm, GitOps, etc.).
EOF

Additional Reading: