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
Remediation
Manual Steps
-
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.jsonReview
/tmp/pod-securitycontexts.jsonfor pods or containers wherepodSCorscisnullor missing. -
Prioritize high‑risk namespaces and workloads
- Run on: any machine with kubectl access
# List namespaceskubectl get ns# Show workloads in a target namespace (example: default)kubectl get deploy,sts,ds,job,cronjob -n default -o wideFocus review on: internet‑facing apps, workloads handling sensitive data, and any pod using
hostNetwork,hostPID,hostIPC, orhostPathvolumes. -
Inspect detailed security context and privilege usage
- Run on: any machine with kubectl access
# Replace <ns> and <pod> with values from step 1kubectl get pod <pod> -n <ns> -o yaml > /tmp/<ns>-<pod>.yamlIn each YAML, check under
specandspec.containers[*]for:securityContext.runAsNonRoot: trueorrunAsUsernon‑0securityContext.allowPrivilegeEscalation: falsesecurityContext.capabilities.drop: ["ALL"](or at least ALL dropped)securityContext.privileged: false(or absent)securityContext.readOnlyRootFilesystem: truewhere feasible
Flag workloads that lack these or explicitly violate them.
-
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.
-
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: truerunAsUser: 1000fsGroup: 2000containers:- name: <container-name>image: <image>securityContext:allowPrivilegeEscalation: falseprivileged: falsecapabilities:drop: ["ALL"]readOnlyRootFilesystem: trueApply the updated manifest:
kubectl apply -f <updated-manifest>.yamlRepeat for other workloads, ensuring privileged/host‑level workloads are scoped to restricted namespaces/service accounts only.
- Run on: any machine with kubectl access
-
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)"' || trueThe 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: trueor missing when the pod has other risky settingssecurityContext.runAsUser: 0orrunAsNonRoot: false(or missing when the image clearly runs as root)capabilities.addincluding powerful capabilities (e.g.SYS_ADMIN,NET_ADMIN,DAC_*)hostNetwork: true,hostPID: true, orhostIPC: truewhere not strictly neededreadOnlyRootFilesystem: falsewhen 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=privilegedor missingenforcelabels entirely. - System or infrastructure namespaces (e.g.
kube-system) may legitimately beprivilegedbut 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
securityContextsettings 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