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 missing a default-deny NetworkPolicy
Run on: any machine with kubectl accessnps=$(kubectl get networkpolicies --all-namespaces -o json)kubectl get namespaces -o json | jq -r --argjson nps "$nps" '[ .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' \| grep 'is_compliant=false' || true -
Pick one non-compliant application namespace to fix
From the previous output, note the value aftername=for each line withis_compliant=false. Choose one namespace (for exampleapp-namespace) and substitute that name exactly in the following commands. -
Create a default-deny ingress NetworkPolicy manifest for that namespace
Run on: any machine with kubectl access (local file creation)cat > default-deny-ingress-app-namespace.yaml << 'EOF'apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:name: default-deny-ingressnamespace: app-namespacespec:podSelector: {}policyTypes:- IngressEOFReplace every occurrence of
app-namespacewith the actual namespace name you are fixing. -
Apply the default-deny ingress NetworkPolicy
Run on: any machine with kubectl accesskubectl apply -f default-deny-ingress-app-namespace.yaml -
Repeat for remaining non-compliant namespaces
For each other namespace shown withis_compliant=false, repeat steps 3–4, adjusting the filename and thenamespace:field each time (or reuse the same filename and overwrite it before eachkubectl apply). -
Verify all non-system namespaces now have a default-deny ingress NetworkPolicy
Run on: any machine with kubectl accessnps=$(kubectl get networkpolicies --all-namespaces -o json)kubectl get namespaces -o json | jq -r --argjson nps "$nps" '[ .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 there are no lines with
is_compliant=false.
Using kubectl
# 1) Identify non-system namespaces that lack a default-deny ingress NetworkPolicy
# Run on: any machine with kubectl access
nps=$(kubectl get networkpolicies --all-namespaces -o json)
kubectl get namespaces -o json | jq -r --argjson nps "$nps" '
[ .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' | \
awk '$0 ~ /is_compliant=false/ {for (i=1;i<=NF;i++) if ($i ~ /^name=/){split($i,a,"="); print a[2]}}' \
> /tmp/namespaces-missing-default-deny.txt
cat /tmp/namespaces-missing-default-deny.txt
Create a manifest template for the default-deny ingress NetworkPolicy:
# 2) Create a manifest template (edit as needed for naming conventions)
# Run on: any machine with kubectl access
cat > /tmp/default-deny-ingress-networkpolicy.yaml << 'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
Apply the default-deny ingress NetworkPolicy to each non-compliant namespace:
# 3) Apply to each listed namespace
# Run on: any machine with kubectl access
while read ns; do
[ -z "$ns" ] && continue
echo "Applying default-deny ingress NetworkPolicy to namespace: $ns"
kubectl apply -n "$ns" -f /tmp/default-deny-ingress-networkpolicy.yaml
done < /tmp/namespaces-missing-default-deny.txt
Verification:
# 4) Re-run the audit to confirm every non-system namespace has a default-deny ingress policy
# Run on: any machine with kubectl access
nps=$(kubectl get networkpolicies --all-namespaces -o json)
kubectl get namespaces -o json | jq -r --argjson nps "$nps" '
[ .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
# Automation: Ensure every non-system namespace has a default-deny ingress NetworkPolicy
# Scope: run on any machine with kubectl access and a current kube-context
set -euo pipefail
# Name of the default-deny NetworkPolicy to create/ensure in each namespace
NP_NAME="default-deny-ingress"
# 1. Discover target namespaces (exclude core system namespaces)
echo "Discovering non-system namespaces..."
mapfile -t TARGET_NAMESPACES < <(
kubectl get namespaces -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' \
| grep -Ev '^(kube-system|kube-public|kube-node-lease)$' \
| sort
)
if [ "${#TARGET_NAMESPACES[@]}" -eq 0 ]; then
echo "No non-system namespaces found. Nothing to do."
exit 0
fi
# 2. Ensure a default-deny ingress NetworkPolicy exists in each namespace
for ns in "${TARGET_NAMESPACES[@]}"; do
echo "Ensuring default-deny ingress NetworkPolicy in namespace: ${ns}"
# Idempotent apply: creates if absent, updates if present
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ${NP_NAME}
namespace: ${ns}
spec:
podSelector: {} # Selects all pods in the namespace
policyTypes:
- Ingress
# No ingress rules defined -> deny all ingress traffic by default
EOF
done
# 3. Verification: re-run the benchmark-style logic to confirm compliance
echo "Verifying that every non-system namespace has at least one default-deny ingress NetworkPolicy..."
nps_json="$(kubectl get networkpolicies --all-namespaces -o json)"
kubectl get namespaces -o json | jq -r --argjson nps "$nps_json" '
[ .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 "Verification complete. Review the 'is_compliant' field above; all non-system namespaces should report is_compliant=true."