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 not compliant (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' -
For each non-compliant application namespace (replace
APPLICATION_NAMESPACEwith the actual name), create a default-deny ingress NetworkPolicy manifest file (run on any machine with kubectl access):cat > default-deny-ingress-APPLICATION_NAMESPACE.yaml << 'EOF'apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:name: default-deny-ingressnamespace: APPLICATION_NAMESPACEspec:podSelector: {}policyTypes:- IngressEOF -
Apply the default-deny NetworkPolicy to that namespace (run on any machine with kubectl access):
sed "s/APPLICATION_NAMESPACE/your-namespace-name/g" \default-deny-ingress-APPLICATION_NAMESPACE.yaml | kubectl apply -f - -
(Optional but recommended) For each application, create additional NetworkPolicies in its namespace to allow only required ingress flows (run on any machine with kubectl access). For example, create a file:
cat > allow-namespace-APPLICATION_NAMESPACE.yaml << 'EOF'apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:name: allow-required-ingressnamespace: APPLICATION_NAMESPACEspec:podSelector:matchLabels:app: your-app-labelpolicyTypes:- Ingressingress:- from:- namespaceSelector:matchLabels:name: allowed-namespaceEOFsed -e "s/APPLICATION_NAMESPACE/your-namespace-name/g" \-e "s/your-app-label/actual-app-label/g" \-e "s/allowed-namespace/actual-allowed-namespace-label/g" \allow-namespace-APPLICATION_NAMESPACE.yaml | kubectl apply -f - -
Repeat steps 2–4 for each non-system, non-compliant namespace reported in step 1.
-
Verify that all non-system namespaces now have 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 there are no lines with
is_compliant=false.
Using kubectl
On any machine with kubectl access:
- Identify non-compliant namespaces (replace the jq path if needed, but this is the same logic as the audit):
{ 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
Assume this prints (example):
team-a
team-b
- Create a default-deny ingress NetworkPolicy manifest for each non-compliant namespace.
Example for team-a:
cat << 'EOF' > default-deny-ingress-team-a.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: team-a
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
Apply it:
kubectl apply -f default-deny-ingress-team-a.yaml
Repeat for each namespace that needs the policy, changing only the namespace: field and filename:
cat << 'EOF' > default-deny-ingress-team-b.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: team-b
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
kubectl apply -f default-deny-ingress-team-b.yaml
- Verification (same logic as the audit, run from any machine with kubectl):
{ 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
#
# Apply a default-deny ingress NetworkPolicy to every non-system namespace
# that does not already have one.
#
# Run on: any machine with kubectl access to the cluster.
# Requirements: bash, kubectl, jq in PATH, current context pointing to target OKE cluster.
set -euo pipefail
# Namespaces to ignore (system namespaces)
SYSTEM_NAMESPACES=(
"kube-system"
"kube-public"
"kube-node-lease"
)
# Name of the default-deny NetworkPolicy we will manage
NP_NAME="default-deny-ingress"
# Convert SYSTEM_NAMESPACES to jq array
jq_sys_ns_array() {
local first=1 out="["
for ns in "${SYSTEM_NAMESPACES[@]}"; do
if [ "$first" -eq 1 ]; then
first=0
else
out+=","
fi
out+="\"$ns\""
done
out+="]"
printf '%s\n' "$out"
}
main() {
# Ensure we can talk to the cluster
kubectl version --request-timeout='5s' >/dev/null 2>&1 || {
echo "ERROR: kubectl cannot reach the cluster. Check context and network." >&2
exit 1
}
# Get all namespaces except the system ones
mapfile -t app_namespaces < <(
kubectl get namespaces -o json | jq -r \
--argjson sys "$(jq_sys_ns_array)" '
.items[]
| select(.metadata.name as $n | $sys | index($n) | not)
| .metadata.name
'
)
if [ "${#app_namespaces[@]}" -eq 0 ]; then
echo "No non-system namespaces found; nothing to do."
fi
# For each application namespace, ensure a default-deny ingress NetworkPolicy exists
for ns in "${app_namespaces[@]}"; do
echo "Processing namespace: ${ns}"
# Detect if there is already at least one default-deny ingress NetworkPolicy in this namespace
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))
and ((.spec.policyTypes // []) | index("Ingress")))
] | length
' 2>/dev/null || echo "0"
)"
if [ "$existing_count" -gt 0 ]; then
echo " Namespace ${ns} already has a default-deny ingress NetworkPolicy (${existing_count} found); skipping creation."
continue
fi
echo " Creating ${NP_NAME} NetworkPolicy in namespace ${ns}"
# Apply an idempotent default-deny ingress NetworkPolicy
# Empty podSelector + policyTypes: [Ingress] -> deny all ingress by default.
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ${NP_NAME}
namespace: ${ns}
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
done
echo
echo "Verification (re-running benchmark-style audit):"
{
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
'
}
main "$@"