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