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 noncompliant pods and their owning workloads (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
    | ([ ($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)"
    + (if $own == null then "" else " ownerKind=\($own.kind) ownerName=\($own.name)" end)
    ][]'
  2. For each listed pod, edit the owning workload manifest (Deployment, StatefulSet, DaemonSet, Job, CronJob, or the Pod itself) to drop all capabilities (run on any machine with kubectl access). Example for a Deployment:

    kubectl -n <namespace> edit deployment <deployment-name>

    Under each spec.template.spec.containers[] (and initContainers[] if present), add or modify:

    securityContext:
    capabilities:
    drop:
    - "ALL"
    # add:
    # - "NET_BIND_SERVICE" # example only if strictly required
  3. If the pod is not controlled by a higher-level workload (no ownerReferences), edit the Pod spec directly and reapply it as a new manifest (pods cannot be updated in place for some fields). Export the pod spec, clean it, and save as a manifest (run on any machine with kubectl access):

    kubectl -n <namespace> get pod <pod-name> -o yaml > /tmp/<pod-name>.yaml

    Edit /tmp/<pod-name>.yaml:

    • Remove status: section.
    • Remove fields under metadata: such as uid, resourceVersion, managedFields, creationTimestamp, and ownerReferences.
    • Under each container and initContainer, ensure:
      securityContext:
      capabilities:
      drop:
      - "ALL"
      # add:
      # - "<ONLY_WHAT_IS_NEEDED>"
  4. Apply the updated standalone Pod manifest (run on any machine with kubectl access):

    kubectl apply -f /tmp/<pod-name>.yaml
  5. Confirm updated workloads have rolled out successfully (run on any machine with kubectl access), for example for a Deployment:

    kubectl -n <namespace> rollout status deployment/<deployment-name>
  6. Verify compliance across the cluster (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
    | "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'

    Ensure that the output is either a single line is_compliant=true or that every listed container has is_compliant=true.

Using kubectl

On any machine with kubectl access:

  1. Identify the non‑compliant Pod and its controller

    kubectl get pods -A
    kubectl get pod <POD_NAME> -n <NAMESPACE> -o yaml | yq '.metadata.ownerReferences'

    If the Pod is owned by a controller (Deployment, StatefulSet, DaemonSet, Job, CronJob, etc.), you must edit the controller, not the Pod, or the change will be overwritten.

  2. Edit the Pod spec (ephemeral / stand‑alone Pod only)
    For a Pod not managed by a controller:

    kubectl edit pod <POD_NAME> -n <NAMESPACE>

    Under each container (and initContainer if present), add:

    spec:
    containers:
    - name: <container-name>
    securityContext:
    capabilities:
    drop:
    - "ALL"
    # add:
    # - "NET_BIND_SERVICE" # example only; include only what is required
    initContainers:
    - name: <init-container-name>
    securityContext:
    capabilities:
    drop:
    - "ALL"
  3. Edit the owning controller (preferred, declarative)

    Deployment example:

    kubectl edit deployment <DEPLOYMENT_NAME> -n <NAMESPACE>

    Add the same block under every container and initContainer:

    spec:
    template:
    spec:
    containers:
    - name: <container-name>
    securityContext:
    capabilities:
    drop:
    - "ALL"
    # add:
    # - "NET_BIND_SERVICE"
    initContainers:
    - name: <init-container-name>
    securityContext:
    capabilities:
    drop:
    - "ALL"

    For other controllers, replace deployment with statefulset, daemonset, job, or cronjob and edit .spec.template.spec similarly.

    If you manage manifests declaratively, update your YAML files with the same securityContext.capabilities.drop: ["ALL"] blocks and apply:

    kubectl apply -f <your-manifest>.yaml
  4. Verification

    Re‑run the benchmark audit command from 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
    | "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 that all listed containers now show capabilitiesDrop=ALL and is_compliant=true (or that the output is just is_compliant=true).

Automation
#!/usr/bin/env bash
set -euo pipefail

# Automation for: Containers Should Drop All Linux Capabilities (CBP C1.5)
# Scope: Oracle OKE, all namespaces except: kube-system, kube-public, kube-node-lease
# 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

WORKDIR="$(pwd)/capability-fix-$(date +%s)"
mkdir -p "${WORKDIR}"

echo "Discovering non-compliant pods..."
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)
| select((
((.spec.containers // []) + (.spec.initContainers // []))[]
| (.securityContext.capabilities.drop // []) as $drop
| (($drop | index("ALL")) or ($drop | index("all"))) == false
) // false)
| [.metadata.namespace, .metadata.name]
| @tsv' | sort -u > "${WORKDIR}/non_compliant_pods.tsv" || true

if [[ ! -s "${WORKDIR}/non_compliant_pods.tsv" ]]; then
echo "No non-compliant pods found. Nothing to change."
else
echo "Found non-compliant pods:"
cat "${WORKDIR}/non_compliant_pods.tsv"
fi

fix_pod() {
local ns="$1"
local pod="$2"

echo "Processing Pod ${ns}/${pod}..."

# Determine owner (Deployment/StatefulSet/DaemonSet/Job/CronJob) if any
owner_json="$(kubectl get pod "${pod}" -n "${ns}" -o jsonpath='{.metadata.ownerReferences}' 2>/dev/null || echo "")"
owner_kind=""
owner_name=""
if [[ -n "${owner_json}" && "${owner_json}" != "null" ]]; then
owner_kind="$(kubectl get pod "${pod}" -n "${ns}" -o jsonpath='{.metadata.ownerReferences[0].kind}' || true)"
owner_name="$(kubectl get pod "${pod}" -n "${ns}" -o jsonpath='{.metadata.ownerReferences[0].name}' || true)"
fi

# Decide target resource: prefer owner controller if present
target_kind="Pod"
target_name="${pod}"
if [[ -n "${owner_kind}" && -n "${owner_name}" ]]; then
target_kind="${owner_kind}"
target_name="${owner_name}"
fi

# Get raw YAML for target
yaml_path="${WORKDIR}/${ns}-${target_kind,,}-${target_name}.yaml"
echo " Fetching ${target_kind} ${ns}/${target_name}..."
if ! kubectl get "${target_kind,,}" "${target_name}" -n "${ns}" -o yaml > "${yaml_path}"; then
echo " WARNING: Failed to fetch ${target_kind} ${ns}/${target_name}, skipping." >&2
return
fi

# Create a patched copy
patched_path="${yaml_path%.yaml}.patched.yaml"
cp "${yaml_path}" "${patched_path}"

# Use yq if available for safer YAML edits; otherwise use jq on JSON
if command -v yq >/dev/null 2>&1; then
echo " Patching capabilities with yq..."
yq '
(.. | select(has("containers")) ) as $p
| . as $root
| ($root | path($p)) as $path
| ($root | getpath($path)) as $obj
| ($obj.containers // []) |=
(map(
.securityContext.capabilities.drop =
( ((.securityContext.capabilities.drop // [])
| map(select(. != "all" and . != "ALL"))
+ ["ALL"]) | unique )
))
| ($obj.initContainers // []) |=
(map(
.securityContext.capabilities.drop =
( ((.securityContext.capabilities.drop // [])
| map(select(. != "all" and . != "ALL"))
+ ["ALL"]) | unique )
))
' "${yaml_path}" > "${patched_path}.tmp" && mv "${patched_path}.tmp" "${patched_path}"
else
echo " yq not found; patching via JSON round-trip with jq..."
json_path="${yaml_path%.yaml}.json"
json_patched_path="${yaml_path%.yaml}.patched.json"
kubectl get "${target_kind,,}" "${target_name}" -n "${ns}" -o json > "${json_path}"
jq '
def ensure_drop_all:
.securityContext |= ( . // {} )
| .securityContext.capabilities |= ( . // {} )
| .securityContext.capabilities.drop |= (
( . // [] )
| map(select(. != "all" and . != "ALL"))
| . + ["ALL"]
| unique
);
if .spec.template? then
.spec.template.spec.containers |= (map(ensure_drop_all)) |
.spec.template.spec.initContainers |= (map(ensure_drop_all) // .)
else
.spec.containers |= (map(ensure_drop_all)) |
.spec.initContainers |= (map(ensure_drop_all) // .)
end
' "${json_path}" > "${json_patched_path}"
# Convert back to YAML with kubectl
kubectl apply -f "${json_patched_path}" --dry-run=client -o yaml > "${patched_path}"
fi

echo " Applying patched manifest for ${target_kind} ${ns}/${target_name}..."
kubectl apply -f "${patched_path}"

echo " Waiting for updated pods from ${target_kind} ${ns}/${target_name} to be Ready..."
case "${target_kind}" in
Deployment|StatefulSet|DaemonSet)
kubectl rollout status "${target_kind,,}/${target_name}" -n "${ns}" --timeout=5m || \
echo " WARNING: rollout status for ${target_kind} ${ns}/${target_name} did not complete in time." >&2
;;
CronJob)
echo " NOTE: CronJob pods will reflect changes on next scheduled run."
;;
Job|Pod)
kubectl get pod -n "${ns}" "${pod}" >/dev/null 2>&1 || true
;;
esac
}

if [[ -s "${WORKDIR}/non_compliant_pods.tsv" ]]; then
while IFS=$'\t' read -r ns pod; do
fix_pod "${ns}" "${pod}"
done < "${WORKDIR}/non_compliant_pods.tsv"
fi

echo
echo "Verifying compliance after remediation..."
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 "is_compliant=false" end'