Containers Should Drop All Linux Capabilities
More Info:
Verifies every container drops ALL capabilities and adds back only what it needs. Excess capabilities expand the attack surface of a compromised container.
Risk Level
High
Address
Security
Compliance Standards
- Cloudanix Best Practice
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Identify offending pods and containers (run on any machine with kubectl access):
kubectl get pods --all-namespaces -o json | jq -r '[ .items[]| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)| .metadata as $m| ((.spec.containers // []) + (.spec.initContainers // []))[]| (.securityContext.capabilities.drop // []) as $drop| (($drop | index("ALL")) or ($drop | index("all"))) as $ok| select($ok | not)| "\($m.namespace) \($m.name) \(.name)"][]'Each line is:
NAMESPACE POD_NAME CONTAINER_NAME. Use it as input for the next steps. -
For each non-compliant workload managed by a higher-level controller (Deployment, StatefulSet, DaemonSet, Job, CronJob), edit the controller manifest (run on any machine with kubectl access):
kubectl -n <NAMESPACE> get deployment <DEPLOYMENT_NAME> -o yaml > /tmp/deploy-<DEPLOYMENT_NAME>.yamlOr replace
deploymentwithstatefulset,daemonset,job, orcronjobas appropriate. -
In the saved manifest file, add a
securityContext.capabilities.drop: ["ALL"]to every container (includinginitContainers) underspec.template.spec. For example:spec:template:spec:containers:- name: appimage: your-imagesecurityContext:capabilities:drop:- "ALL"# add back only what is strictly needed, e.g.:# add:# - NET_BIND_SERVICEinitContainers:- name: initimage: your-init-imagesecurityContext:capabilities:drop:- "ALL"If specific Linux capabilities are required, list them under
add:explicitly; otherwise omitadd:. -
Apply the updated manifest (run on any machine with kubectl access):
kubectl apply -f /tmp/deploy-<DEPLOYMENT_NAME>.yamlRepeat Steps 2–4 for each affected controller. This will cause controlled rollouts of new pods.
-
For stand-alone Pods (not owned by a controller), recreate them with the proper securityContext (run on any machine with kubectl access):
kubectl -n <NAMESPACE> get pod <POD_NAME> -o yaml > /tmp/pod-<POD_NAME>.yamlEdit
/tmp/pod-<POD_NAME>.yaml:- Remove
metadata.uid,metadata.resourceVersion,metadata.creationTimestamp,metadata.managedFields,status, and anyownerReferences. - Under
spec.containers[]andspec.initContainers[]add:securityContext:capabilities:drop:- "ALL"# add:# - <ONLY-REQUIRED-CAPABILITY>
Then delete and recreate:
kubectl -n <NAMESPACE> delete pod <POD_NAME>kubectl apply -f /tmp/pod-<POD_NAME>.yaml - Remove
-
Verify compliance (run on any machine with kubectl access):
kubectl get pods --all-namespaces -o json | jq -r '[ .items[]| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)| .metadata as $m| ((.spec.containers // []) + (.spec.initContainers // []))[]| (.securityContext.capabilities.drop // []) as $drop| (($drop | index("ALL")) or ($drop | index("all"))) as $ok| select($ok | not)] as $rows| if ($rows | length) == 0 then "is_compliant=true" else "is_compliant=false" end'The output must be
is_compliant=true.
Using kubectl
On any machine with kubectl access to the cluster:
-
Identify the noncompliant pod and its owning controller (from the audit output’s
owner=field). For example, if you see:owner=Deployment/default/my-app/...then you must edit the
Deployment/my-appin thedefaultnamespace (never edit only the live Pod). -
Edit the controller and add the capabilities drop to every container (including initContainers if present). Example for a Deployment:
kubectl -n default edit deployment my-appIn the opened manifest, under each container, add a
securityContext.capabilities.drop: ["ALL"]. If the container truly needs a specific capability, add it back underadd.Example
spec.template.specsnippet:spec:template:spec:securityContext:runAsNonRoot: truecontainers:- name: appimage: nginx:1.27securityContext:allowPrivilegeEscalation: falsecapabilities:drop:- "ALL"add:- "NET_BIND_SERVICE" # only if strictly required# ...initContainers:- name: init-jobimage: busybox:1.36securityContext:capabilities:drop:- "ALL"# add: [] or omit unless a specific capability is neededSave and exit; Kubernetes will roll out updated Pods with the new securityContext.
-
For objects not managed by higher-level controllers (for example, a standalone Pod manifest in Git), update the source manifest similarly, then apply:
kubectl apply -f /absolute/path/to/pod-or-controller.yaml -
Verification (from any machine with kubectl):
kubectl get pods --all-namespaces -o json | jq -r '[ .items[]| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)| .metadata as $m| (($m.ownerReferences // []) | map(select(.controller)) | first) as $own| ((.spec.containers // []) + (.spec.initContainers // []))[]| (.securityContext.capabilities.drop // []) as $drop| (($drop | index("ALL")) or ($drop | index("all"))) as $ok| "kind=Pod ns=\($m.namespace) name=\($m.name) container=\(.name) is_compliant=\(if $ok then "true" else "false" end)"] as $rows| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'Confirm all listed
is_compliant=values aretrue(or that no rows are output, meaning all applicable pods comply).
Automation
#!/usr/bin/env bash
set -euo pipefail
# Automation for: Containers Should Drop ALL Linux Capabilities (CBP C1.5)
# Runs on: any machine with kubectl access and jq installed
if ! command -v kubectl >/dev/null 2>&1; then
echo "kubectl not found in PATH" >&2
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "jq not found in PATH" >&2
exit 1
fi
TMPDIR="$(mktemp -d)"
trap 'rm -rf "$TMPDIR"' EXIT
echo "Identifying non-compliant Pods (excluding kube-system, kube-public, kube-node-lease)..."
# Get namespaced Pod list that is non-compliant (no ALL/all in capabilities.drop)
kubectl get pods --all-namespaces -o json \
| jq -r '
.items[]
| select(.metadata.namespace as $n
| ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| ((.spec.containers // []) + (.spec.initContainers // [])) as $allc
| [ $allc[]
| (.securityContext.capabilities.drop // []) as $drop
| (($drop | index("ALL")) or ($drop | index("all"))) as $ok
| select($ok | not)
] as $badContainers
| select(($badContainers | length) > 0)
| "\($m.namespace) \($m.name)"
' | sort -u > "$TMPDIR/non_compliant_pods.txt"
if ! [[ -s "$TMPDIR/non_compliant_pods.txt" ]]; then
echo "All Pods already drop ALL capabilities (excluding system namespaces). Nothing to do."
else
echo "Non-compliant Pods detected:"
cat "$TMPDIR/non_compliant_pods.txt"
fi
# Function: patch a single Pod manifest yaml to ensure ALL is in drop list for all containers/initContainers
patch_pod_yaml() {
local in_yaml=$1 out_yaml=$2
# jq-based transformation via yq (if available) or kubectl-kustomize-style is complex.
# Use yq v4 if present for robust YAML editing.
if command -v yq >/dev/null 2>&1; then
yq '
.spec |= (
if has("containers") then
.containers |= map(
.securityContext.capabilities.drop |= (
(//[]) as $d
| if ($d | index("ALL")) != null then $d else ($d + ["ALL"]) end
)
)
else . end
)
| .spec |= (
if has("initContainers") then
.initContainers |= map(
.securityContext.capabilities.drop |= (
(//[]) as $d
| if ($d | index("ALL")) != null then $d else ($d + ["ALL"]) end
)
)
else . end
)
' "$in_yaml" > "$out_yaml"
return 0
fi
# Fallback: server-side strategic merge patch using kubectl (no local YAML change)
# This function is not used when yq is missing.
return 1
}
if [[ -s "$TMPDIR/non_compliant_pods.txt" ]]; then
echo
echo "Patching non-compliant Pods (type: Pod only; higher-level controllers must be fixed via their manifests separately)."
while read -r NS NAME; do
[[ -z "$NS" || -z "$NAME" ]] && continue
echo "Processing Pod: $NS/$NAME"
POD_JSON="$(kubectl get pod "$NAME" -n "$NS" -o json)"
# Build a strategic merge patch that adds "ALL" to drop for each container/initContainer
PATCH="$(echo "$POD_JSON" | jq '
{
"spec": {
"containers": ( (.spec.containers // []) | map(
{
"name": .name,
"securityContext": {
"capabilities": {
"drop": (
(
((.securityContext.capabilities.drop // []) | map(
if . == "all" then "ALL" else . end
)) as $d
| if ($d | index("ALL")) != null then $d else ($d + ["ALL"]) end
)
)
}
}
}
)),
"initContainers": ( (.spec.initContainers // []) | map(
{
"name": .name,
"securityContext": {
"capabilities": {
"drop": (
(
((.securityContext.capabilities.drop // []) | map(
if . == "all" then "ALL" else . end
)) as $d
| if ($d | index("ALL")) != null then $d else ($d + ["ALL"]) end
)
)
}
}
}
))
}
}
')"
echo "$PATCH" > "$TMPDIR/patch-$NS-$NAME.json"
kubectl patch pod "$NAME" -n "$NS" --type merge -p "$(cat "$TMPDIR/patch-$NS-$NAME.json")" >/dev/null
done < "$TMPDIR/non_compliant_pods.txt"
fi
echo
echo "Re-running compliance audit to verify result..."
kubectl get pods --all-namespaces -o json | jq -r '
[ .items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| ((.spec.containers // []) + (.spec.initContainers // []))[]
| (.securityContext.capabilities.drop // []) as $drop
| (($drop | index("ALL")) or ($drop | index("all"))) as $ok
| select($ok | not)
] as $rows
| if ($rows | length) == 0 then
"All non-system Pods now have securityContext.capabilities.drop including ALL (is_compliant=true)"
else
"Some Pods are still non-compliant (is_compliant=false). Review owning controllers and their manifests."
end
'