Frontend Labs
⋯

Chapter 19 — Kubernetes network security

In one sentence

By default every pod can talk to every other pod, the API server, the kubelet, and the cloud metadata endpoint — so Kubernetes network security is about imposing default-deny with NetworkPolicy (enforced by a capable CNI), plus identity on every call via the mesh.

Where we are

G1 Ch 39 and Part 2 Ch 8 covered network segmentation and cloud VPCs. Inside a cluster it's a different model: policy objects selected by labels, enforced by the CNI, and it starts wide open.

How it works

NetworkPolicy basics:

   - Namespaced. Selects pods by label. Applies to Ingress, Egress, or both.
   - DEFAULT: all traffic allowed. The moment ANY NetworkPolicy selects a
     pod for a direction, that pod becomes DEFAULT-DENY for that direction
     -- only explicitly-listed traffic is allowed.
   - Peers: podSelector (same ns), namespaceSelector, ipBlock (+ except),
     and ports.
   - ONLY enforced if the CNI supports it: Calico, Cilium, Antrea, Weave,
     Kube-router. flannel does NOT -> policies are silently inert.

   The must-have baseline, per namespace:
     # deny all ingress and egress
     apiVersion: networking.k8s.io/v1
     kind: NetworkPolicy
     metadata: { name: default-deny, namespace: core }
     spec:
       podSelector: {}
       policyTypes: ["Ingress", "Egress"]
   Then explicit allows:
     - allow DNS egress to kube-dns (UDP/TCP 53) -- forgetting this breaks
       everything.
     - allow ingress from the specific caller pods/namespaces on the
       service port.
     - allow egress to the specific downstream services.
     - allow egress to the API server only for pods that call it.
     - DENY egress to 169.254.169.254/32 (metadata) and to the node CIDR /
       kubelet port unless explicitly needed.

What plain NetworkPolicy can't do (use Cilium/Calico enterprise CRDs):

   - L7 rules (HTTP method/path, gRPC service/method, Kafka topic).
   - FQDN-based egress ("allow egress to api.stripe.com") -- plain policy
     is IP-based, and cloud service IPs are dynamic.
   - DNS-aware policy, cluster-wide policies, default policies, node
     egress, identity-based (not just label-based) rules.
   CiliumNetworkPolicy / CiliumClusterwideNetworkPolicy and Calico
   GlobalNetworkPolicy give you these. FQDN egress is how you actually
   allowlist third-party APIs.

The service mesh layer (identity on every call):

   NetworkPolicy says "pod A CAN reach pod B on port 8080". It does NOT
   verify WHO is calling or authenticate the connection. A mesh (Istio,
   Linkerd, Cilium Service Mesh, Consul) adds:
     - automatic mTLS between workloads, with SPIFFE identities (Part 5) ->
       "reachable but refused" for anyone without the right identity.
     - AuthorizationPolicy: "only workloads with identity
       spiffe://.../core/web may call orders-api, method POST /orders".
     - telemetry: every call, with source and destination identity ->
       detection (Ch 54).
   Use BOTH: NetworkPolicy for coarse reachability (defence in depth,
   works even if the mesh sidecar is bypassed), mesh AuthorizationPolicy
   for identity-based L7 authz.

Other cluster-network exposure:

   [ ] Metadata endpoint: pods can hit 169.254.169.254 by default -> IMDS
       creds (Part 2). Block via NetworkPolicy egress deny + IMDSv2
       hop-limit-1 on nodes; on EKS use the "disable pod access to IMDS"
       setting or a network policy; GKE Workload Identity blocks the
       legacy metadata paths.
   [ ] The kubelet (:10250) and etcd (:2379-2380) should be unreachable
       from workload pods.
   [ ] Ingress controllers: TLS config (Part G1-7), rate limiting, WAF,
       and they often run privileged / hostNetwork -> treat as a sensitive
       workload.
   [ ] LoadBalancer/NodePort Services expose things outside the cluster --
       gate their creation by policy (Ch 18).
   [ ] DNS: CoreDNS is a high-value target (poison it -> redirect every
       service lookup). Lock down its RBAC and config; consider DNS
       policies.
   [ ] East-west by default: kube-proxy makes every Service reachable
       cluster-wide -- NetworkPolicy is the only thing narrowing it.

Worked example

Blocking pod-to-metadata and proving lateral containment.

   BEFORE: `edge:web` pod is popped (RCE). Attacker:
     curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
       -> the NODE's instance role creds (if IMDSv1 or hop-limit>1).
     curl http://orders-api.core:8080/internal/orders  -> reachable
       (no NetworkPolicy), and if orders-api trusts network position, data.

   APPLY:
     1. `default-deny` (ingress+egress) in `edge` and `core`.
     2. `edge`: allow egress to kube-dns:53; allow egress to
        `core/orders-api:8080`; allow ingress from the ingress controller.
        Explicitly deny egress to 169.254.169.254/32 (an ipBlock `except`
        or a Cilium policy).
     3. `core/orders-api`: allow ingress ONLY from `edge/web` and
        `edge/bff` on 8080; allow egress to the DB namespace + DNS.
     4. Nodes: IMDSv2 required, hop-limit 1 -> even without the policy,
        a pod (not the node) can't complete the token PUT.
     5. Mesh AuthorizationPolicy: `orders-api` accepts calls only from
        SPIFFE identity `.../edge/bff` (not `web`), method-scoped.

   AFTER: the popped `web` pod can reach DNS, `orders-api:8080`, and
   nothing else. It cannot get node creds. Its calls to `orders-api` are
   rejected unless they carry `bff`'s mTLS identity AND a valid end-user
   context (Part 5). Blast radius: DNS lookups and refused connections.
   Falco alerts on the outbound-connection-from-web and the shell.

Practice (60 min) — kind cluster with Cilium or Calico

  1. Install Cilium (or Calico) on kind so NetworkPolicy is actually enforced. Confirm: deploy two pods, curl between them (works), apply a default-deny, curl again (fails).
  2. Build the SecureShop namespace policies: default-deny everywhere, then explicit DNS, service-to-service, and ingress allows. Verify each path with kubectl exec ... curl.
  3. Block 169.254.169.254 egress. From a pod, confirm the metadata endpoint is now unreachable.
  4. Write a CiliumNetworkPolicy with FQDN egress allowing only api.stripe.com from the payments pod; confirm curl example.com fails and curl api.stripe.com (or a stand-in) works.
  5. Install Linkerd or Istio ambient; enable mTLS; write an AuthorizationPolicy so orders-api only accepts bff's identity. Call it from web (rejected) and bff (allowed).
  6. Run kube-hunter (or manual probes) before/after and note what's no longer reachable.

Across the series

  • The packet path through Services, kube-proxy NAT, CNI overlays, and NetworkPolicy (including the DNS egress rule that every default-deny policy forgets): TCP/IP guide, Chapter 58. Host-level packet path and conntrack: Chapter 52.

Common mistakes

  • Assuming NetworkPolicy is active. With flannel (or a misconfigured CNI) it's inert. Test it.
  • No default-deny. A namespace with only "allow" policies still permits everything not otherwise selected in unselected pods.
  • Forgetting DNS egress. default-deny egress without a kube-dns allow breaks every workload — then someone deletes the policy.
  • IP-based egress for third-party APIs. Their IPs rotate; you need FQDN policies (Cilium/Calico).
  • Relying on NetworkPolicy for authz. It's reachability, not identity. Add the mesh for "who is calling".
  • Ignoring the metadata endpoint. The single highest-value pod-reachable target. Block it and use IMDSv2 hop-limit-1.
  • Privileged ingress controllers left unhardened. They're internet-facing and often hostNetwork.

Check yourself

  1. What is a pod's default network posture, and what flips a pod to default-deny?
  2. What must the CNI do for NetworkPolicy to work, and name one that doesn't.
  3. What can plain NetworkPolicy not express that CiliumNetworkPolicy can?
  4. NetworkPolicy vs mesh AuthorizationPolicy — what does each provide, and why use both?
  5. Why is 169.254.169.254 egress the priority block, and what node setting backs it up?
  6. What's the one allow rule people forget in a default-deny egress namespace?

Further reading

  • Docs: Kubernetes "Network Policies"; Cilium and Calico docs (NetworkPolicy
    • their CRDs + FQDN policies); "Network Policy Editor" (networkpolicy.io).
  • Recipes: ahmetb/kubernetes-network-policy-recipes.
  • Mesh: Istio "Security" (mTLS, AuthorizationPolicy, PeerAuthentication); Linkerd "Automatic mTLS" and "Policy"; Cilium Service Mesh.
  • Hardening: NSA/CISA guidance (network section); "Kubernetes network security" chapters of Kubernetes Security and Observability.

Generated from real-life-security-guide-v1.md · 8 Oct 2026 16:36