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.
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:
- 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.
- Directional Independence: A Pod can be isolated for Ingress (incoming traffic), isolated for Egress (outgoing traffic), isolated for both, or isolated for neither.
- 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:
- Verify CNI Plugin: Ensure Calico or Cilium is running (
kubectl get pods -n kube-system). - Check Namespace Labels: Namespaces referenced in
namespaceSelectormust have matching labels (kubectl get ns --show-labels). Note: Kubernetes 1.21+ automatically injects the labelkubernetes.io/metadata.name: <namespace-name>on all namespaces. - Test with
ncorcurl:# Exec into frontend pod and test connection to backend kubectl exec -it frontend-pod -n ecommerce -- nc -zvw 2 backend-svc 8080
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?
Examine the following NetworkPolicy ingress snippet:
ingress:
Which incoming connections will this policy permit?
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?