Skip to main content

Ensure CNI Plugin Supports Network Policies

More Info:

The CNI plugin must support NetworkPolicy resources for traffic segmentation to be enforceable. Use a CNI plugin that supports network policies and follow least-privilege network design.

Risk Level

Medium

Address

Security

Compliance Standards

  • CIS EKS

Triage and Remediation

Remediation

Manual Steps
  1. Confirm the CNI plugin and its NetworkPolicy support

    • Run on: any machine with kubectl access
    • Commands:
      kubectl get pods -n kube-system -o wide | egrep 'calico|cilium|weave|flannel|antrea|kube-router'
      kubectl get daemonset -n kube-system -o wide
    • Review:
      • Identify the CNI in use (e.g., Calico, Cilium, Antrea) from pod/DaemonSet names.
      • Cross-check its documentation to confirm it supports Kubernetes NetworkPolicy (and whether it supports both ingress and egress).
  2. Check whether NetworkPolicy objects are in use at all

    • Run on: any machine with kubectl access
    • Commands:
      kubectl get networkpolicy --all-namespaces
    • Review:
      • If no NetworkPolicy resources exist, the cluster currently has no enforced traffic segmentation. You must design and deploy policies (starting with least-privilege) once you confirm the CNI supports them.
  3. Identify namespaces lacking a default restrictive policy

    • Run on: any machine with kubectl access
    • Commands:
      # List all namespaces
      kubectl get ns

      # For each namespace, list its policies (example for 'default'):
      kubectl get networkpolicy -n default -o yaml
    • Review for each namespace:
      • Check whether there is at least one “default deny” style policy that selects all pods and denies all ingress and (if supported) egress by default. Typical patterns:
        • PodSelector is empty (podSelector: {}) to match all pods.
        • No ingress section, or policyTypes includes Ingress but no allowed peers.
        • For egress control, policyTypes includes Egress and either no egress rules or only narrowly scoped destinations.
  4. Assess current policies against least-privilege

    • Run on: any machine with kubectl access
    • Commands:
      kubectl get networkpolicy --all-namespaces -o yaml > /tmp/all-networkpolicies.yaml
    • Review /tmp/all-networkpolicies.yaml:
      • Look for broad rules such as:
        • podSelector: {} combined with ingress from all sources or egress to 0.0.0.0/0.
        • namespaceSelector: {} without additional constraints.
      • Determine whether each policy exists to explicitly allow necessary traffic given a default-deny stance, or if it unintentionally creates wide-open access.
  5. Design and apply least-privilege “default deny” and scoped allow policies

    • Run on: any machine with kubectl access
    • For a namespace missing default-deny, create a manifest like:
      apiVersion: networking.k8s.io/v1
      kind: NetworkPolicy
      metadata:
      name: default-deny-all
      namespace: default
      spec:
      podSelector: {}
      policyTypes:
      - Ingress
      - Egress
    • Apply:
      kubectl apply -f default-deny-all.yaml
    • Then, iteratively define additional NetworkPolicy manifests that:
      • Select only the pods that must communicate.
      • Allow only specific ingress sources and/or egress destinations and ports required for application function.
  6. Verify enforcement and adjust as needed

    • Run on: any machine with kubectl access
    • Commands:
      kubectl get networkpolicy --all-namespaces -o wide
      kubectl describe networkpolicy -n default default-deny-all
    • Optional functional check:
      • Use test pods (e.g., kubectl run -it --rm test --image=busybox --restart=Never -- sh) to attempt connections that should be blocked or allowed, confirming the CNI is enforcing NetworkPolicy as designed. Adjust policies if legitimate traffic is unintentionally denied.
Using kubectl
# 1) List all installed CNI plugins (pods in kube-system)
# Run on: any machine with kubectl access
kubectl get pods -n kube-system -o wide

Look for pods with names like calico-*, cilium-*, azure-cni-*, aws-node, weave-net, antrea-*, etc.
Investigate the documented capabilities of the detected CNI. If it does not explicitly support Kubernetes NetworkPolicy (or you see only a basic CNI like flannel without policy add-ons), network policies will not be enforced.

# 2) Check if any NetworkPolicies exist cluster-wide
kubectl get networkpolicies --all-namespaces

If this returns No resources found (or very few policies in only one namespace), then there is likely no meaningful segmentation. This is a policy gap even if the CNI supports NetworkPolicy.

# 3) Inspect network policies in a given namespace
# Replace NAMESPACE with a real namespace, e.g. "default"
kubectl get networkpolicy -n NAMESPACE -o wide
kubectl describe networkpolicy -n NAMESPACE POLICY_NAME
kubectl get networkpolicy -n NAMESPACE -o yaml

Things that indicate a problem:

  • No “default deny” style policy (e.g. policies that select many/all pods and deny all ingress/egress by default).
  • Policies only allow traffic but never restrict it (no baseline deny).
  • podSelector: {} with overly broad ingress/egress rules that effectively allow all.
# 4) Verify whether default namespaces have baseline protection
kubectl get ns
# For each namespace of interest, inspect its policies:
for ns in default kube-system kube-public; do
echo "Namespace: $ns"
kubectl get networkpolicy -n "$ns"
done

If default or other application namespaces have no NetworkPolicy objects at all, pods there can communicate freely with each other and, often, with the internet. That conflicts with a least-privilege design.

# 5) Test that policies are enforced (requires existing policies)
# 5a) Get two pods in the same namespace
kubectl get pods -n NAMESPACE -o wide

# 5b) From one pod, try reaching another (adjust POD_NAME and TARGET_IP)
kubectl exec -n NAMESPACE -it POD_NAME -- ping -c 3 TARGET_IP
kubectl exec -n NAMESPACE -it POD_NAME -- nc -vz TARGET_IP TARGET_PORT

If you have a NetworkPolicy that should deny this traffic but pings / connections still succeed, that suggests:

  • The CNI plugin does not enforce NetworkPolicy, or
  • The policy is misconfigured (e.g. selectors don’t match pods).
# 6) Check if a "default deny" exists in important namespaces
# Example label-based search (may or may not return results)
kubectl get networkpolicy -A -l "policy-type=default-deny" -o yaml

If no namespaces have any “deny all” style policy (by label or by manual inspection of specs), then the cluster likely lacks a least-privilege baseline; traffic is broadly allowed until explicitly denied.

Use the findings above to decide:

  • Whether your current CNI needs to be replaced/upgraded to one that supports NetworkPolicy.
  • Where to design and apply deny-all (or global) policies as a baseline and then add narrowly scoped allow rules.
Automation
#!/usr/bin/env bash
# Purpose:
# - Report which CNI is installed
# - Check if it is one that supports NetworkPolicy
# - Summarize NetworkPolicy usage by namespace
#
# Run on: any machine with kubectl access and cluster-admin permissions

set -euo pipefail

echo "=== 1. Detecting CNI plugin(s) ==="
echo "-> Inspecting kube-system namespace for common CNI DaemonSets and Pods..."
kubectl get ds,pod -n kube-system -o wide | grep -Ei 'calico|cilium|weave|flannel|canal|antrea|ovn|amazon-vpc-cni|aws-node|azure|gke|cni' || \
echo "No obvious CNI DaemonSet/Pod found by name pattern."

echo
echo "=== 1a. Detailed CNI pod images (kube-system) ==="
kubectl get pods -n kube-system -o jsonpath='{range .items[*]}{.metadata.name}{" "}{range .spec.containers[*]}{.image}{" "}{end}{"\n"}{end}' \
| grep -Ei 'calico|cilium|weave|flannel|canal|antrea|ovn|cni|aws-node|azure|gke' || \
echo "No obvious CNI container images matched."

cat <<'EOF'

INTERPRETATION (CNI):
- You MUST confirm from documentation whether your detected CNI supports Kubernetes NetworkPolicy.
- Common cases:
* Supports NetworkPolicy: calico, cilium, antrea, weave-net (with NP), canal, ovn-kubernetes, GKE-native, Azure CNI (with NP), etc.
* Does NOT enforce NetworkPolicy (or only partially): basic flannel, some cloud-native CNIs unless NP mode is enabled.
- If you see only 'flannel' or a cloud CNI known not to enforce NetworkPolicy, traffic segmentation via NetworkPolicy is NOT effective -> PROBLEM.

EOF

echo "=== 2. Cluster-wide NetworkPolicy usage ==="
echo "-> Count NetworkPolicy objects per namespace"
echo "NAMESPACE NP_COUNT"
kubectl get networkpolicy --all-namespaces --no-headers 2>/dev/null \
| awk '{print $1}' | sort | uniq -c | awk '{printf "%-10s %s\n", $2, $1}' || \
echo "No NetworkPolicy resources found in any namespace."

echo
echo "=== 2a. Namespaces with ZERO NetworkPolicies ==="
# List all namespaces, join with those that have NPs
ns_with_np="$(kubectl get networkpolicy --all-namespaces --no-headers 2>/dev/null | awk '{print $1}' | sort -u || true)"
echo "Namespaces without any NetworkPolicy defined:"
kubectl get ns --no-headers | awk '{print $1}' | while read -r ns; do
if ! grep -qx "$ns" <<< "$ns_with_np"; then
echo " - $ns"
fi
done

cat <<'EOF'

INTERPRETATION (NetworkPolicy presence):
- Namespaces with ZERO NetworkPolicies are likely allowing all traffic by default.
- If your CNI DOES enforce NetworkPolicy, you should:
* At least have baseline policies in sensitive namespaces, and
* Consider a namespace-wide "default deny" (ingress/egress) where appropriate.

EOF

echo "=== 3. Detect namespaces missing a 'default deny' style policy ==="
echo "-> Heuristic: look for NetworkPolicies that set podSelector: {} and policyTypes including Ingress/Egress"
echo
echo "Namespaces and policies that LOOK like 'default deny' candidates:"
kubectl get networkpolicy --all-namespaces -o json \
| jq -r '
.items[]
| select(
(.spec.podSelector | tostring) == "{}"
)
| [
.metadata.namespace,
.metadata.name,
( .spec.policyTypes // [] | join(",") ),
( ( .spec.ingress // [] | length ) | tostring ),
( ( .spec.egress // [] | length ) | tostring )
]
| @tsv
' 2>/dev/null \
| awk 'BEGIN{printf "%-20s %-30s %-20s %-8s %-8s\n","NAMESPACE","POLICY","POLICY_TYPES","INGRESS","EGRESS"}
{printf "%-20s %-30s %-20s %-8s %-8s\n",$1,$2,$3,$4,$5}' || \
echo "No NetworkPolicies found that look like namespace-wide defaults."

cat <<'EOF'

INTERPRETATION (default deny):
- A robust least-privilege design usually:
* Starts with a namespace-wide deny-all (podSelector: {} and policyTypes: [Ingress,Egress]),
* Then adds specific allow rules.
- Potential PROBLEMS:
* Sensitive namespaces (e.g., containing workloads with secrets or external exposure) have:
- No NetworkPolicies at all, or
- Only allow rules but no clear default deny foundation.
* The CNI does not enforce NetworkPolicy, making any defined policies ineffectual.

EOF

echo "=== 4. Verification summary ==="
echo "- Confirm from vendor docs that the detected CNI ENFORCES NetworkPolicy."
echo "- Review:"
echo " * Namespaces with no NetworkPolicies"
echo " * Namespaces lacking a clear default deny-style policy"
echo "If CNI does not support NetworkPolicy, or policies do not follow least privilege, this check is NOT satisfied."

Problem indicators in the script output:

  • CNI detection only shows plugins known not to enforce NetworkPolicy (e.g., plain flannel, or a cloud CNI without NP support enabled).
  • No NetworkPolicy resources found in any namespace. for a production cluster.
  • Sensitive namespaces listed under “Namespaces without any NetworkPolicy defined”.
  • No or very few “default deny”-style policies in the heuristic section for critical namespaces.