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 that are missing a 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| ([ $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| select($deny == 0)| .name ' -r -
For one non-compliant namespace (replace
your-namespacewith the actual name), create a manifest file defining a default-deny ingress NetworkPolicy (run on any machine with kubectl access):cat > default-deny-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 for that namespace (run on any machine with kubectl access):
kubectl apply -f default-deny-ingress-your-namespace.yaml -
Repeat steps 2–3 for each non-system namespace reported in step 1, changing the filename and the
namespace:field (run on any machine with kubectl access). -
(Optional but recommended) Define additional, more specific NetworkPolicies in each namespace to allow only the necessary ingress flows for your applications (run on any machine with kubectl access, using your own manifests and requirements):
kubectl apply -f your-allow-policy.yaml -
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| ([ $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) defaultDenyPolicies=\($deny)"+ " is_compliant=\(if $deny > 0 then "true" else "false" end)"] as $rows| if ($rows | map(select(. | test("is_compliant=false"))) | length) == 0then "is_compliant=true"else $rows[]end'
Using kubectl
# 1) List non-system namespaces and see which lack default-deny
# Run on: any machine with kubectl access
kubectl get ns
# 2) For each *application* namespace that should be isolated, create a default-deny NetworkPolicy.
# Replace <NAMESPACE> with the target namespace name.
cat <<'EOF' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: <NAMESPACE>
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
# Example for namespace "payments"
cat <<'EOF' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: payments
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
# 3) (Optional but recommended) Store the manifest in Git/IaC.
# Example manifest file you can commit, parameterized per namespace:
# file: networkpolicy-default-deny-ingress-payments.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: payments
spec:
podSelector: {}
policyTypes:
- Ingress
# Apply it:
kubectl apply -f networkpolicy-default-deny-ingress-payments.yaml
Verification (adapted from the audit):
# 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'
Automation
#!/usr/bin/env bash
#
# Ensure every non-system namespace in a GKE cluster has a default-deny
# ingress NetworkPolicy, per CBP C3.1.
#
# Requirements:
# - Run on any machine with kubectl access and sufficient RBAC
# - kubectl configured for the target GKE cluster/context
#
# Safe to re-run: uses create --dry-run=client | apply for idempotency.
set -euo pipefail
# Name of the default-deny policy to enforce in each namespace
POLICY_NAME="default-deny-ingress"
echo "Discovering non-system namespaces..."
# Get all namespaces except the standard system ones
mapfile -t NAMESPACES < <(
kubectl get namespaces -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' \
| grep -Ev '^(kube-system|kube-public|kube-node-lease)$' \
|| true
)
if [ "${#NAMESPACES[@]}" -eq 0 ]; then
echo "No non-system namespaces found."
else
for ns in "${NAMESPACES[@]}"; do
[ -z "$ns" ] && continue
echo "Ensuring default-deny ingress NetworkPolicy in namespace: ${ns}"
# Create-or-update a default-deny ingress NetworkPolicy:
# - empty podSelector => applies to all pods in the namespace
# - policyTypes: [Ingress] => denies all ingress by default
kubectl create networkpolicy "${POLICY_NAME}" \
--namespace "${ns}" \
--pod-selector='' \
--ingress='{}' \
--dry-run=client -o yaml \
| kubectl apply -f -
done
fi
echo
echo "Verification: checking that every non-system namespace has at least one"
echo "default-deny ingress NetworkPolicy (empty podSelector, policyTypes includes Ingress)."
echo
{ 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'