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
Remediation
Manual Steps
-
Identify pods lacking a
securityContext- Run on: any machine with
kubectlaccess - 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.
- Run on: any machine with
-
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.securityContextand each.spec.containers[].securityContextfor settings such asrunAsNonRoot,runAsUser,runAsGroup,readOnlyRootFilesystem,allowPrivilegeEscalation, andcapabilities.
-
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-0runAsUser). - 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: falseif not).
- Whether the container can run as non-root (
- Collect inputs from the app owners before changing manifests.
- For each application/team owning the pods, determine:
-
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.yamlto add security contexts, for example:spec:template:spec:securityContext:runAsNonRoot: truerunAsUser: 1000runAsGroup: 1000containers:- name: appimage: your-imagesecurityContext:readOnlyRootFilesystem: trueallowPrivilegeEscalation: falsecapabilities: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.
- Retrieve the controller manifest (Deployment/StatefulSet/Job/etc.):
-
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 usingkubectl runwithout saving and hardening the YAML first.
- List standalone pods (no ownerReferences):
-
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 yamlto ensure the intendedsecurityContextsettings are present.
- Re-run the initial check:
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
securityContextat all at pod or container level. runAsUser: 0orrunAsNonRoot: false(or not set where non-root is expected).privileged: true.allowPrivilegeEscalation: trueor not set.capabilities.addincluding dangerous capabilities (e.g.,SYS_ADMIN,NET_ADMIN).hostNetwork: true,hostPID: true,hostIPC: truewithout 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 yamlshows the intendedsecurityContextfields 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