Initializing Enclave...

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 egress block (or a misconfigured namespaceSelector) is silently dropping all outbound traffic from your pods to another namespace.
  • How to fix it: Add an explicit egress rule with the correct namespaceSelector matching the destination namespace's labels, plus the required ports block.
  • 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 reach auth-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-dns lives in kube-system. If your egress policy doesn't explicitly allow UDP/TCP 53 to kube-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 namespaceSelector and podSelector are under the same - list item (same to entry), 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).

Related Diagnostics

"Part of the Security Utility Matrix."

View all 140 Security Tools →