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 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}'
  2. For each application namespace that should be isolated (replace my-namespace with 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/v1
    kind: NetworkPolicy
    metadata:
    name: default-deny-ingress
    spec:
    podSelector: {}
    policyTypes:
    - Ingress
    EOF
  3. (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/v1
    kind: NetworkPolicy
    metadata:
    name: allow-from-namespace-foo
    spec:
    podSelector: {}
    ingress:
    - from:
    - namespaceSelector:
    matchLabels:
    name: foo
    policyTypes:
    - Ingress
    EOF
  4. 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 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'

    Confirm that is_compliant=true is reported for the cluster and that each application namespace shows defaultDenyPolicies greater than 0.

Using kubectl

On any machine with kubectl access:

  1. 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}'
  1. For each application namespace that is missing a default-deny ingress NetworkPolicy, create one. Example manifest (save as default-deny-ingress.yaml and 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
  1. 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."