Skip to main content

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

Manual Steps
  1. List non-system namespaces missing a default-deny 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' \
    | grep 'is_compliant=false' || true
  2. Pick one non-compliant application namespace to fix
    From the previous output, note the value after name= for each line with is_compliant=false. Choose one namespace (for example app-namespace) and substitute that name exactly in the following commands.

  3. 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/v1
    kind: NetworkPolicy
    metadata:
    name: default-deny-ingress
    namespace: app-namespace
    spec:
    podSelector: {}
    policyTypes:
    - Ingress
    EOF

    Replace every occurrence of app-namespace with the actual namespace name you are fixing.

  4. Apply the default-deny ingress NetworkPolicy
    Run on: any machine with kubectl access

    kubectl apply -f default-deny-ingress-app-namespace.yaml
  5. Repeat for remaining non-compliant namespaces
    For each other namespace shown with is_compliant=false, repeat steps 3–4, adjusting the filename and the namespace: field each time (or reuse the same filename and overwrite it before each kubectl apply).

  6. Verify all non-system namespaces now have 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'

    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."