Fixing 'Egress Traffic Denied by NetworkPolicy' Between Kubernetes Namespaces
Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 10–20 mins
TL;DR
- What broke: A NetworkPolicy with no
egressblock (or a misconfigurednamespaceSelector) is silently dropping all outbound traffic from your pods to another namespace. - How to fix it: Add an explicit
egressrule with the correctnamespaceSelectormatching the destination namespace's labels, plus the requiredportsblock. - Fast path: Use our Client-Side Sandbox below to auto-refactor this — paste your failing NetworkPolicy YAML and get a corrected version without sending your config to any external server.
The Incident (What Does the Error Mean?)
You'll see this surface in pod logs, curl timeouts, or via a service mesh like Istio/Linkerd as:
Error: dial tcp <ClusterIP>:<port>: i/o timeout
# or from a CNI like Calico:
EGRESS traffic denied by NetworkPolicy: source=<pod> destination=<namespace>/<service>
Kubernetes NetworkPolicy is default-deny by omission. The moment you attach any NetworkPolicy to a pod, all traffic not explicitly permitted is dropped — including egress. If your policy has an ingress block but no egress block, every outbound packet from that pod is silently killed. No ICMP unreachable. No RST. Just a timeout.
Immediate consequence: Any pod selected by this policy cannot reach services in other namespaces — databases, auth services, external APIs routed through a gateway namespace. This is a full service communication blackout.
The Attack Vector / Blast Radius
This is a misconfiguration-as-outage pattern. The blast radius depends on how broadly the podSelector is scoped:
- If
podSelector: {}(matches all pods in namespace) — entire namespace is egress-isolated. Every microservice calling a dependency in another namespace fails simultaneously. - Cascading failure path:
frontend→ can't reachauth-service(different namespace) → returns 500 → circuit breaker trips → frontend marks itself unhealthy → load balancer pulls it → full service outage. - In a zero-trust posture, this is intended behavior applied at the wrong scope. The danger is that developers add a NetworkPolicy for ingress hardening and unknowingly nuke all egress without realizing NetworkPolicy is additive-only and non-reversible without an explicit allow rule.
- DNS is the first casualty.
kube-dnslives inkube-system. If your egress policy doesn't explicitly allow UDP/TCP 53 tokube-system, DNS resolution fails before any application-level connection is even attempted. This makes the error appear as a generic connection failure, masking the real cause.
How to Fix It (The Solution)
Basic Fix — Add Egress Rule for Target Namespace
First, confirm the destination namespace has the correct label:
kubectl get namespace target-namespace --show-labels
# Must show a label you can match, e.g.: kubernetes.io/metadata.name=target-namespace
Then patch your NetworkPolicy:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-network-policy
namespace: source-namespace
spec:
podSelector:
matchLabels:
app: my-service
policyTypes:
- Ingress
+ - Egress
ingress:
- from:
- podSelector:
matchLabels:
app: allowed-client
+ egress:
+ - to:
+ - namespaceSelector:
+ matchLabels:
+ kubernetes.io/metadata.name: target-namespace
+ ports:
+ - protocol: TCP
+ port: 8080
+ - to:
+ - namespaceSelector:
+ matchLabels:
+ kubernetes.io/metadata.name: kube-system
+ ports:
+ - protocol: UDP
+ port: 53
+ - protocol: TCP
+ port: 53
Do not forget the DNS egress rule. Skipping it means your pods resolve nothing, and the real NetworkPolicy error gets buried under DNS timeout noise.
Enterprise Best Practice — Least-Privilege Egress with Combined Selectors
Using only namespaceSelector is too broad if the target namespace runs multiple services. Combine namespaceSelector + podSelector to pin egress to the exact target pods:
egress:
- - to:
- - namespaceSelector:
- matchLabels:
- kubernetes.io/metadata.name: target-namespace
- ports:
- - protocol: TCP
- port: 8080
+ - to:
+ - namespaceSelector:
+ matchLabels:
+ kubernetes.io/metadata.name: target-namespace
+ podSelector:
+ matchLabels:
+ app: payment-service
+ tier: backend
+ ports:
+ - protocol: TCP
+ port: 8080
Critical: When
namespaceSelectorandpodSelectorare under the same-list item (sametoentry), Kubernetes evaluates them as AND (intersection). If you put them as separate-entries, it's OR (union) — which is almost always too permissive. This YAML indentation distinction is the #1 source of NetworkPolicy bugs in production.
💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.
Prevention in CI/CD
1. OPA/Gatekeeper — Enforce egress DNS rule presence:
Write a Rego policy that rejects any NetworkPolicy with policyTypes containing Egress but no egress rule targeting port 53. Gate this in your admission webhook.
2. Checkov scan in pipeline:
checkov -f networkpolicy.yaml --check CKV_K8S_7
# CKV_K8S_7: Ensure NetworkPolicy is not overly permissive
Add custom Checkov checks for missing DNS egress rules — the built-in checks don't catch this by default.
3. Kyverno policy — Require namespace labels before NetworkPolicy references them:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-ns-label-for-egress
spec:
validationFailureAction: Enforce
rules:
- name: check-namespaceselector-label
match:
resources:
kinds: [NetworkPolicy]
validate:
message: "Egress namespaceSelector must use kubernetes.io/metadata.name label."
pattern:
spec:
egress:
- to:
- namespaceSelector:
matchLabels:
"kubernetes.io/metadata.name": "?*"
4. Automated integration test: Run a netpol-tester job (e.g., networkpolicy-viewer or cyclonus) as a post-deploy step in staging. It enumerates all NetworkPolicies and validates actual pod-to-pod reachability against declared intent. Fail the pipeline if reachability matrix diverges from spec.
5. Label hygiene: Enforce via CI that every namespace has kubernetes.io/metadata.name set (it's auto-set in K8s 1.21+, but custom labels used in selectors must be validated to exist before the policy is applied).