4.5 NetworkPolicies: Ingress, Egress & Default-Deny Isolation

Key Takeaways

  • By default, Kubernetes enforces an open, non-isolated network where all Pods can communicate with all other Pods and external destinations without restriction.
  • NetworkPolicies (networking.k8s.io/v1) are additive allow-lists; once a Pod is selected by any policy, it becomes isolated for the specified policyTypes (Ingress, Egress, or both).
  • A NetworkPolicy with an empty podSelector (podSelector: {}) and no ingress/egress rules defines a default-deny boundary for the entire namespace.
  • When configuring egress isolation, administrators MUST explicitly allow UDP/TCP port 53 to CoreDNS (kube-dns in kube-system); otherwise, all DNS resolution fails.
  • In policy rules, separate list elements under 'from'/'to' represent logical OR semantics, whereas combining selectors inside the same element represents logical AND semantics.
Last updated: August 2026

4.5 NetworkPolicies: Ingress, Egress & Default-Deny Isolation

By default, Kubernetes operates on a flat, non-isolated network model: every Pod in the cluster can transmit packets to and receive packets from any other Pod, regardless of namespace, node placement, or application tier. While this frictionless connectivity accelerates initial development, it poses a severe security risk in production enterprise environments.

The NetworkPolicy resource (networking.k8s.io/v1) is the native Kubernetes mechanism for implementing Microsegmentation and Zero-Trust Network Architecture. NetworkPolicies function as declarative, dynamic layer 3 and layer 4 (IP and Port) distributed firewall rules.


1. NetworkPolicy Fundamentals & The Allow-List Model

+-----------------------------------------------------------------------------------------+
|                        NETWORKPOLICY ISOLATION & ALLOW-LIST LOGIC                       |
|                                                                                         |
|   SCENARIO A: No NetworkPolicies Exist                                                  |
|   [ Pod A ] <================== (All Traffic Allowed) ==================> [ Pod B ]     |
|   Status: Pods are NON-ISOLATED. Ingress and Egress are wide open.                      |
|                                                                                         |
|   SCENARIO B: NetworkPolicy Selects Pod B for Ingress                                   |
|   [ Pod A ] - - - - - - - - - -> [ Pod B (ISOLATED) ] <================== [ Pod C ]     |
|                                  - Only explicitly allowed traffic (Pod C) reaches B.   |
|                                  - All other unlisted ingress (Pod A) is BLOCKED.       |
|                                                                                         |
|   * Core Principle: NetworkPolicies are ADDITIVE (Whitelists).                          |
|     There is no explicit "DENY" action; if any policy selects a Pod,                    |
|     it only accepts/sends traffic matching at least one allowed rule.                   |
+-----------------------------------------------------------------------------------------+

Key Enforcement Invariants:

  1. Additive Behavior: If multiple NetworkPolicies select the same Pod, their rules are combined using a logical OR. Traffic is permitted if it matches any rule in any applying policy.
  2. Directional Independence: A Pod can be isolated for Ingress (incoming traffic), isolated for Egress (outgoing traffic), isolated for both, or isolated for neither.
  3. CNI Enforcement Requirement: NetworkPolicy objects are pure API manifests stored in etcd. The enforcement of these rules is entirely the responsibility of the installed CNI network plugin. Flannel does NOT enforce NetworkPolicies! To enforce policies, the cluster must run an enforcing CNI such as Calico, Cilium, Weave Net, Kube-router, or Antrea.

2. Anatomy of the NetworkPolicy Specification

A NetworkPolicy consists of three primary components: Target Selection (podSelector), Directional Scoping (policyTypes), and Rule Sets (ingress / egress).

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: secure-backend-policy
  namespace: production
spec:
  # 1. Target Selector: Which pods in 'production' namespace does this policy apply to?
  podSelector:
    matchLabels:
      app.kubernetes.io/name: backend-api
      tier: api
  
  # 2. Policy Types: Explicitly declare which directions are isolated
  policyTypes:
    - Ingress
    - Egress
  
  # 3. Ingress Allow Rules: What incoming traffic is allowed?
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app.kubernetes.io/name: frontend-web
      ports:
        - protocol: TCP
          port: 8080
  
  # 4. Egress Allow Rules: What outgoing traffic is allowed?
  egress:
    - to:
        - podSelector:
            matchLabels:
              app.kubernetes.io/name: database
      ports:
        - protocol: TCP
          port: 5432
    # DNS egress to CoreDNS Pods in kube-system:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

3. The Four Foundational Baseline Isolation Patterns

Before implementing complex granular rules, security best practices mandate establishing baseline default policies in every namespace.

Pattern 1: Default-Deny All Ingress Traffic

Isolates all Pods in the namespace from incoming traffic. Any Pod created in this namespace will reject all incoming packets unless another policy explicitly allows them.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: production
spec:
  podSelector: {} # Empty selector matches ALL pods in the namespace
  policyTypes:
    - Ingress
  # Omitting 'ingress' field creates an empty allow-list (Deny All)

Pattern 2: Default-Deny All Egress Traffic

Isolates all Pods in the namespace from transmitting outgoing traffic. Warning: This blocks outbound DNS queries to CoreDNS unless explicitly permitted!

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: production
spec:
  podSelector: {} # Matches all pods
  policyTypes:
    - Egress
  # Omitting 'egress' field creates an empty allow-list (Deny All)

Pattern 3: Default-Deny ALL Traffic (Ingress AND Egress)

The gold standard for Zero-Trust environments. Every Pod is completely isolated in both directions upon creation.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

Pattern 4: Default-Allow All Ingress & Egress

Explicitly opens all traffic in a namespace (used to override restrictive higher-level configurations during debugging).

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-allow-all
  namespace: production
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - {} # Empty rule allows ALL ingress
  egress:
    - {} # Empty rule allows ALL egress

4. Selector Combinations: Logical AND vs. Logical OR Semantics

The most critical syntax pitfall on the CKA examination is confusing YAML array elements (-) in the from and to blocks, which changes the logic from AND to OR.

+-----------------------------------------------------------------------------------------+
|                        SELECTOR LOGIC: AND VS. OR SYNTAX                                |
|                                                                                         |
|   SYNTAX A: Logical OR (Two Separate Array Items with '-')                              |
|   ingress:                                                                              |
|     - from:                                                                             |
|         - namespaceSelector:           <--- ITEM 1: Allows ANY pod in namespaces with   |
|             matchLabels:                            label 'tier: trusted'               |
|               tier: trusted                                                             |
|         - podSelector:                 <--- ITEM 2: Allows pods with label 'app: web'   |
|             matchLabels:                            in the LOCAL namespace              |
|               app: web                                                                  |
|   * Result: Traffic allowed if IT MATCHES ITEM 1  OR  ITEM 2.                           |
|                                                                                         |
|   SYNTAX B: Logical AND (Single Array Item combining both selectors)                    |
|   ingress:                                                                              |
|     - from:                                                                             |
|         - namespaceSelector:           <--- SINGLE ITEM: Must match BOTH criteria:      |
|             matchLabels:                    1. Namespace has 'tier: trusted'            |
|               tier: trusted                 AND                                         |
|           podSelector:                      2. Pod has 'app: web'                       |
|             matchLabels:                                                                |
|               app: web                                                                  |
|   * Result: Traffic ONLY allowed from 'app: web' pods INSIDE 'tier: trusted' namespaces.|
+-----------------------------------------------------------------------------------------+

Working with ipBlock (CIDR Ranges):

ipBlock allows specifying external IP ranges (outside the cluster) for ingress/egress. It must not be used for internal Pod IPs because Pod IPs are dynamic.

  egress:
    - to:
        - ipBlock:
            cidr: 198.51.100.0/24
            except:
              - 198.51.100.50/32 # Block specific blacklisted host
      ports:
        - protocol: TCP
          port: 443

Named Ports vs. Numerical Ports:

NetworkPolicies support both numerical integers and string named ports. Named ports automatically resolve to the matching containerPort name in the target Pod manifest:

  ingress:
    - ports:
        - protocol: TCP
          port: http-metrics # Resolves to containerPort: 9090 in Pod spec

5. Practical Architecture: Complete Multi-Tier Zero-Trust Implementation

Consider an enterprise 3-tier web architecture in namespace ecommerce:

  • Frontend Tier (tier: frontend): Accepts HTTP traffic from the outside world; connects outbound only to Backend API and CoreDNS.
  • Backend API Tier (tier: backend): Accepts traffic only from Frontend; connects outbound only to Database and CoreDNS.
  • Database Tier (tier: database): Accepts PostgreSQL traffic only from Backend API; no outbound connections allowed (strict isolation).
# --- 1. DEFAULT DENY ALL IN NAMESPACE ---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: ecommerce
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]
---
# --- 2. FRONTEND POLICY ---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-policy
  namespace: ecommerce
spec:
  podSelector:
    matchLabels:
      tier: frontend
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: ingress-nginx
      ports:
        - protocol: TCP
          port: 80
  egress:
    - to:
        - podSelector:
            matchLabels:
              tier: backend
      ports:
        - protocol: TCP
          port: 8080
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
---
# --- 3. BACKEND API POLICY ---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
  namespace: ecommerce
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: frontend
      ports:
        - protocol: TCP
          port: 8080
  egress:
    - to:
        - podSelector:
            matchLabels:
              tier: database
      ports:
        - protocol: TCP
          port: 5432
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
---
# --- 4. DATABASE POLICY ---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: database-policy
  namespace: ecommerce
spec:
  podSelector:
    matchLabels:
      tier: database
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: backend
      ports:
        - protocol: TCP
          port: 5432
  # No egress rules: Database cannot initiate ANY outbound connections

6. NetworkPolicy Troubleshooting Runbook

When testing NetworkPolicies in the CKA exam:

  1. Verify CNI Plugin: Ensure Calico or Cilium is running (kubectl get pods -n kube-system).
  2. Check Namespace Labels: Namespaces referenced in namespaceSelector must have matching labels (kubectl get ns --show-labels). Note: Kubernetes 1.21+ automatically injects the label kubernetes.io/metadata.name: <namespace-name> on all namespaces.
  3. Test with nc or curl:
    # Exec into frontend pod and test connection to backend
    kubectl exec -it frontend-pod -n ecommerce -- nc -zvw 2 backend-svc 8080
    
Loading diagram...
Zero-Trust Multi-Tier NetworkPolicy Traffic Boundaries
Test Your Knowledge

An administrator applies a NetworkPolicy with 'spec.policyTypes: [Egress]' and an empty egress rule list to isolate all pods in the 'analytics' namespace. Immediately, all applications crash because they can no longer resolve database domain names. What is the cause and resolution?

A
B
C
D
Test Your Knowledge

Examine the following NetworkPolicy ingress snippet: ingress:

  • from:
    • namespaceSelector: matchLabels: env: prod
    • podSelector: matchLabels: role: monitoring
Which incoming connections will this policy permit?

A
B
C
D
Test Your Knowledge

A CKA candidate creates a NetworkPolicy intended to restrict traffic to a database pod. When testing from an unauthorized pod, the connection is still established. Inspection confirms the policy YAML syntax is flawless and the pod labels match exactly. Which of the following is the most likely reason the policy is not working?

A
B
C
D