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 EKS
- 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
-
Identify pods and namespaces lacking securityContext
- On any machine with kubectl access:
kubectl get pods -A -o=jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.securityContext}{"\t"}{range .spec.containers[*]}{.name}{"="}{.securityContext}{";"}{end}{"\n"}{end}' \| column -t
- Flag pods/containers where
.spec.securityContextis<nil>and any container.securityContextis missing or empty for review.
- On any machine with kubectl access:
-
Review current privilege-related settings on suspect workloads
- For each flagged pod, inspect full spec:
kubectl get pod <pod-name> -n <namespace> -o yaml
- Look for:
securityContext.privileged,allowPrivilegeEscalation,runAsUser,runAsNonRoot,capabilities,hostNetwork,hostPID,hostIPC,hostPath/other volume types,fsGroup,supplementalGroups. - Decide, with the application owner, whether the workload truly requires any privileged or host-level access.
- For each flagged pod, inspect full spec:
-
Define or update a restrictive baseline policy per namespace
- For namespaces that should be locked down (everything except core infra like
kube-system), create or adjust a baseline manifest reflecting the benchmark’s restrictive intent, for example (edit names/labels/namespaces as needed):cat > restricted-baseline.yaml << 'EOF'apiVersion: v1kind: Namespacemetadata:name: secure-appslabels:pod-security.kubernetes.io/enforce: restrictedpod-security.kubernetes.io/audit: restrictedpod-security.kubernetes.io/warn: restrictedEOFkubectl apply -f restricted-baseline.yaml - For existing namespaces, apply labels directly:
kubectl label namespace <namespace> \pod-security.kubernetes.io/enforce=restricted \--overwrite
- For namespaces that should be locked down (everything except core infra like
-
Harden pod specs to meet the restrictive intent
- For each non-privileged workload, update its Deployment/StatefulSet/Job manifest (never edit bare pods that are not managed by a controller) to add container-level securityContext such as:
securityContext:allowPrivilegeEscalation: falseprivileged: falserunAsNonRoot: truerunAsUser: 1000readOnlyRootFilesystem: truecapabilities:drop:- ALL
- Apply updated manifests from your IaC/manifest repo using kubectl from any machine with access:
kubectl apply -f <updated-manifest>.yaml
- For each non-privileged workload, update its Deployment/StatefulSet/Job manifest (never edit bare pods that are not managed by a controller) to add container-level securityContext such as:
-
Handle necessary exceptions in tightly scoped namespaces
- For workloads that require broader access (e.g., logging agents, CNI, storage drivers), ensure they are:
- Deployed into dedicated namespaces (e.g.,
kube-systemor a specificinfra-loggingnamespace). - Bound to specific service accounts with RBAC tailored to their needs:
kubectl get rolebindings,clusterrolebindings -A \-o wide | grep -E '<service-account-name>|<namespace>'
- Deployed into dedicated namespaces (e.g.,
- Loosen securityContext only for these pods and document the justification per workload.
- For workloads that require broader access (e.g., logging agents, CNI, storage drivers), ensure they are:
-
Re-verify posture after changes
- Confirm that most pods now carry non-empty securityContext fields and that no unexpected privileged/host-level settings exist:
kubectl get pods -A -o=jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{range .spec.containers[*]}{.name}{"="}{.securityContext}{";"}{end}{"\n"}{end}' \| column -t
- Spot-check a sample of critical namespaces (
default, app namespaces) withkubectl get pod <pod> -n <ns> -o yamlto ensure configurations align with your restrictive baseline and the benchmark’s intent.
- Confirm that most pods now carry non-empty securityContext fields and that no unexpected privileged/host-level settings exist:
Using kubectl
Using kubectl
Run these from any machine with kubectl access.
1. List pods that declare no securityContext at all
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| select((.spec.securityContext // {}) == {} and
([(.spec.containers[]?.securityContext),
(.spec.initContainers[]?.securityContext)] | map(select(. != null)) | length) == 0)
| [.metadata.namespace, .metadata.name]
| @tsv' \
| column -t
Problem indication: Any application namespace (e.g. not kube-system, kube-public, kube-node-lease) showing pods here means those pods have no explicit security context and should be reviewed and likely hardened.
2. Find containers that can run as root or with no user restriction
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| . as $pod
| ($pod.spec.containers + ($pod.spec.initContainers // []))[]
| {
ns: $pod.metadata.namespace,
pod: $pod.metadata.name,
c: .name,
podRunAsNonRoot: $pod.spec.securityContext.runAsNonRoot,
podRunAsUser: $pod.spec.securityContext.runAsUser,
cRunAsNonRoot: .securityContext.runAsNonRoot,
cRunAsUser: .securityContext.runAsUser
}
| select(
# flag if explicitly running as 0
( .podRunAsUser == 0 or .cRunAsUser == 0 )
or
# or neither pod nor container enforce non-root
( (.podRunAsNonRoot // false) == false
and (.cRunAsNonRoot // false) == false
and (.podRunAsUser // .cRunAsUser // null) == null
)
)
| [.ns, .pod, .c,
"podRunAsNonRoot=" + ( .podRunAsNonRoot|tostring ),
"cRunAsNonRoot=" + ( .cRunAsNonRoot|tostring ),
"podRunAsUser=" + ( .podRunAsUser|tostring ),
"cRunAsUser=" + ( .cRunAsUser|tostring )
]
| @tsv' \
| column -t
Problem indication: Application workloads here are effectively allowed to run as root (UID 0) or have no restriction to non‑root. Compare against your policy; in line with the benchmark, you typically want runAsNonRoot: true or a non‑0 runAsUser.
3. Find containers allowed to escalate privileges
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| . as $pod
| ($pod.spec.containers + ($pod.spec.initContainers // []))[]
| {
ns: $pod.metadata.namespace,
pod: $pod.metadata.name,
c: .name,
podAPE: $pod.spec.securityContext.allowPrivilegeEscalation,
cAPE: .securityContext.allowPrivilegeEscalation
}
| select(
# container explicitly allows escalation, or inherits no restriction
( .cAPE == true )
or
( .cAPE == null and (.podAPE == null or .podAPE == true) )
)
| [.ns, .pod, .c,
"podAllowPrivilegeEscalation=" + ( .podAPE|tostring ),
"cAllowPrivilegeEscalation=" + ( .cAPE|tostring )
]
| @tsv' \
| column -t
Problem indication: Non‑system workloads in this list allow privilege escalation. The benchmark recommends allowPrivilegeEscalation: false as a default.
4. Find privileged containers and host‑level access
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| . as $pod
| ($pod.spec.containers + ($pod.spec.initContainers // []))[]
| {
ns: $pod.metadata.namespace,
pod: $pod.metadata.name,
c: .name,
privileged: .securityContext.privileged,
hostPID: $pod.spec.hostPID,
hostIPC: $pod.spec.hostIPC,
hostNetwork: $pod.spec.hostNetwork,
capAdd: (.securityContext.capabilities.add // [])
}
| select(
.privileged == true or
.hostPID == true or
.hostIPC == true or
.hostNetwork == true or
(.capAdd | length > 0)
)
| [.ns, .pod, .c,
"privileged=" + (.privileged|tostring),
"hostPID=" + (.hostPID|tostring),
"hostIPC=" + (.hostIPC|tostring),
"hostNetwork=" + (.hostNetwork|tostring),
"capAdd=" + ( (.capAdd|join(",")) )
]
| @tsv' \
| column -t
Problem indication: Any application pod here has elevated privileges (privileged, extra capabilities, or host namespace access). Only tightly‑controlled system or daemon pods should appear, and even those should be explicitly justified.
5. Identify containers without restricted capabilities / read‑only root FS
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| . as $pod
| ($pod.spec.containers + ($pod.spec.initContainers // []))[]
| {
ns: $pod.metadata.namespace,
pod: $pod.metadata.name,
c: .name,
drop: (.securityContext.capabilities.drop // []),
rofs: .securityContext.readOnlyRootFilesystem
}
| select(
# missing drop ALL or not read-only root FS
( (.drop | index("ALL")) == null or (.rofs // false) == false )
)
| [.ns, .pod, .c,
"drop=" + ( (.drop|join(",")) ),
"readOnlyRootFilesystem=" + (.rofs|tostring)
]
| @tsv' \
| column -t
Problem indication: For most workloads aligned with the sample restrictive policy, you expect drop: ["ALL"] and readOnlyRootFilesystem: true (or a conscious exception). Entries here are candidates for hardening.
6. Review volume types that may bypass restrictions
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| . as $pod
| {
ns: .metadata.namespace,
pod: .metadata.name,
vols: [.spec.volumes[]? |
if .hostPath then "hostPath"
elif .nfs then "nfs"
elif .glusterfs then "glusterfs"
elif .cephfs then "cephfs"
elif .iscsi then "iscsi"
elif .fc then "fc"
elif .cinder then "cinder"
elif .gcePersistentDisk then "gcePersistentDisk"
elif .awsElasticBlockStore then "awsElasticBlockStore"
elif .azureDisk or .azureFile then "azure"
else ""
end
] | unique | map(select(. != "")) }
| select(.vols | length > 0)
| [.ns, .pod, "volumes=" + (.vols|join(","))]
| @tsv' \
| column -t
Problem indication: While persistent volumes are generally allowed, widespread hostPath or other direct node‑level mounts in application namespaces often signal pods that should be tightly reviewed and potentially restricted or moved under a privileged policy only where strictly necessary.
7. Check for Pod-level securityContext defaults
kubectl get pods --all-namespaces -o yaml \
| sed -n '1,120p'
Problem indication: When inspecting individual pod specs, look for:
securityContextat pod and container level missing or overly permissive.- Absence of fields that align with your restrictive baseline, such as:
runAsNonRoot: trueor non‑0runAsUserallowPrivilegeEscalation: falsereadOnlyRootFilesystem: truecapabilities: { drop: ["ALL"] }- No
privileged: true, no host namespaces unless strictly required.
Use the above commands to shortlist pods, then inspect those pods’ full specs with:
kubectl get pod <pod-name> -n <namespace> -o yaml
A human must decide which pods are legitimate exceptions (e.g. certain kube-system components, logging agents) and where manifests or higher‑level policy (PSPs, Pod Security admission, or other policy engines) should be tightened.
Automation
#!/usr/bin/env bash
# Report pods/containers without explicit securityContext (or with risky settings)
# Run on: any machine with kubectl access & current context set
set -euo pipefail
# 1) High‑level summary of securityContext usage per pod
echo "=== Pod-level securityContext summary (per namespace/pod) ==="
kubectl get pods -A -o json \
| jq -r '
.items[]
| {
ns: .metadata.namespace,
pod: .metadata.name,
podSc: ( .spec.securityContext // {} ),
containers: [ .spec.containers[] | {name, sc: (.securityContext // {})} ],
initContainers: ( [ (.spec.initContainers // [])[] | {name, sc: (.securityContext // {})} ] )
}
| . as $pod
| $pod
| [
.ns,
.pod,
(if (.podSc | length) > 0 then "pod-sc:yes" else "pod-sc:NO" end),
(if ( [ .containers[] | select((.sc | length) > 0)] | length ) > 0
then "ctr-sc:some"
else "ctr-sc:NONE"
end),
(if ( [ .initContainers[] | select((.sc | length) > 0)] | length ) > 0
then "init-sc:some"
else (if (.initContainers | length) > 0 then "init-sc:NONE" else "init-sc:none-present" end)
end)
]
| @tsv
' \
| column -t
cat <<'EOF'
Interpretation:
- pod-sc:NO => Pod has no pod-level securityContext.
- ctr-sc:NONE => No regular container in the pod has a container-level securityContext.
- init-sc:NONE => Init containers present but none have securityContext.
These lines are candidates for manual review to ensure non-root, no privilege escalation, etc.
EOF
# 2) Detailed list of containers with clearly risky or missing fields
echo
echo "=== Containers with missing or risky securityContext settings ==="
kubectl get pods -A -o json \
| jq -r '
.items[]
| .metadata.namespace as $ns
| .metadata.name as $pod
| (
(.spec.containers // []) + (.spec.initContainers // [])
)[]
| {
ns: $ns,
pod: $pod,
ctype: (if (.name as $n | ( . // {} ) | has("envFrom")) then "container" else "container" end), # label as container
cname: .name,
sc: (.securityContext // {})
}
| . as $ct
| {
ns: .ns,
pod: .pod,
cname: .cname,
runAsNonRoot: (.sc.runAsNonRoot // "UNSET"),
runAsUser: (.sc.runAsUser // "UNSET"),
allowPrivilegeEscalation: (.sc.allowPrivilegeEscalation // "UNSET"),
privileged: (.sc.privileged // "UNSET"),
readOnlyRootFilesystem: (.sc.readOnlyRootFilesystem // "UNSET"),
capabilities_add: (.sc.capabilities.add // []),
capabilities_drop: (.sc.capabilities.drop // [])
}
| select(
# Highlight if *any* of these indicate risk or are unset
(.runAsNonRoot == "UNSET") or
(.runAsNonRoot == false) or
(.allowPrivilegeEscalation == "UNSET") or
(.allowPrivilegeEscalation == true) or
(.privileged == true) or
(.privileged == "UNSET") or
(.readOnlyRootFilesystem == "UNSET") or
(.capabilities_add | length > 0) or
(.capabilities_drop | index("ALL") | not)
)
| [
.ns,
.pod,
.cname,
"runAsNonRoot=" + ( .runAsNonRoot | tostring ),
"runAsUser=" + ( .runAsUser | tostring ),
"allowPrivilegeEscalation=" + ( .allowPrivilegeEscalation | tostring ),
"privileged=" + ( .privileged | tostring ),
"readOnlyRootFilesystem=" + ( .readOnlyRootFilesystem | tostring ),
"capAdd=" + ( .capabilities_add | join(",") ),
"capDrop=" + ( .capabilities_drop | join(",") )
]
| @tsv
' \
| column -t || true
cat <<'EOF'
Interpretation (problem indicators):
- runAsNonRoot=UNSET or false
=> Container may run as root; benchmark expects non-root (and ideally Required via policy).
- runAsUser=UNSET
=> No explicit user; often defaults to root.
- allowPrivilegeEscalation=UNSET or true
=> Containers can escalate privileges; benchmark recommends false.
- privileged=true or privileged=UNSET
=> Privileged container or not explicitly disabled; should be false for general workloads.
- readOnlyRootFilesystem=UNSET
=> Root FS not explicitly read-only; consider setting to true where possible.
- capAdd non-empty OR capDrop missing ALL
=> Extra Linux capabilities retained/added; benchmark example drops ALL by default.
These pods/containers should be reviewed. Decide which ones truly need elevated permissions
(e.g., logging/monitoring agents) and tighten securityContext everywhere else.
EOF