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
HighAddress
SecurityCompliance Standards
- Cloudanix Best Practice
Triage and Remediation
- Remediation
Remediation
Manual Steps
Manual Steps
-
Identify noncompliant 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.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)" ][]' -
For a noncompliant Pod created directly (not via Deployment/ReplicaSet/etc.), edit the Pod spec (any machine with kubectl access):
In
kubectl -n <namespace> get pod <pod-name> -o yaml > /tmp/pod-fixed.yaml/tmp/pod-fixed.yaml, under each.spec.containers[].securityContextand.spec.initContainers[].securityContext:Then apply and recreate (Pods cannot be updated in place for some fields, so delete and recreate if needed):securityContext: capabilities: drop: - "ALL" # add back only what is required, for example: # add: # - "NET_BIND_SERVICE"kubectl -n <namespace> delete pod <pod-name> kubectl -n <namespace> apply -f /tmp/pod-fixed.yaml -
For Pods managed by a higher-level controller (e.g., Deployment, StatefulSet, DaemonSet), find the owning resource (any machine with kubectl access):
Then fetch the controller manifest:
kubectl -n <namespace> get pod <pod-name> -o jsonpath='{.metadata.ownerReferences[0].kind}{" "}{.metadata.ownerReferences[0].name}{"\n"}'kubectl -n <namespace> get <Kind-lowercased> <owner-name> -o yaml > /tmp/owner-fixed.yaml -
Edit the controller manifest to drop all capabilities (any machine with kubectl access). In
/tmp/owner-fixed.yaml, under each container and initContainer template path like:- Deployments:
.spec.template.spec.containers[]and.spec.template.spec.initContainers[] - StatefulSets/DaemonSets/ReplicaSets: same template path
Save the file.securityContext: capabilities: drop: - "ALL" # add: # - "NET_BIND_SERVICE" - Deployments:
-
Apply the updated controller spec and wait for Pods to be recreated (any machine with kubectl access):
kubectl -n <namespace> apply -f /tmp/owner-fixed.yaml kubectl -n <namespace> rollout status deployment/<owner-name> # for a Deployment # or: # kubectl -n <namespace> rollout status daemonset/<owner-name> # kubectl -n <namespace> rollout status statefulset/<owner-name> -
Verify compliance (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'
Using kubectl
Using kubectl
# 1) Identify non-compliant 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
| "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' \
| grep 'is_compliant=false' || echo "All non-system pods compliant"
# 2) Export the owning workload manifest (example: Deployment)
# Replace <namespace> and <deployment-name> with real values from step 1
# Run on: any machine with kubectl access
kubectl -n <namespace> get deploy <deployment-name> -o yaml > /tmp/deploy-<deployment-name>.yaml
# 3) Edit the manifest to drop ALL capabilities for every container
# Open /tmp/deploy-<deployment-name>.yaml in an editor and, for each container
# under spec.template.spec.containers and spec.template.spec.initContainers,
# add or modify securityContext like:
# ...
# spec:
# template:
# spec:
# containers:
# - name: app
# image: your-image:tag
# securityContext:
# capabilities:
# drop:
# - "ALL"
# # OPTIONAL: if the workload truly needs specific capabilities, add them back explicitly:
# # add:
# # - "NET_BIND_SERVICE"
# # other fields...
# initContainers:
# - name: init
# image: your-init-image:tag
# securityContext:
# capabilities:
# drop:
# - "ALL"
# # add: [] # or specific capabilities if absolutely required
# 4) Apply the updated manifest
# Run on: any machine with kubectl access
kubectl apply -f /tmp/deploy-<deployment-name>.yaml
# 5) Repeat steps 2–4 for each non-compliant owning object
# (e.g., Deployment, DaemonSet, StatefulSet, Job, CronJob, or standalone Pod)
# 6) Verification: re-run the benchmark audit command
# 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'
Automation
Automation
#!/usr/bin/env bash
set -euo pipefail
# Automation for CBP C1.5 on AKS:
# Ensure every container/initContainer drops ALL capabilities
# for Pods that are NOT managed by higher-level controllers.
#
# This script:
# - Detects non-compliant Pods (excluding system namespaces)
# - Patches only standalone Pods (no controller ownerReferences)
# - Adds securityContext.capabilities.drop: ["ALL"] where missing
# - Is idempotent and safe to re-run
#
# Run on: any machine with kubectl access to the AKS cluster
# Prereqs: kubectl, jq, yq (https://github.com/mikefarah/yq) v4+
command -v kubectl >/dev/null 2>&1 || { echo "kubectl not found"; exit 1; }
command -v jq >/dev/null 2>&1 || { echo "jq not found"; exit 1; }
command -v yq >/dev/null 2>&1 || { echo "yq not found (v4 required)"; exit 1; }
WORKDIR="$(mktemp -d)"
trap 'rm -rf "$WORKDIR"' EXIT
echo "Discovering non-compliant standalone 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)
# Only Pods without controller ownerReferences (standalone Pods)
| select(((.metadata.ownerReferences // [])
| map(select(.controller == true)) | length) == 0)
as $pod
| ($pod.spec.containers // [] + $pod.spec.initContainers // []) as $cs
| [$cs[]
| (.securityContext.capabilities.drop // []) as $drop
| (($drop | index("ALL")) or ($drop | index("all"))) as $ok
| select($ok | not)
] as $noncompliant_containers
| select(($noncompliant_containers | length) > 0)
| "\($pod.metadata.namespace) \($pod.metadata.name)"
' > "$WORKDIR/noncompliant_pods.txt"
if [[ ! -s "$WORKDIR/noncompliant_pods.txt" ]]; then
echo "No non-compliant standalone Pods found; nothing to change."
else
echo "Found the following non-compliant standalone Pods:"
cat "$WORKDIR/noncompliant_pods.txt"
fi
while read -r NS NAME; do
[[ -z "$NS" || -z "$NAME" ]] && continue
echo "Processing Pod ${NS}/${NAME} ..."
YAML_FILE="$WORKDIR/${NS}_${NAME}.yaml"
PATCH_FILE="$WORKDIR/${NS}_${NAME}_patch.yaml"
kubectl get pod "$NAME" -n "$NS" -o yaml > "$YAML_FILE"
# Build a strategic merge patch that:
# - ensures securityContext exists on each container/initContainer
# - ensures capabilities.drop contains "ALL"
#
# This is done by transforming the live YAML into a patch that
# only touches the relevant fields.
yq eval '
.spec |= (
. // {} |
.containers |= (
. // [] |
map(
.securityContext |= (. // {}) |
.securityContext.capabilities |= (. // {}) |
.securityContext.capabilities.drop |= (
. // [] |
(if (map(ascii_upcase) | index("ALL")) == null
then . + ["ALL"] | unique
else .
end)
)
)
) |
.initContainers |= (
. // [] |
map(
.securityContext |= (. // {}) |
.securityContext.capabilities |= (. // {}) |
.securityContext.capabilities.drop |= (
. // [] |
(if (map(ascii_upcase) | index("ALL")) == null
then . + ["ALL"] | unique
else .
end)
)
)
)
)
# Reduce to only the patch-relevant structure
| {spec: {containers: .spec.containers, initContainers: .spec.initContainers}}
' "$YAML_FILE" > "$PATCH_FILE"
# If nothing meaningful changed, skip
if diff -q <(yq '.spec' "$YAML_FILE") <(yq '.spec *+ (load("'"$PATCH_FILE"'") | .spec)' "$YAML_FILE") >/dev/null 2>&1; then
echo " No changes required for ${NS}/${NAME} (already compliant)."
continue
fi
echo " Applying patch to ${NS}/${NAME} ..."
kubectl patch pod "$NAME" -n "$NS" --type merge --patch-file "$PATCH_FILE"
done < "$WORKDIR/noncompliant_pods.txt"
echo
echo "Re-running compliance check to verify..."
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)
# Only standalone Pods
| select(((.metadata.ownerReferences // [])
| map(select(.controller == true)) | length) == 0)
| .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 standalone Pods are now compliant (drop ALL)."
else "Non-compliant standalone Pods still exist; investigate controller-managed workloads separately."
end
'

