Every Non-System Namespace Should Have A Default-Deny
More Info:
Verifies each application namespace has a default-deny ingress NetworkPolicy. Without one, every pod is reachable from every other pod.
Risk Level
High
Address
Security
Compliance Standards
- Cloudanix Best Practice
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List non-system namespaces that need a default-deny NetworkPolicy (run on any machine with kubectl access):
kubectl get namespaces \--no-headers \| awk '!/kube-system|kube-public|kube-node-lease/ {print $1}' -
For each application namespace that should be isolated (replace
my-namespacewith the actual namespace), create a default-deny ingress NetworkPolicy (run on any machine with kubectl access):kubectl apply -n my-namespace -f - <<'EOF'apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:name: default-deny-ingressspec:podSelector: {}policyTypes:- IngressEOF -
(Optional but recommended) In each namespace where you created the default-deny policy, define explicit allow NetworkPolicies for the traffic that should be permitted (for example, allowing ingress from a specific namespace; run on any machine with kubectl access and adjust selectors as needed):
kubectl apply -n my-namespace -f - <<'EOF'apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:name: allow-from-namespace-foospec:podSelector: {}ingress:- from:- namespaceSelector:matchLabels:name: foopolicyTypes:- IngressEOF -
Verify that each non-system namespace now has at least one default-deny ingress NetworkPolicy (run on any machine with kubectl access):
{ kubectl get networkpolicies --all-namespaces -o jsonkubectl get namespaces -o json} | jq -rs '.[0] as $nps | .[1] |[ .items[]| select(.metadata.name as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)| .metadata as $m| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels| ([ $nps.items[]| select(.metadata.namespace == $m.name)| select((.spec.podSelector == {}) or (.spec.podSelector.matchLabels == null and .spec.podSelector.matchExpressions == null))| select((.spec.policyTypes // []) | index("Ingress")) ] | length) as $deny| "kind=Namespace name=\($m.name) uid=\($m.uid) apiVersion=v1"+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)+ (if $labels == "" then "" else " labels=\($labels)" end)+ " defaultDenyPolicies=\($deny)"+ " is_compliant=\(if $deny > 0 then "true" else "false" end)"] as $rows| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'Confirm that
is_compliant=trueis reported for the cluster and that each application namespace showsdefaultDenyPoliciesgreater than 0.
Using kubectl
On any machine with kubectl access:
- List non-system namespaces that need a default-deny NetworkPolicy
kubectl get ns \
--no-headers \
| awk '!/kube-system|kube-public|kube-node-lease/ {print $1}'
- For each application namespace that is missing a default-deny ingress NetworkPolicy, create one. Example manifest (save as
default-deny-ingress.yamland apply per namespace):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: example-namespace
spec:
podSelector: {}
policyTypes:
- Ingress
Apply it:
kubectl apply -f default-deny-ingress.yaml
Or create it directly with kubectl (replace example-namespace each time):
kubectl apply -n example-namespace -f - <<'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
- Verification
Re-run the check (same logic as the audit) from any machine with kubectl access:
{ kubectl get networkpolicies --all-namespaces -o json
kubectl get namespaces -o json
} | jq -rs '
.[0] as $nps | .[1] |
[ .items[]
| select(.metadata.name as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ $nps.items[]
| select(.metadata.namespace == $m.name)
| select((.spec.podSelector == {}) or (.spec.podSelector.matchLabels == null and .spec.podSelector.matchExpressions == null))
| select((.spec.policyTypes // []) | index("Ingress")) ] | length) as $deny
| "kind=Namespace name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ " defaultDenyPolicies=\($deny)"
+ " is_compliant=\(if $deny > 0 then "true" else "false" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Automation
#!/usr/bin/env bash
#
# Enforce default-deny ingress NetworkPolicy in every non-system namespace
# that does not already have one.
#
# Requirements:
# - Run on any machine with kubectl access and current context set to target AKS cluster.
# - kubectl, jq must be installed.
set -euo pipefail
# Name of the default-deny NetworkPolicy we will manage
DEFAULT_DENY_NP_NAME="default-deny-ingress"
echo "Discovering non-system namespaces..."
# Get all non-system namespaces: exclude kube-system, kube-public, kube-node-lease
mapfile -t APP_NAMESPACES < <(
kubectl get namespaces -o json \
| jq -r '.items[]
| select(.metadata.name as $n
| ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata.name'
)
if [ "${#APP_NAMESPACES[@]}" -eq 0 ]; then
echo "No non-system namespaces found. Nothing to do."
else
echo "Non-system namespaces: ${APP_NAMESPACES[*]}"
fi
for ns in "${APP_NAMESPACES[@]}"; do
echo "Processing namespace: ${ns}"
# Check if a qualifying default-deny ingress NetworkPolicy already exists
existing_count="$(
kubectl get networkpolicies -n "${ns}" -o json 2>/dev/null \
| jq '[ .items[]
| select(
((.spec.podSelector // {}) == {})
or (.spec.podSelector.matchLabels == null
and .spec.podSelector.matchExpressions == null)
)
| select((.spec.policyTypes // []) | index("Ingress"))
] | length'
)" || existing_count="0"
if [ "${existing_count}" != "0" ]; then
echo " Namespace ${ns} already has at least one default-deny ingress NetworkPolicy (${existing_count} found). Skipping creation."
continue
fi
echo " No default-deny ingress NetworkPolicy found in ${ns}. Creating ${DEFAULT_DENY_NP_NAME}..."
# Apply an idempotent default-deny ingress NetworkPolicy manifest
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ${DEFAULT_DENY_NP_NAME}
namespace: ${ns}
labels:
security.cloud-bp/controls: "C3.1"
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
done
echo
echo "Verifying compliance using the benchmark audit logic..."
# Re-run the audit logic (adapted from the provided command)
{ kubectl get networkpolicies --all-namespaces -o json
kubectl get namespaces -o json
} | jq -rs '
.[0] as $nps | .[1] |
[ .items[]
| select(.metadata.name as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ $nps.items[]
| select(.metadata.namespace == $m.name)
| select((.spec.podSelector == {}) or (.spec.podSelector.matchLabels == null and .spec.podSelector.matchExpressions == null))
| select((.spec.policyTypes // []) | index("Ingress")) ] | length) as $deny
| "kind=Namespace name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ " defaultDenyPolicies=\($deny)"
+ " is_compliant=\(if $deny > 0 then "true" else "false" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
echo
echo "Verification complete. Ensure all non-system namespaces report is_compliant=true."