Skip to main content

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

Manual Steps
  1. 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)"
    ][]'
  2. 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
  3. Add securityContext.capabilities.drop: ["ALL"] to each container

    • In the manifest’s pod template (spec.template.spec.containers and, 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 securityContext or capabilities already exists, merge without removing other needed fields (e.g., runAsNonRoot, readOnlyRootFilesystem).
  4. 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.
  5. Handle pods without controllers (bare Pods)

    • If ownerReferences is empty, export and edit the Pod manifest directly:
    kubectl get pod POD_NAME -n NAMESPACE -o yaml > /tmp/pod-POD_NAME.yaml
    vi /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_NAME
    kubectl apply -f /tmp/pod-POD_NAME.yaml
  6. 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:

  1. 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.

  2. 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.yaml

    Edit /tmp/deploy-capabilities.yaml so each container (and initContainer, if present) has:

    spec:
    template:
    spec:
    containers:
    - name: your-container
    securityContext:
    capabilities:
    drop:
    - "ALL"
    # if you must add specific capabilities back:
    # add:
    # - NET_BIND_SERVICE
    initContainers:
    - name: your-init-container
    securityContext:
    capabilities:
    drop:
    - "ALL"

    Then apply:

    kubectl apply -f /tmp/deploy-capabilities.yaml

    This will trigger a rollout of the Deployment.

  3. 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.yaml

    Edit each containers and initContainers entry as above, adding:

    securityContext:
    capabilities:
    drop:
    - "ALL"

    Then delete and recreate the Pod from the edited manifest:

    kubectl delete -n YOUR_NAMESPACE pod YOUR_POD
    kubectl apply -f /tmp/pod-capabilities.yaml
  4. 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=true for 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."