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
-
Identify non-system namespaces that lack a default-deny NetworkPolicy (run on 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)"][]' | grep 'is_compliant=false' || echo "All non-system namespaces are compliant" -
For each non-compliant application namespace (replace
YOUR_NAMESPACE), create a default-deny ingress NetworkPolicy manifest (run on any machine with kubectl access):cat > deny-all-ingress-YOUR_NAMESPACE.yaml << 'EOF'apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:name: default-deny-ingressnamespace: YOUR_NAMESPACEspec:podSelector: {}policyTypes:- IngressEOF -
Apply the default-deny ingress NetworkPolicy to the target namespace (run on any machine with kubectl access):
kubectl apply -f deny-all-ingress-YOUR_NAMESPACE.yaml -
(Optional but recommended) For each application namespace, create additional NetworkPolicies that explicitly allow only the required ingress flows for your workloads (run on any machine with kubectl access). Example skeleton to edit before applying:
cat > allow-required-ingress-YOUR_NAMESPACE.yaml << 'EOF'apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:name: allow-required-ingressnamespace: YOUR_NAMESPACEspec:podSelector:matchLabels:app: YOUR_APP_LABELpolicyTypes:- Ingressingress:- from:- namespaceSelector:matchLabels:name: ALLOWED_NAMESPACEEOFkubectl apply -f allow-required-ingress-YOUR_NAMESPACE.yaml -
Repeat steps 2–4 for each non-compliant application namespace you need to protect.
-
Verify that every 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 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)"][]'Confirm there are no lines with
is_compliant=false.
Using kubectl
Run these steps from any machine with kubectl access.
kubectl get namespaces \
--no-headers \
-o custom-columns='NAME:.metadata.name' \
| grep -Ev '^(kube-system|kube-public|kube-node-lease)$' \
| while read ns; do
cat <<EOF | kubectl apply -n "$ns" -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
done
If you prefer to apply to a specific namespace only, for example production:
cat <<EOF | kubectl apply -n production -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
Verification (same automated check logic):
{ 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
# Purpose: Ensure every non-system namespace in an EKS cluster has a default-deny
# ingress NetworkPolicy. Safe to re-run (idempotent).
# Runs on: Any machine with kubectl access to the cluster.
set -euo pipefail
# 1. Discover target namespaces (exclude system namespaces)
echo "Discovering non-system namespaces..."
NAMESPACES=$(kubectl get namespaces -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | \
grep -vE '^(kube-system|kube-public|kube-node-lease)$' || true)
if [ -z "${NAMESPACES}" ]; then
echo "No non-system namespaces found. Nothing to do."
exit 0
fi
# 2. Apply/ensure default-deny ingress NetworkPolicy in each namespace
# - Empty podSelector: applies to all pods
# - policyTypes: [Ingress]
# - No ingress rules: deny all ingress
for ns in ${NAMESPACES}; do
echo "Ensuring default-deny ingress NetworkPolicy in namespace: ${ns}"
# Use a consistent name so it's easy to manage later
# This is idempotent: apply will create or update as needed.
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: ${ns}
labels:
security.cloudanix.com/default-deny: "true"
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
done
# 3. Verification (adapted from the audit command)
echo "Verifying default-deny ingress NetworkPolicy presence in all non-system namespaces..."
{
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 "Automation complete."