Module 4: Networking Ingress And Networkpolicy
6. `NetworkPolicy`: by default, everyone talks to everyone — and why that doesn't last
Description
This module's lesson 2 demonstrated, with real evidence, that the CNI guarantees any Pod can talk to any other Pod, with no NAT, regardless of which node each one lives on. That lesson ended with a warning this one picks up fully: "the CNI guarantees technical capability for communication — it says nothing about whether that communication should exist." Today, with no restriction declared, any Pod in andes-cargo-cluster — including one you (or someone else with cluster access) created tomorrow, with no relation to Andes Cargo — can curl status-api-service directly, completely bypassing the Ingress lessons 4-5 built. NetworkPolicy is the Kubernetes object that closes that open door.
Connection to the module
This lesson is conceptual — the foundation lesson 7 builds on, building and verifying a real NetworkPolicy. It's also the lesson that literally fulfills a promise another guide in this ecosystem left in writing.
The literal delegation: what cloud-security-and-guardrails-guide deliberately left out
This guide didn't decide to build NetworkPolicy on its own initiative — it's, in part, a scope boundary another already-designed sibling guide explicitly declared in its own DISEÑO.md. The complete quote, already previewed in this module's lesson 1, deserves its full development here:
"EKS and container runtime (in-cluster admission controllers — OPA Gatekeeper, Kyverno —, Docker image scanning,
NetworkPolicy) →kubernetes-and-eks-in-production-guide. Andes Cargo has no containers in this ecosystem... this guide's scanning and signing is about deployment artifacts and IaC, not container images."
cloud-security-and-guardrails-guide builds infrastructure supply-chain security: SBOM, artifact signing with cosign, Terraform HCL scanning, policies on what a terraform plan is going to create before it exists. None of those tools operate inside a running Kubernetes cluster — all of them evaluate code or artifacts before deployment. NetworkPolicy, by contrast, is a rule that lives inside the cluster, evaluated in real time, every time a network packet tries to cross from one Pod to another — a completely different security layer, one that only makes sense once the cluster already exists and has real traffic running. That's precisely the technical reason for the boundary: it's not that NetworkPolicy was less important for that guide, it's that it belongs to a different discipline (a cluster's runtime security) from what that guide covers (infrastructure supply-chain security).
The default model: with no NetworkPolicy, everything's allowed
Before building the solution, confirm the problem precisely. A Kubernetes namespace, with no NetworkPolicy declared, has no traffic restriction between Pods whatsoever — not even across different namespaces. Any Pod, running in the default namespace, with no relation to Andes Cargo, can curl status-api-service.andes-cargo.svc.cluster.local directly (the same DNS name you already know from Module 3), exactly the same way a Pod inside the andes-cargo namespace itself would.
THE DEFAULT MODEL: EVERYONE TALKS TO EVERYONE
namespace: default namespace: andes-cargo
┌───────────────────┐ ┌──────────────────────────┐
│ any-pod │────────►│ andes-cargo-status-api │
│ (no relation to │ direct │ (real Pod, with sensitive │
│ Andes Cargo) │ curl │ business data) │
└───────────────────┘ └──────────────────────────┘
namespace: ingress-nginx namespace: andes-cargo
┌───────────────────┐ ┌──────────────────────────┐
│ ingress-nginx- │────────►│ andes-cargo-status-api │ ← legitimate
│ controller │ curl │ │ traffic,
└───────────────────┘ └──────────────────────────┘ via Ingress
With no NetworkPolicy at all: both arrows above are
technically indistinguishable to the cluster.
The second diagram is this lesson's central point: legitimate traffic (from ingress-nginx, following the path lessons 4-5 built) and traffic that should be blocked (any Pod, in any namespace, completely bypassing Ingress) are technically indistinguishable to today's cluster. Both arrive through the same mechanism — a direct TCP connection to the Service's IP — and neither meets any resistance.
This isn't a Kubernetes design oversight — it's a deliberate project decision: the default networking model prioritizes everything working with no friction from the very first moment (exactly what made Module 2 work with no network rule declared whatsoever). Network security is, on purpose, a layer you add when you need it — never a factory restriction you have to disable first.
NetworkPolicy: the object that restricts, not the one that allows
Here's where the object's name can confuse: NetworkPolicy isn't, by default, a list of permission rules — it's a selector of Pods to isolate. When you declare a NetworkPolicy that selects a set of Pods (for example, all Pods with the app: andes-cargo-status-api label) and declares policyTypes: [Ingress], the immediate effect is: those Pods stop accepting any kind of incoming traffic, except what that same policy's — or another additional NetworkPolicy's — ingress rules explicitly allow.
This has an important consequence worth internalizing before lesson 7: Kubernetes NetworkPolicy objects are additive, they never subtract permissions from each other. If two different policies select the same Pod, the result is the union of everything both allow — never the intersection. There's no "explicitly deny" rule in the standard NetworkPolicy model that overrides a permission from another policy; each policy can only add an exception to the isolation it triggered.
THE deny-by-default PATTERN, IN TWO POLICIES
Policy 1: default-deny-ingress
┌────────────────────────────────────────┐
│ podSelector: {} (every Pod │
│ in the namespace) │
│ policyTypes: [Ingress] │
│ (no "from" rule declared) │
└────────────────────────────────────────┘
│
▼ isolates ALL incoming traffic in the namespace
Policy 2: allow-from-ingress-nginx
┌────────────────────────────────────────┐
│ podSelector: app=andes-cargo-status-api │
│ policyTypes: [Ingress] │
│ from: namespace ingress-nginx │
│ ports: 8080/TCP │
└────────────────────────────────────────┘
│
▼ adds ONE exception: only ingress-nginx,
only to port 8080, only toward those Pods
Combined result: EVERYTHING blocked, EXCEPT
ingress-nginx → andes-cargo-status-api:8080
This two-policy pattern — one that isolates everything (podSelector: {}, which selects every Pod in the namespace), another that adds the exact exception you need — is, literally, the deny-by-default pattern this module's lesson 7 builds against andes-cargo-cluster for real.
Why "everyone talks to everyone" doesn't last in a production cluster
This lesson's title isn't rhetorical: Kubernetes' default model is exactly correct for a single-person learning lab — it's what let Modules 1-3 of this guide work with you never having to think about network rules — but it stops being acceptable the moment a cluster has more than one team, more than one customer, or data one of those Pods shouldn't be able to read. Three concrete reasons, each with a real case:
- Multi-tenancy inside the same cluster. A real production cluster rarely serves a single team — different namespaces often belong to different teams, or even to different platform customers. Without
NetworkPolicy, team A can directly reach any of team B's internal services, with no review whatsoever. - Reducing a compromise's blast radius. If an attacker manages to run code inside one Pod (from a dependency vulnerability, for example — the same kind of finding
trivy imageis going to detect in this guide's Module 6), the default model hands them, for free, the ability to move laterally toward any other Pod in the cluster.NetworkPolicyreduces that lateral movement to the minimum possible: only toward what an explicit rule allows. - Least privilege, applied to the network. It's the same principle you already know from IAM (
aws-core-services-guide) and from Kubernetes RBAC, applied to a different layer: no one should have more access than they need, and "network access" isn't an exception to that rule just because Kubernetes doesn't restrict it by default.
Analogy: the building where no interior door opens on its own
Picking back up lesson 1's analogy: if Ingress is the building's single front desk, NetworkPolicy is the access policy for the interior doors — and today, with no policy declared, every one of those doors is unlocked. Anyone already inside the building (any Pod, on any floor/namespace) can walk straight to Andes Cargo's office and walk in directly, bypassing the front desk, with nothing stopping them. A real office building never operates that way: every interior door has a lock that, by default, opens for no one — the equivalent of default-deny — and only people with the correct access card (an explicit NetworkPolicy rule) can enter each specific office. Lesson 7 installs exactly that lock system, with real evidence it works.
Common mistakes
Thinking NetworkPolicy is a Kubernetes security capability in itself, automatically available (this lesson's most important conceptual mistake, which lesson 7 is going to test with real evidence). What happens: someone assumes that, because the NetworkPolicy object exists in the Kubernetes API, any cluster automatically enforces it the moment you apply it. How to spot it: if you've never asked yourself who, exactly, is responsible for reading a NetworkPolicy and blocking traffic that doesn't comply. How to fix it: NetworkPolicy, same as Ingress (lesson 3), is only a declaration — the object saves perfectly into etcd regardless of whether anything enforces it. Who actually blocks or allows traffic is, again, the CNI — the same kindnet from lesson 2 — and not every CNI implements this capability. Lesson 7 investigates this in depth, against your own cluster.
Confusing "isolate" with "block everything, including responses to already-permitted requests" (configuration, relevant in lesson 7). What happens: someone assumes a NetworkPolicy with policyTypes: [Ingress] also blocks that same Pod's outgoing (egress) traffic — for example, the connection attempt andes-cargo-status-api makes toward the ConfigMap's LocalStack endpoint (which never responds, because no module in this guide installs LocalStack). How to spot it: if you expect an Ingress-type policy to affect a Pod's ability to initiate outbound connections. How to fix it: Ingress and Egress are independent policyTypes — a NetworkPolicy that only declares Ingress doesn't touch outgoing traffic at all, no matter how restrictive its incoming rules are. This module builds no Egress policy — this guide's whole scope is limited to controlling who can come in.
Assuming two policies that select the same Pod combine by subtracting permissions (conceptual, already explained above, worth repeating as a common mistake because it's counterintuitive). What happens: someone declares a restrictive NetworkPolicy and a more permissive one over the same set of Pods, expecting the more restrictive one to "win." How to spot it: if your mental model includes the idea of "priority" between NetworkPolicy policies, like would exist in a traditional firewall with ordered rules. How to fix it: there's no priority or order — the result is always the union of everything any applicable policy allows. If you need one rule to "win" over another in a more sophisticated way (for example, explicitly denying something another rule would allow), that requires a more advanced policy engine than Kubernetes' standard NetworkPolicy — Calico, named by contrast in lesson 7, offers that extended capability under the name GlobalNetworkPolicy, out of this guide's scope.
Exercises
Exercise 1 — Quote the delegation without rereading it. Without going back to the corresponding section, write from memory: (a) which guide delegated NetworkPolicy to this guide; (b) what type of security that guide builds instead; (c) why those two layers are different, not redundant.
See solution
(a) cloud-security-and-guardrails-guide. (b) Infrastructure supply-chain security: SBOM, artifact signing, Terraform HCL scanning, policies on a terraform plan. (c) That guide evaluates code/artifacts before deployment, outside any running cluster; NetworkPolicy is a rule evaluated in real time, inside an already-running cluster, every time a network packet tries to cross between Pods — a runtime security discipline, not a supply-chain one.
Exercise 2 — Explain why two policies never subtract. A colleague, used to traditional firewall rules with priority and order, asks why they can't just add a second, more restrictive NetworkPolicy to override an existing permission rule. Explain to them, in two or three sentences, why that doesn't work that way in Kubernetes.
See solution
A reasonable explanation: "Kubernetes NetworkPolicies have no order or priority between them — when more than one selects the same Pod, the result is the union of everything any of them allows, never the intersection. There's no 'explicitly deny' rule that beats a permission rule from another policy — each policy can only add exceptions to the isolation, never subtract them. If you need that kind of priority, you need a more advanced policy engine than Kubernetes' standard."
Exercise 3 — Design, without running it, the two-policy pattern for a new case. Andes Cargo, in the future, adds a second internal service (manifest-processor-api) that should only receive traffic from andes-cargo-status-api, never from ingress-nginx or any other Pod. Describe, without writing YAML, which two policies you'd need and what podSelector/from each one would have.
See solution
You'd need, first, a default-deny-ingress policy that isolates all incoming traffic in the namespace (podSelector: {}, policyTypes: [Ingress], no from rule) — this lesson's same first policy in the pattern, which would probably already cover manifest-processor-api if it lives in the same andes-cargo namespace. Second, a new policy that specifically selects manifest-processor-api's Pods (podSelector: app=manifest-processor-api) and declares a from rule pointing, not at an entire namespace like allow-from-ingress-nginx does, but at the Pods with the app: andes-cargo-status-api label within the same namespace (a podSelector nested inside from, instead of a namespaceSelector) — allowing traffic only from that specific service, and no other, not even ingress-nginx.
Summary and next step
This lesson explained the problem NetworkPolicy solves, with no command run: by default, any Pod in any namespace can talk directly to status-api-service, completely bypassing the Ingress lessons 4-5 built — legitimate and illegitimate traffic are, today, technically indistinguishable. It also picked back up cloud-security-and-guardrails-guide's literal delegation, and explained the two-policy pattern — default-deny plus an explicit exception — that makes the deny-by-default model possible, including the counterintuitive rule that policies never subtract from each other, only add.
Before moving on you should be able to: explain why Kubernetes' default networking model allows all traffic; describe the two-policy pattern (default-deny + exception) without looking at the diagram; and quote cloud-security-and-guardrails-guide's delegation from memory.
Next lesson: hands-on, real NetworkPolicy on andes-cargo. There you build exactly this lesson's pattern against your own cluster, with a real finding that corrects a widespread assumption about kind — verified with curl blocked, and then allowed, with literal evidence of both results.
Resources
- Kubernetes — Network Policies — the complete official
NetworkPolicyspecification, including the additive behavior between multiple policies this lesson explains. cloud-security-and-guardrails-guide(NIEVA),DISEÑO.md— the complete source for the literal delegation quoted in this lesson.- Kubernetes — Declare Network Policy — official step-by-step tutorial for the default-deny pattern this lesson explains in theory.
kubernetes-and-eks-in-production-guide(NIEVA), Module 4, lesson 2 — the networking model that makes this lesson's problem possible in the first place.