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 all noncompliant pods
- 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.nodeName // "") as $node| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own| ((.spec.containers // []) + (.spec.initContainers // []))[]| (.securityContext.capabilities.drop // []) as $drop| (($drop | index("ALL")) or ($drop | index("all"))) as $ok| select($ok | not)| "ns=\($m.namespace) pod=\($m.name) container=\(.name)"][]' -
Locate and edit the owning workload manifest(s)
- For each noncompliant pod, find the controller and kind:
kubectl get pod POD_NAME -n NAMESPACE -o jsonpath='{.metadata.ownerReferences[0].kind}{" "}{.metadata.ownerReferences[0].name}{"\n"}'- Export the current manifest for that controller (example for a Deployment):
kubectl get deployment DEPLOYMENT_NAME -n NAMESPACE -o yaml > /tmp/deployment-DEPLOYMENT_NAME.yaml- Edit the file:
vi /tmp/deployment-DEPLOYMENT_NAME.yaml -
Add
securityContext.capabilities.drop: ["ALL"]to each container- In the manifest’s pod template (
spec.template.spec.containersand, if present,spec.template.spec.initContainers), ensure each container has:
securityContext:capabilities:drop:- "ALL"# add:# - "NET_BIND_SERVICE" # example; include only if strictly required- If
securityContextorcapabilitiesalready exists, merge without removing other needed fields (e.g.,runAsNonRoot,readOnlyRootFilesystem).
- In the manifest’s pod template (
-
Apply the updated manifest
- Run on: any machine with kubectl access
kubectl apply -f /tmp/deployment-DEPLOYMENT_NAME.yaml- For other controller kinds (StatefulSet, DaemonSet, Job, CronJob), export, edit, and apply their manifests similarly.
-
Handle pods without controllers (bare Pods)
- If
ownerReferencesis empty, export and edit the Pod manifest directly:
kubectl get pod POD_NAME -n NAMESPACE -o yaml > /tmp/pod-POD_NAME.yamlvi /tmp/pod-POD_NAME.yaml- Add the same
securityContext.capabilities.drop: ["ALL"]stanza to each container, then recreate the pod:
kubectl delete -n NAMESPACE pod POD_NAMEkubectl apply -f /tmp/pod-POD_NAME.yaml - If
-
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)] | if (length)==0 then "is_compliant=true" else .[] end'- The environment is compliant when the command outputs only
is_compliant=true.
Using kubectl
On any machine with kubectl access:
-
Identify the noncompliant Pod and its owner (from the audit output). If the Pod is owned by a higher-level resource (Deployment, DaemonSet, Job, etc.), you must patch that owner; changes to a standalone Pod will be lost when it is recreated.
-
Example: patch a Deployment to drop all capabilities for all app containers.
kubectl -n YOUR_NAMESPACE get deploy YOUR_DEPLOYMENT -o yaml > /tmp/deploy-capabilities.yamlEdit
/tmp/deploy-capabilities.yamlso each container (and initContainer, if present) has:spec:template:spec:containers:- name: your-containersecurityContext:capabilities:drop:- "ALL"# if you must add specific capabilities back:# add:# - NET_BIND_SERVICEinitContainers:- name: your-init-containersecurityContext:capabilities:drop:- "ALL"Then apply:
kubectl apply -f /tmp/deploy-capabilities.yamlThis will trigger a rollout of the Deployment.
-
Example: patch a standalone Pod (not recommended for controllers, but works for ad‑hoc Pods):
kubectl -n YOUR_NAMESPACE get pod YOUR_POD -o yaml > /tmp/pod-capabilities.yamlEdit each
containersandinitContainersentry as above, adding:securityContext:capabilities:drop:- "ALL"Then delete and recreate the Pod from the edited manifest:
kubectl delete -n YOUR_NAMESPACE pod YOUR_PODkubectl apply -f /tmp/pod-capabilities.yaml -
Verification (same 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.nodeName // "") as $node| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels| ([ ($m.ownerReferences // [])[] | 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) uid=\($m.uid) apiVersion=v1"+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)+ (if $node == "" then "" else " node=\($node)" end)+ (if $labels == "" then "" else " labels=\($labels)" end)+ (if $own == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)+ " container=\(.name) image=\(.image)"+ " capabilitiesDrop=\(if ($drop | length) == 0 then "none" else ($drop | join("+")) end)"+ " is_compliant=\(if $ok then "true" else "false" end)"] as $rows| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'Confirm
is_compliant=truefor all relevant containers.
Automation
#!/usr/bin/env bash
#
# Enforce: all containers drop ALL Linux capabilities (CBP C1.5)
# Scope: EKS – Kubernetes API objects via kubectl
# Requirements: kubectl, jq installed; current context points to target cluster.
set -euo pipefail
# Temporary workspace
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT
echo "Discovering non-compliant Pods..."
# Capture non-compliant pod JSON (excluding control-plane/system namespaces per check)
kubectl get pods --all-namespaces -o json > "${WORKDIR}/all-pods.json"
# Function: list non-compliant pod namespaced-names
list_non_compliant() {
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 $cs
| select(
any($cs[];
.securityContext.capabilities.drop as $drop
| (($drop | index("ALL")) or ($drop | index("all"))) | not
)
)
| "\($m.namespace) \($m.name)"
' "${WORKDIR}/all-pods.json" | sort -u
}
NON_COMPLIANT_LIST="$(list_non_compliant || true)"
if [ -z "$NON_COMPLIANT_LIST" ]; then
echo "No non-compliant Pods found. Cluster is already compliant."
exit 0
fi
echo "Found non-compliant Pods:"
echo "$NON_COMPLIANT_LIST"
# Helper: ensure securityContext.capabilities.drop contains "ALL"
patch_pod_json='
# For each container-like list (containers, initContainers):
def ensure_drop_all:
if . == null then
[] # no containers
else
map(
. as $c
| ($c.securityContext // {}) as $sc
| ($sc.capabilities // {}) as $cap
| ($cap.drop // []) as $drop
| $c
| .securityContext = $sc
| .securityContext.capabilities = $cap
| .securityContext.capabilities.drop =
(if ( ($drop | index("ALL")) or ($drop | index("all")) )
then $drop
else ($drop + ["ALL"] | unique)
end)
)
end;
. as $pod
| $pod
| .spec.containers = (.spec.containers | ensure_drop_all)
| .spec.initContainers = (.spec.initContainers | ensure_drop_all)
'
echo
echo "Patching non-compliant Pods by updating their Pod specs."
echo "NOTE: If Pods are controlled by Deployments/ReplicaSets/DaemonSets/etc.,"
echo " you MUST update the controller manifests instead; otherwise this"
echo " change will be overwritten on reschedule."
# Attempt to patch each non-compliant Pod (best-effort).
# This is idempotent: reruns will keep drop=["ALL", ...] unchanged.
while read -r ns name; do
[ -z "$ns" ] && continue
echo "Processing Pod ${ns}/${name}..."
kubectl get pod "${name}" -n "${ns}" -o json > "${WORKDIR}/${ns}-${name}.orig.json" || {
echo " Skipping ${ns}/${name}: unable to fetch (may have been deleted)."
continue
}
jq "$patch_pod_json" "${WORKDIR}/${ns}-${name}.orig.json" > "${WORKDIR}/${ns}-${name}.patched.json"
# Use server-side apply via "kubectl replace" (patching full Pod object)
# This will restart the Pod.
kubectl replace -n "${ns}" -f "${WORKDIR}/${ns}-${name}.patched.json" >/dev/null || {
echo " WARNING: Failed to replace Pod ${ns}/${name}. It may be controlled by a higher-level resource."
echo " Update the owning controller (Deployment/DaemonSet/StatefulSet/Job, etc.) manifest instead."
continue
}
echo " Patched ${ns}/${name}."
done <<< "$NON_COMPLIANT_LIST"
echo
echo "Re-running compliance check for verification..."
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
' > "${WORKDIR}/compliance-status.txt"
cat "${WORKDIR}/compliance-status.txt"
STATUS="$(cat "${WORKDIR}/compliance-status.txt")"
if [ "$STATUS" != "is_compliant=true" ]; then
echo
echo "Some workloads remain non-compliant."
echo "They are most likely managed by higher-level controllers."
echo "Identify them with:"
echo
echo ' kubectl get pods --all-namespaces -o json | jq -r '\'''
echo ' [ .items[]'
echo ' | select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)'
echo ' | .metadata as $m'
echo ' | ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own'
echo ' | ((.spec.containers // []) + (.spec.initContainers // []))[]'
echo ' | (.securityContext.capabilities.drop // []) as $drop'
echo ' | (($drop | index("ALL")) or ($drop | index("all"))) as $ok'
echo ' | select($ok | not)'
echo ' | "ns=\($m.namespace) pod=\($m.name) owner=\($own.kind)/\($m.namespace)/\($own.name)"'
echo ' ][]'\'''
echo
echo "Then modify the corresponding Deployment/DaemonSet/StatefulSet/Job manifests"
echo "to include securityContext.capabilities.drop: [\"ALL\"] for each container."
exit 1
fi
echo "Cluster now compliant for this control."