Skip to main content

Apply SecurityContext To Your Pods And Containers

More Info:

SecurityContexts constrain the privileges and capabilities of pods and containers. Apply them to harden workloads.

Risk Level

Medium

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. Identify pods lacking a securityContext

    • Run on: any machine with kubectl access
    • Command:
      kubectl get pods --all-namespaces -o json \
      | jq -r '.items[]
      | select((.spec.securityContext // {} ) == {} and
      ( [.spec.containers[].securityContext] | map(select(. != null)) | length == 0 ))
      | "\(.metadata.namespace) \(.metadata.name)"'
    • This lists pods where neither the pod nor any containers define a securityContext.
  2. Review current security settings for selected pods

    • Pick a workload (preferably from a non-system namespace) from the list above.
    • Command:
      kubectl get pod -n NAMESPACE PODNAME -o yaml
    • Inspect .spec.securityContext and each .spec.containers[].securityContext for settings such as runAsNonRoot, runAsUser, runAsGroup, readOnlyRootFilesystem, allowPrivilegeEscalation, and capabilities.
  3. Decide required hardening based on workload needs

    • For each application/team owning the pods, determine:
      • Whether the container can run as non-root (runAsNonRoot: true, non-0 runAsUser).
      • Whether it needs write access to root filesystem (if not, readOnlyRootFilesystem: true).
      • Whether it needs extra Linux capabilities (drop all, then selectively add if required).
      • Whether privilege escalation is needed (allowPrivilegeEscalation: false if not).
    • Collect inputs from the app owners before changing manifests.
  4. Update manifests to include appropriate securityContext

    • Retrieve the controller manifest (Deployment/StatefulSet/Job/etc.):
      kubectl get deploy -n NAMESPACE DEPLOYMENTNAME -o yaml > deployment-secctx.yaml
    • Edit deployment-secctx.yaml to add security contexts, for example:
      spec:
      template:
      spec:
      securityContext:
      runAsNonRoot: true
      runAsUser: 1000
      runAsGroup: 1000
      containers:
      - name: app
      image: your-image
      securityContext:
      readOnlyRootFilesystem: true
      allowPrivilegeEscalation: false
      capabilities:
      drop: ["ALL"]
    • Apply the updated manifest:
      kubectl apply -f deployment-secctx.yaml
    • Repeat with other controllers (StatefulSets, DaemonSets, Jobs, CronJobs) rather than editing pods directly.
  5. Handle pods not managed by higher-level controllers

    • List standalone pods (no ownerReferences):
      kubectl get pods --all-namespaces -o json \
      | jq -r '.items[]
      | select((.metadata.ownerReferences // []) | length == 0)
      | "\(.metadata.namespace) \(.metadata.name)"'
    • For any long-lived standalone pod you intend to keep, recreate it from a manifest that includes a securityContext; avoid using kubectl run without saving and hardening the YAML first.
  6. Verify that security contexts are now applied

    • Re-run the initial check:
      kubectl get pods --all-namespaces -o json \
      | jq -r '.items[]
      | select((.spec.securityContext // {} ) == {} and
      ( [.spec.containers[].securityContext] | map(select(. != null)) | length == 0 ))
      | "\(.metadata.namespace) \(.metadata.name)"'
    • Confirm that all non-exempt workloads (per your policy) no longer appear in the output and spot-check a few pods with kubectl get pod -n NAMESPACE PODNAME -o yaml to ensure the intended securityContext settings are present.
Using kubectl

Using kubectl

1. List pods that have no pod-level securityContext

Run on: any machine with kubectl access.

kubectl get pods --all-namespaces -o jsonpath='{range .items[?(@.spec.securityContext==null)]}{.metadata.namespace}{"\t"}{.metadata.name}{"\n"}{end}'

Indication of a problem:
Any lines in the output are pods that do not define a pod-level securityContext at all and should be manually reviewed. Lack of a pod-level securityContext is a red flag, especially for non-system namespaces.


2. List containers without a container-level securityContext

kubectl get pods --all-namespaces -o json | \
jq -r '
.items[]
| {
ns: .metadata.namespace,
pod: .metadata.name,
containers: [.spec.containers[]? | select(.securityContext == null) | .name],
initContainers: [.spec.initContainers[]? | select(.securityContext == null) | .name]
}
| select((.containers|length) > 0 or (.initContainers|length) > 0)
| "\(.ns)\t\(.pod)\tcontainers:\(.containers|join(","))\tinitContainers:\(.initContainers|join(","))"
'

Indication of a problem:
Any line in the output shows a pod where one or more regular or init containers do not define a securityContext. These containers must be reviewed to determine if explicit restrictions are needed (usually yes).


3. Inspect a specific pod in detail

Replace <namespace> and <pod-name>.

kubectl get pod <pod-name> -n <namespace> -o yaml

Focus your manual review on:

  • .spec.securityContext
  • .spec.containers[].securityContext
  • .spec.initContainers[].securityContext

Indication of a problem:
You see missing or overly-permissive fields such as:

  • No securityContext at all at pod or container level.
  • runAsUser: 0 or runAsNonRoot: false (or not set where non-root is expected).
  • privileged: true.
  • allowPrivilegeEscalation: true or not set.
  • capabilities.add including dangerous capabilities (e.g., SYS_ADMIN, NET_ADMIN).
  • hostNetwork: true, hostPID: true, hostIPC: true without a clear, documented reason.
  • readOnlyRootFilesystem: false (or missing) where a read-only root FS is possible.

These require human judgement to determine if the privilege level is justified.


4. Quickly surface some high‑risk patterns

a. Pods using privileged containers

kubectl get pods --all-namespaces -o json | \
jq -r '
.items[]
| . as $pod
| (.spec.containers[]?, .spec.initContainers[]?)
| select(.securityContext.privileged == true)
| "\($pod.metadata.namespace)\t\($pod.metadata.name)\t\(.name)\tprivileged=true"
'

Problem indication: Any output shows containers running in privileged mode, which is usually a serious concern unless explicitly required.


b. Pods allowing privilege escalation

kubectl get pods --all-namespaces -o json | \
jq -r '
.items[]
| . as $pod
| (.spec.containers[]?, .spec.initContainers[]?)
| select(.securityContext.allowPrivilegeEscalation == true)
| "\($pod.metadata.namespace)\t\($pod.metadata.name)\t\(.name)\tallowPrivilegeEscalation=true"
'

Problem indication: Any output shows containers explicitly allowing privilege escalation. This is typically not desired.


c. Pods running containers as root (explicitly)

kubectl get pods --all-namespaces -o json | \
jq -r '
.items[]
| . as $pod
| (.spec.containers[]?, .spec.initContainers[]?)
| select(.securityContext.runAsUser == 0)
| "\($pod.metadata.namespace)\t\($pod.metadata.name)\t\(.name)\trunAsUser=0"
'

Problem indication: Any output shows containers explicitly configured to run as UID 0. This should be justified and usually avoided.

(Note: many images run as root implicitly; this command only catches explicit runAsUser: 0. Determining implicit root requires image review, not just kubectl.)


5. Verification after you update manifests

After you manually adjust pod or deployment manifests to add appropriate securityContext settings, re-run:

  • The “no pod-level securityContext” command (step 1).
  • The “containers without securityContext” command (step 2).
  • Any specific high-risk pattern commands you are targeting (step 4).

Verification success criteria:

  • The previously flagged pods/containers no longer appear in the outputs, and
  • A kubectl get pod <pod> -n <ns> -o yaml shows the intended securityContext fields applied.
Automation
#!/usr/bin/env bash
#
# Report pods and containers that lack SecurityContext settings
# Run on: any machine with kubectl access and KUBECONFIG set
# Requires: kubectl, jq

set -euo pipefail

# Check dependencies
for bin in kubectl jq; do
if ! command -v "$bin" >/dev/null 2>&1; then
echo "ERROR: $bin not found in PATH" >&2
exit 1
fi
done

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

# Summary counters
total_pods=0
pods_without_pod_sc=0
pods_with_any_container_missing_sc=0

# Header for detailed report
printf "NAMESPACE\tPOD\tPOD_SC\tCONTAINER\tCONTAINER_SC\tINIT_CONTAINER\tPROBLEM\n"

# Get all pods as JSON and iterate
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| {
ns: .metadata.namespace,
pod: .metadata.name,
pod_sc: (.spec.securityContext // {}),

# normal containers
containers: (
(.spec.containers // [])
| map({
name: .name,
sc: (.securityContext // {})
})
),

# init containers
init_containers: (
(.spec.initContainers // [])
| map({
name: .name,
sc: (.securityContext // {})
})
)
}
| @base64' \
| while read -r pod_b64; do
_jq() { echo "$pod_b64" | base64 --decode | jq -r "$1"; }

ns=$(_jq '.ns')
pod=$(_jq '.pod')
pod_sc_json=$(_jq '.pod_sc')

total_pods=$((total_pods + 1))

# Determine if pod-level securityContext is empty
if [ "$pod_sc_json" = "{}" ] || [ "$pod_sc_json" = "null" ]; then
pod_sc="none"
pods_without_pod_sc=$((pods_without_pod_sc + 1))
pod_sc_problem="POD:missing_pod_securityContext"
else
pod_sc="present"
pod_sc_problem=""
fi

# Process regular containers
echo "$pod_b64" | base64 --decode \
| jq -r '.containers[]? | @base64' \
| while read -r c_b64; do
c_name=$(echo "$c_b64" | base64 --decode | jq -r '.name')
c_sc_json=$(echo "$c_b64" | base64 --decode | jq -r '.sc')

if [ "$c_sc_json" = "{}" ] || [ "$c_sc_json" = "null" ]; then
c_sc="none"
problem="CONTAINER:missing_container_securityContext"
pods_with_any_container_missing_sc=$((pods_with_any_container_missing_sc + 1))
else
c_sc="present"
problem="$pod_sc_problem"
fi

printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" \
"$ns" "$pod" "$pod_sc" "$c_name" "$c_sc" "no" "${problem:-}"
done

# Process init containers
echo "$pod_b64" | base64 --decode \
| jq -r '.init_containers[]? | @base64' \
| while read -r ic_b64; do
ic_name=$(echo "$ic_b64" | base64 --decode | jq -r '.name')
ic_sc_json=$(echo "$ic_b64" | base64 --decode | jq -r '.sc')

if [ "$ic_sc_json" = "{}" ] || [ "$ic_sc_json" = "null" ]; then
ic_sc="none"
problem="INIT_CONTAINER:missing_container_securityContext"
pods_with_any_container_missing_sc=$((pods_with_any_container_missing_sc + 1))
else
ic_sc="present"
problem="$pod_sc_problem"
fi

printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" \
"$ns" "$pod" "$pod_sc" "$ic_name" "$ic_sc" "yes" "${problem:-}"
done

# If pod has no containers at all, still emit one line
if ! echo "$pod_b64" | base64 --decode | jq -e '.containers | length > 0 or .init_containers | length > 0' >/dev/null 2>&1; then
printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" \
"$ns" "$pod" "$pod_sc" "-" "-" "-" "$pod_sc_problem"
fi
done

echo
echo "Cluster summary:"
echo " Total pods: $total_pods"
echo " Pods without pod-level SecurityContext: $pods_without_pod_sc"
echo " Pods with any container lacking SC: $pods_with_any_container_missing_sc"

cat <<'EOF'

How to interpret the output:

- POD_SC column:
- "none" => .spec.securityContext is not set on the pod (possible hardening gap).
- "present" => Pod has a pod-level securityContext; contents must be reviewed manually.

- CONTAINER_SC column:
- "none" => .spec.containers[].securityContext or .spec.initContainers[].securityContext
is not set. This is usually a problem and should be reviewed.
- "present" => Container has a securityContext; you must still validate it against your
policies and the CIS Docker/Kubernetes recommendations.

- PROBLEM column highlights obvious issues:
- "POD:missing_pod_securityContext"
- "CONTAINER:missing_container_securityContext"
- "INIT_CONTAINER:missing_container_securityContext"

Any line where POD_SC = "none" or CONTAINER_SC = "none" indicates a workload that
likely needs a manual securityContext review and possible hardening.
EOF