Skip to main content

Ensure All Namespaces Have Network Policies Defined

More Info:

Namespaces without NetworkPolicy objects allow unrestricted pod-to-pod traffic. Every namespace holding user workloads should have at least one NetworkPolicy defined.

Risk Level

High

Address

Security

Compliance Standards

  • CIS GKE

Triage and Remediation

Remediation

Manual Steps
  1. List namespaces missing NetworkPolicy objects
    Run on: any machine with kubectl access

    (kubectl get ns -o json; kubectl get networkpolicy -A -o json) \
    | jq -rs '
    (.[0].items | map(.metadata.name)
    | map(select(.!="kube-system" and .!="kube-public" and .!="kube-node-lease"))) as $ns
    |
    ( (.[1].items // [])
    | sort_by(.metadata.namespace)
    | group_by(.metadata.namespace)
    | map({key: .[0].metadata.namespace, value: length})
    | from_entries
    ) as $np
    |
    [ $ns[] | select( ($np[.] // 0) == 0 ) ] as $missing
    |
    $missing[]
    '

    Save the output list of namespaces; you will create at least one NetworkPolicy in each.

  2. For a selected namespace, create a baseline “default deny ingress” NetworkPolicy
    Run on: any machine with kubectl access
    Replace TARGET_NAMESPACE with the namespace you are fixing (for each namespace, repeat with its name):

    cat > default-deny-ingress-TARGET_NAMESPACE.yaml << 'EOF'
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
    name: default-deny-ingress
    namespace: TARGET_NAMESPACE
    spec:
    podSelector: {}
    policyTypes:
    - Ingress
    EOF

    sed -i 's/TARGET_NAMESPACE/your-namespace-name/g' default-deny-ingress-your-namespace-name.yaml
    kubectl apply -f default-deny-ingress-your-namespace-name.yaml
  3. (Optional but recommended) Add a default deny egress policy for the namespace
    Run on: any machine with kubectl access
    Replace TARGET_NAMESPACE with the namespace name:

    cat > default-deny-egress-TARGET_NAMESPACE.yaml << 'EOF'
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
    name: default-deny-egress
    namespace: TARGET_NAMESPACE
    spec:
    podSelector: {}
    policyTypes:
    - Egress
    EOF

    sed -i 's/TARGET_NAMESPACE/your-namespace-name/g' default-deny-egress-your-namespace-name.yaml
    kubectl apply -f default-deny-egress-your-namespace-name.yaml
  4. (Optional) Create explicit allow policies to restore required traffic
    Run on: any machine with kubectl access
    Example: allow all pods in the same namespace to talk to each other (adapt as needed per application):

    cat > allow-same-namespace-TARGET_NAMESPACE.yaml << 'EOF'
    apiVersion: networking.k8s.io/v1
    kind: NetworkPolicy
    metadata:
    name: allow-same-namespace
    namespace: TARGET_NAMESPACE
    spec:
    podSelector: {}
    ingress:
    - from:
    - podSelector: {}
    policyTypes:
    - Ingress
    EOF

    sed -i 's/TARGET_NAMESPACE/your-namespace-name/g' allow-same-namespace-your-namespace-name.yaml
    kubectl apply -f allow-same-namespace-your-namespace-name.yaml
  5. Repeat for all namespaces that were missing NetworkPolicies
    Run on: any machine with kubectl access
    For each namespace identified in step 1, create at least one NetworkPolicy (for example, the default deny policies plus any needed allow rules) by repeating steps 2–4 with the appropriate namespace name.

  6. Verify all namespaces now have at least one NetworkPolicy
    Run on: any machine with kubectl access

    (kubectl get ns -o json; kubectl get networkpolicy -A -o json) \
    | jq -rs '
    (.[0].items | map(.metadata.name)
    | map(select(.!="kube-system" and .!="kube-public" and .!="kube-node-lease"))) as $ns
    |
    ( (.[1].items // [])
    | sort_by(.metadata.namespace)
    | group_by(.metadata.namespace)
    | map({key: .[0].metadata.namespace, value: length})
    | from_entries
    ) as $np
    |
    [ $ns[] | select( ($np[.] // 0) == 0 ) ] as $missing
    |
    if ($missing|length)>0
    then ($missing[] | "FOUND_NAMESPACE_WITHOUT_NETWORKPOLICY:"+.)
    else "ALL_NAMESPACES_HAVE_NETWORK_POLICIES"
    end
    '

    The check is remediated when the command prints ALL_NAMESPACES_HAVE_NETWORK_POLICIES.

Using kubectl
# 1. Identify namespaces missing NetworkPolicies
# Run on: any machine with kubectl access
(kubectl get ns -o json; kubectl get networkpolicy -A -o json) \
| jq -rs '
(.[0].items | map(.metadata.name)
| map(select(.!="kube-system" and .!="kube-public" and .!="kube-node-lease"))) as $ns
|
( (.[1].items // [])
| sort_by(.metadata.namespace)
| group_by(.metadata.namespace)
| map({key: .[0].metadata.namespace, value: length})
| from_entries
) as $np
|
[ $ns[] | select( ($np[.] // 0) == 0 ) ] as $missing
|
if ($missing|length)>0
then ($missing[] | .)
else empty
end
'

# Suppose the above prints a list like:
# default
# dev
# prod
# Replace this list explicitly in the manifest below.

Create a baseline, deny-all-ingress / allow-all-egress NetworkPolicy for each workload namespace:

# 2. Create a manifest with one NetworkPolicy per affected namespace
# Run on: any machine with kubectl access
cat > baseline-namespace-networkpolicies.yaml << 'EOF'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: default
spec:
podSelector: {} # select all Pods in this namespace
policyTypes:
- Ingress
ingress: [] # deny all ingress

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: dev
spec:
podSelector: {}
policyTypes:
- Ingress
ingress: []

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: prod
spec:
podSelector: {}
policyTypes:
- Ingress
ingress: []
EOF

Apply the NetworkPolicies:

# 3. Apply the NetworkPolicy resources
# Run on: any machine with kubectl access
kubectl apply -f baseline-namespace-networkpolicies.yaml

Verification (rerun the benchmark audit logic):

# 4. Verify all non-system namespaces now have at least one NetworkPolicy
# Run on: any machine with kubectl access
(kubectl get ns -o json; kubectl get networkpolicy -A -o json) \
| jq -rs '
(.[0].items | map(.metadata.name)
| map(select(.!="kube-system" and .!="kube-public" and .!="kube-node-lease"))) as $ns
|
( (.[1].items // [])
| sort_by(.metadata.namespace)
| group_by(.metadata.namespace)
| map({key: .[0].metadata.namespace, value: length})
| from_entries
) as $np
|
[ $ns[] | select( ($np[.] // 0) == 0 ) ] as $missing
|
if ($missing|length)>0
then ($missing[] | "FOUND_NAMESPACE_WITHOUT_NETWORKPOLICY:"+.)
else "ALL_NAMESPACES_HAVE_NETWORK_POLICIES"
end
'
Automation
#!/usr/bin/env bash
set -euo pipefail

# Automation for CIS GKE 4.3.2:
# Ensure each non-system namespace has at least one NetworkPolicy.
# Runs from: any machine with kubectl access and jq installed.

# CONFIG: label selector for namespaces to manage
NS_SELECTOR='!name in (kube-system,kube-public,kube-node-lease)'

# 1. Discover namespaces that currently have zero NetworkPolicies
echo "[INFO] Discovering namespaces without NetworkPolicy objects..."

MISSING_NS=$(
(kubectl get ns -o json; kubectl get networkpolicy -A -o json) \
| jq -rs '
(.[0].items
| map(.metadata.name)
| map(select(.!="kube-system" and .!="kube-public" and .!="kube-node-lease"))
) as $ns
|
( (.[1].items // [])
| sort_by(.metadata.namespace)
| group_by(.metadata.namespace)
| map({key: .[0].metadata.namespace, value: length})
| from_entries
) as $np
|
[ $ns[] | select( ($np[.] // 0) == 0 ) ][]'
)

if [[ -z "${MISSING_NS}" ]]; then
echo "[INFO] ALL_NAMESPACES_HAVE_NETWORK_POLICIES (excluding kube-system, kube-public, kube-node-lease)"
exit 0
fi

echo "[INFO] Namespaces without NetworkPolicy:"
printf ' - %s\n' ${MISSING_NS}

# 2. For each missing namespace, create a baseline NetworkPolicy if none exists.
# This example creates a default-deny-all policy. Review before use.
# You can customize per namespace by editing the manifest generation below.

for NS in ${MISSING_NS}; do
echo "[INFO] Processing namespace: ${NS}"

# Double-check idempotency: skip if any NetworkPolicy now exists (race-safe).
NP_COUNT=$(kubectl get networkpolicy -n "${NS}" --no-headers 2>/dev/null | wc -l || true)
if [[ "${NP_COUNT}" -gt 0 ]]; then
echo "[INFO] Namespace ${NS} already has NetworkPolicy objects; skipping creation."
continue
fi

echo "[INFO] Creating baseline default-deny-all NetworkPolicy in namespace ${NS}"

# Apply a default-deny policy using kubectl apply (idempotent).
# Policy name is deterministic so re-runs are safe.
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: ${NS}
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
EOF

done

# 3. Verification using the authoritative audit logic
echo "[INFO] Verifying that all non-system namespaces now have at least one NetworkPolicy..."

(kubectl get ns -o json; kubectl get networkpolicy -A -o json) \
| jq -rs '
(.[0].items | map(.metadata.name)
| map(select(.!="kube-system" and .!="kube-public" and .!="kube-node-lease"))) as $ns
|
( (.[1].items // [])
| sort_by(.metadata.namespace)
| group_by(.metadata.namespace)
| map({key: .[0].metadata.namespace, value: length})
| from_entries
) as $np
|
[ $ns[] | select( ($np[.] // 0) == 0 ) ] as $missing
|
if ($missing|length)>0
then ($missing[] | "FOUND_NAMESPACE_WITHOUT_NETWORKPOLICY:"+.)
else "ALL_NAMESPACES_HAVE_NETWORK_POLICIES"
end
'