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 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'
  2. For each non-compliant application namespace (replace APPLICATION_NAMESPACE with 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/v1
    kind: NetworkPolicy
    metadata:
    name: default-deny-ingress
    namespace: APPLICATION_NAMESPACE
    spec:
    podSelector: {}
    policyTypes:
    - Ingress
    EOF
  3. 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 -
  4. (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/v1
    kind: NetworkPolicy
    metadata:
    name: allow-required-ingress
    namespace: APPLICATION_NAMESPACE
    spec:
    podSelector:
    matchLabels:
    app: your-app-label
    policyTypes:
    - Ingress
    ingress:
    - from:
    - namespaceSelector:
    matchLabels:
    name: allowed-namespace
    EOF

    sed -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 -
  5. Repeat steps 2–4 for each non-system, non-compliant namespace reported in step 1.

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

  1. 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
  1. 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
  1. 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 "$@"