Apply Security Context To Pods And Containers
More Info:
Security contexts constrain the privileges and access of pods and containers at runtime. They should be applied following the CIS Google Container-Optimized OS Benchmark guidance.
Risk Level
Medium
Address
Security
Compliance Standards
- CIS GKE
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List pods and identify targets (any machine with kubectl access)
kubectl get pods -A -o wideDecide which namespaces/pods are in scope (exclude managed system namespaces only if your policy allows, e.g.
kube-system,gke-system, etc., after confirming provider ownership). -
Review pod- and container-level securityContext usage (any machine with kubectl access)
For each in-scope namespace, export full specs and check for missing or weaksecurityContextsettings:# Example for one namespacekubectl get pods -n <namespace> -o yaml > pods-<namespace>.yamlIn the YAML, look for:
- Pod-level:
.spec.securityContext - Container-level for each container and initContainer:
.spec.containers[].securityContextand.spec.initContainers[].securityContext
Note pods/containers wheresecurityContextis absent or obviously over-privileged (e.g.privileged: true,runAsUser: 0,allowPrivilegeEscalation: true,readOnlyRootFilesystem: false,capabilities.addwith broad capabilities).
- Pod-level:
-
Classify pods by required privilege (manual decision)
For each application/team:- Determine if it truly needs root, host access, or additional Linux capabilities (e.g. CNI, CSI, monitoring agents may require more privileges; most apps do not).
- Group pods into: (A) can run as non-root, (B) need limited extra privileges, (C) must remain highly privileged (with explicit business/technical justification).
-
Design appropriate securityContext settings per group (manual decision, guided by CIS GKE / COS)
For each group, decide the target baseline, for example:- Common baseline for A/B (tightest feasible):
runAsNonRoot: truerunAsUser: <non-root UID>allowPrivilegeEscalation: falsereadOnlyRootFilesystem: true(if writable paths can move to volumes)capabilities.drop: ["ALL"]and only minimalcapabilities.addif neededseccompProfile: type: RuntimeDefault(or Localhost profile if you maintain one)
- For group B/C, document each exception (e.g.
privileged: true, hostPath mounts, NET_ADMIN, SYS_ADMIN, etc.) with rationale and compensating controls.
- Common baseline for A/B (tightest feasible):
-
Update owning manifests and re-apply (any machine with kubectl access)
- Locate the source manifests/Helm charts/Kustomize for the identified pods (Git repo, CI pipeline, or local files).
- Edit them to add or refine
securityContextat pod and/or container level, following the design in step 4. Example fragment for a container securityContext:securityContext:runAsNonRoot: truerunAsUser: 1000allowPrivilegeEscalation: falsereadOnlyRootFilesystem: truecapabilities:drop: ["ALL"]seccompProfile:type: RuntimeDefault - Re-deploy via your normal workflow, e.g.:
kubectl apply -f <updated-manifest>.yaml
- Coordinate with app owners to run smoke tests; watch for failures due to tightened permissions and adjust only where strictly necessary.
-
Verify and document residual risk (any machine with kubectl access)
Re-run the inspection and confirm security contexts are present and aligned with your decisions:kubectl get pods -A -o yaml | grep -E "securityContext|privileged|allowPrivilegeEscalation|runAsUser|runAsNonRoot|capabilities|seccompProfile" -nFor any remaining highly privileged pods in group C, record:
- Namespace, pod name, containers
- Required elevated settings
- Business justification and review/expiry date
Keep this as part of your exception register and periodically repeat steps 2–6.
Using kubectl
# 1) List all pods and their service accounts (for review scope)
# Run on: any machine with kubectl access
kubectl get pods -A -o custom-columns='NAMESPACE:.metadata.namespace,NAME:.metadata.name,SA:.spec.serviceAccountName'
# 2) Show securityContext at pod and container level for a specific pod
# Replace <namespace> and <pod-name> with values from the previous command
kubectl get pod <pod-name> -n <namespace> -o yaml | \
sed -n '/^spec:/,/^status:/p' | \
egrep -A3 'securityContext|containers:|name:'
# 3) List pods that have no pod-level securityContext
kubectl get pods -A -o json | \
jq -r '
.items[]
| select(.spec.securityContext == null)
| [.metadata.namespace, .metadata.name]
| @tsv
'
# 4) List containers that lack a container-level securityContext
kubectl get pods -A -o json | \
jq -r '
.items[]
| . as $pod
| $pod.spec.containers[]
| select(.securityContext == null)
| [$pod.metadata.namespace, $pod.metadata.name, .name]
| @tsv
'
# 5) Surface pods/containers that are explicitly privileged
kubectl get pods -A -o json | \
jq -r '
.items[]
| . as $pod
| $pod.spec.containers[]
| select(.securityContext.privileged == true)
| [$pod.metadata.namespace, $pod.metadata.name, .name, "privileged=true"]
| @tsv
'
# 6) Surface pods/containers that can escalate privileges
kubectl get pods -A -o json | \
jq -r '
.items[]
| . as $pod
| $pod.spec.containers[]
| select(.securityContext.allowPrivilegeEscalation != false)
| [$pod.metadata.namespace, $pod.metadata.name, .name, "allowPrivilegeEscalation!=false"]
| @tsv
'
# 7) Surface pods/containers that run as root (where explicitly set)
kubectl get pods -A -o json | \
jq -r '
.items[]
| . as $pod
| (
if $pod.spec.securityContext.runAsUser == 0
then [$pod.metadata.namespace, $pod.metadata.name, "POD", "runAsUser=0"]
else empty
end
),
(
$pod.spec.containers[]
| select(.securityContext.runAsUser == 0)
| [$pod.metadata.namespace, $pod.metadata.name, .name, "runAsUser=0"]
)
| @tsv
'
# 8) Surface pods without readOnlyRootFilesystem set to true
kubectl get pods -A -o json | \
jq -r '
.items[]
| . as $pod
| $pod.spec.containers[]
| select(.securityContext.readOnlyRootFilesystem != true)
| [$pod.metadata.namespace, $pod.metadata.name, .name, "readOnlyRootFilesystem!=true"]
| @tsv
'
Interpretation / what indicates a problem (requires human judgement):
- Pods/containers listed by commands 3 and 4: no securityContext at pod or container level; review against your policy and the CIS guidance to decide if this is acceptable.
- Entries from command 5:
privileged=truealmost always needs strong justification; likely non-compliant. - Entries from command 6:
allowPrivilegeEscalation!=falsemeans privilege escalation is allowed; typically should be set tofalseunless there is a clear need. - Entries from command 7:
runAsUser=0means explicitly running as root; generally undesirable except for carefully reviewed system workloads. - Entries from command 8:
readOnlyRootFilesystem!=truemeans the root filesystem is writable; for many workloads CIS-like guidance prefersreadOnlyRootFilesystem: trueunless write access is required.
Use the detailed kubectl get pod ... -o yaml (command 2) for any flagged pod to review all securityContext fields in context (capabilities, seccompProfile, runAsNonRoot, fsGroup, etc.) and then decide specific changes in your manifests.
Automation
#!/usr/bin/env bash
#
# Report pods and containers without recommended securityContext settings.
# Run on: any machine with kubectl access
#
# Requirements: kubectl, jq
set -euo pipefail
# Namespace selector (empty = all)
NAMESPACE_SELECTOR="${1:-}"
if [[ -n "$NAMESPACE_SELECTOR" ]]; then
NS_ARG="-n $NAMESPACE_SELECTOR"
echo "Scanning namespace: $NAMESPACE_SELECTOR" >&2
else
NS_ARG="--all-namespaces"
echo "Scanning all namespaces" >&2
fi
# Fields we’ll check (aligned with common CIS guidance)
cat <<'HDR'
=== Security Context Report (per container) ===
Fields:
- ns : Namespace
- pod : Pod name
- ctr : Container name
- t : Type (C=container, I=initContainer)
- pr : privileged (true/false/empty)
- capAdd : Linux capabilities added (comma-separated)
- capDrop : Linux capabilities dropped (comma-separated)
- roFs : readOnlyRootFilesystem (true/false/empty)
- runAsNonRoot : runAsNonRoot (true/false/empty; pod or container)
- runAsUser : runAsUser (value/empty; pod or container)
- seLinux : SELinux options present (yes/no)
- allowPrivEsc : allowPrivilegeEscalation (true/false/empty)
POTENTIAL PROBLEMS (to review):
- pr=true
- allowPrivEsc=true or empty
- roFs=false or empty
- runAsNonRoot=false or empty AND runAsUser is 0 or empty
- capAdd has any value
- capDrop is empty
- seLinux=no
HDR
# Dump all pods with all fields, then process via jq
kubectl get pods ${NS_ARG} -o json | \
jq -r '
.items[]
| .metadata as $m
| .spec as $s
| .spec.securityContext as $psc
| [
# build an array of all containers and initContainers with type marker
(
( .spec.containers // [] | map(. + { _type: "C" }) ) +
( .spec.initContainers? // [] | map(. + { _type: "I" }) )
)[]
| . as $ctr
| $ctr.securityContext as $csc
# pod-level fields (can be inherited)
| $psc.runAsNonRoot as $podRunAsNonRoot
| $psc.runAsUser as $podRunAsUser
| $psc.seLinuxOptions as $podSeLinux
# container-level fields
| $csc.runAsNonRoot as $ctrRunAsNonRoot
| $csc.runAsUser as $ctrRunAsUser
| $csc.seLinuxOptions as $ctrSeLinux
# effective runAs* (container overrides pod)
| ($ctrRunAsNonRoot // $podRunAsNonRoot) as $effRunAsNonRoot
| ($ctrRunAsUser // $podRunAsUser) as $effRunAsUser
| ($ctrSeLinux // $podSeLinux) as $effSeLinux
# capabilities
| ($csc.capabilities.add // []) as $capAdd
| ($csc.capabilities.drop // []) as $capDrop
# other container-level fields
| $csc.privileged as $priv
| $csc.readOnlyRootFilesystem as $roFs
| $csc.allowPrivilegeEscalation as $allowPE
# derive simple flags
| ($effSeLinux | if . == null then "no" else "yes" end) as $seLinuxFlag
| ($capAdd | join(",")) as $capAddStr
| ($capDrop | join(",")) as $capDropStr
# output TSV line
| [
$m.namespace,
$m.name,
$ctr.name,
$ctr._type,
(if $priv == null then "" else $priv | tostring end),
$capAddStr,
$capDropStr,
(if $roFs == null then "" else $roFs | tostring end),
(if $effRunAsNonRoot == null then "" else $effRunAsNonRoot | tostring end),
(if $effRunAsUser == null then "" else $effRunAsUser | tostring end),
$seLinuxFlag,
(if $allowPE == null then "" else $allowPE | tostring end)
] | @tsv
]
' | \
awk '
BEGIN {
OFS="\t";
print "ns","pod","ctr","t","pr","capAdd","capDrop","roFs","runAsNonRoot","runAsUser","seLinux","allowPrivEsc","ISSUES";
}
{
ns=$1; pod=$2; ctr=$3; t=$4;
pr=$5; capAdd=$6; capDrop=$7; roFs=$8;
runAsNonRoot=$9; runAsUser=$10; seLinux=$11; allowPE=$12;
issues="";
# privileged
if (pr == "true") {
issues = issues "[privileged=true];";
}
# allowPrivilegeEscalation
if (allowPE == "true" || allowPE == "") {
issues = issues "[allowPrivilegeEscalation not false];";
}
# readOnlyRootFilesystem
if (roFs == "" || roFs == "false") {
issues = issues "[readOnlyRootFilesystem not true];";
}
# runAsNonRoot / runAsUser
if (runAsNonRoot == "" || runAsNonRoot == "false") {
if (runAsUser == "" || runAsUser == "0") {
issues = issues "[runs as root or not proven non-root];";
}
}
# capabilities
if (capAdd != "") {
issues = issues "[capabilities added: " capAdd "];";
}
if (capDrop == "") {
issues = issues "[no capabilities dropped];";
}
# SELinux
if (seLinux == "no") {
issues = issues "[no SELinux options];";
}
print ns,pod,ctr,t,pr,capAdd,capDrop,roFs,runAsNonRoot,runAsUser,seLinux,allowPE,issues;
}
'
cat <<'FOOT'
INTERPRETING OUTPUT:
- Each row is a single container or initContainer.
- The ISSUES column summarizes what needs review.
Examples of rows needing attention:
- ISSUES contains [privileged=true] -> container is privileged.
- ISSUES contains [allowPrivilegeEscalation not false] -> not explicitly disabled.
- ISSUES contains [readOnlyRootFilesystem not true] -> root FS writable.
- ISSUES contains [runs as root or not proven non-root]-> runAsNonRoot/runAsUser not set safely.
- ISSUES contains [capabilities added: ...] -> extra Linux capabilities granted.
- ISSUES contains [no capabilities dropped] -> consider dropping ALL or unneeded caps.
- ISSUES contains [no SELinux options] -> no SELinux confinement configured.
Use this report to:
- Identify pods/containers where securityContext is missing or too permissive.
- Prioritize high‑risk cases (privileged, root user, privilege escalation).
- Manually adjust pod specs/manifests according to your security policy and CIS guidance.
FOOT