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. 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"
  2. 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/v1
    kind: NetworkPolicy
    metadata:
    name: default-deny-ingress
    namespace: YOUR_NAMESPACE
    spec:
    podSelector: {}
    policyTypes:
    - Ingress
    EOF
  3. 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
  4. (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/v1
    kind: NetworkPolicy
    metadata:
    name: allow-required-ingress
    namespace: YOUR_NAMESPACE
    spec:
    podSelector:
    matchLabels:
    app: YOUR_APP_LABEL
    policyTypes:
    - Ingress
    ingress:
    - from:
    - namespaceSelector:
    matchLabels:
    name: ALLOWED_NAMESPACE
    EOF

    kubectl apply -f allow-required-ingress-YOUR_NAMESPACE.yaml
  5. Repeat steps 2–4 for each non-compliant application namespace you need to protect.

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