TT Lab
Get started
Learn Learning paths Courses

Kubernetes Operations

Policy Design Starting From Default Deny

Continue in TT Lab

Goal

You flip a namespace's communication default to deny, and write a set of policies that selects only the necessary paths by label and opens them.

Why it matters

What people get wrong most often in NetworkPolicy is not the syntax but the direction and the way selectors combine. A policy attaches not to the sending side but to the receiving side. To allow communication from web to api, the policy's podSelector must be api, and web appears inside ingress.from. Also, items of the from array are ORed, while the namespaceSelector and podSelector within one item are ANDed. With a single hyphen's position, "a specific Pod in a specific namespace" turns into "all Pods of that namespace, or that Pod anywhere." And when you block egress, be sure to add the DNS exception as a pair. Cases are reported again and again in which, in real production, only a default deny was put in first, name resolution died, and all services stopped in a chain. The cluster in this lab has no real packets flowing between Pods, so grading is based on the contents of the policy objects and the selector semantics. You set the syntax and the mindset here, and you can do the real blocking check in an environment with a CNI.

Steps

  1. Create the namespace netpol-lab and apply /opt/lab/fixtures/k8s/workloads.yaml in it. As a result, the three Pods web, api, and db must each have an app label equal to its own name, and the api Pod must additionally have the label tier=backend.
  2. In netpol-lab, create the NetworkPolicy default-deny-ingress. podSelector is the empty object {}, policyTypes is only Ingress, and there must be no ingress rules at all.
  3. Create the NetworkPolicy allow-web-to-api. podSelector.matchLabels.app is api, and ingress[0].from[0] has only podSelector.matchLabels.app: web (do not put a namespaceSelector in the same item). The allowed port is TCP 8080.
  4. Create the NetworkPolicy default-deny-egress. policyTypes is only Egress. Then create allow-dns-egress, with egress[0].to[0].namespaceSelector.matchLabels set to kubernetes.io/metadata.name: kube-system, and in egress[0].ports, put port 53 as two items, UDP and TCP.
  5. Create the namespace monitoring and attach the label purpose=monitoring. Then, in netpol-lab, create the NetworkPolicy allow-from-monitoring. podSelector.matchLabels.app is api, ingress[0].from[0].namespaceSelector.matchLabels.purpose is monitoring, and the allowed port is 9090.
  6. Create the NetworkPolicy allow-office-cidr. podSelector.matchLabels.app is web, ingress[0].from[0].ipBlock.cidr is 10.0.0.0/16, and except is the single entry 10.0.5.0/24.
  7. Create the NetworkPolicy api-full. podSelector.matchLabels.app is api, and policyTypes is both Ingress and Egress. ingress[0].from must contain 2 or more items (for example, the web Pod and the monitoring namespace), and egress[0].to[0] is podSelector.matchLabels.app: db, with allowed port 5432.
  8. Create /root/ops/netpol/out/policy-map.json. The top-level keys are web, api, and db, and each value is an array of the names of the policies that select that Pod. A policy with an empty selector must go in all three Pods, and allow-web-to-api must go only in api (it must not go in web or db). allow-office-cidr goes in web, and api-full goes in api. In the end, netpol-lab must have 6 or more NetworkPolicies.

Reference

Create the lab namespace and labeled Pods

Create the namespace netpol-lab and apply /opt/lab/fixtures/k8s/workloads.yaml in it. As a result, the three Pods web, api, and db must each have an app label equal to its own name, and the api Pod must additionally have the label tier=backend.

A policy selects targets only by labels. The fixture already has the three Pods ready, so bring them up as they are, but check which namespace you bring them up in.

Create the default ingress deny policy

In netpol-lab, create the NetworkPolicy default-deny-ingress. podSelector is the empty object {}, policyTypes is only Ingress, and there must be no ingress rules at all.

A selector that selects the whole namespace is an empty selector. Denying everything means writing no allow rules at all.

Open only the port from web to api

Create the NetworkPolicy allow-web-to-api. podSelector.matchLabels.app is api, and ingress[0].from[0] has only podSelector.matchLabels.app: web (do not put a namespaceSelector in the same item). The allowed port is TCP 8080.

A policy attaches not to the sending side but to the receiving side. Since it is within the same namespace, do not mix a namespace selector in when choosing the other party.

Default egress deny and a DNS exception

Create the NetworkPolicy default-deny-egress. policyTypes is only Egress. Then create allow-dns-egress, with egress[0].to[0].namespaceSelector.matchLabels set to kubernetes.io/metadata.name: kube-system, and in egress[0].ports, put port 53 as two items, UDP and TCP.

If you block egress, name resolution dies first. Check that DNS does not end with UDP alone, and what the standard label is for selecting the target namespace.

Allow traffic coming from another namespace

Create the namespace monitoring and attach the label purpose=monitoring. Then, in netpol-lab, create the NetworkPolicy allow-from-monitoring. podSelector.matchLabels.app is api, ingress[0].from[0].namespaceSelector.matchLabels.purpose is monitoring, and the allowed port is 9090.

To select a namespace, the label must actually be attached to that namespace. If there is nothing for the selector to select, the policy means nothing.

Allow an IP range and specify an exception

Create the NetworkPolicy allow-office-cidr. podSelector.matchLabels.app is web, ingress[0].from[0].ipBlock.cidr is 10.0.0.0/16, and except is the single entry 10.0.5.0/24.

You use ipBlock by opening broadly and carving narrowly out. The excluded range must be contained within the allowed range.

Put ingress and egress in one policy

Create the NetworkPolicy api-full. podSelector.matchLabels.app is api, and policyTypes is both Ingress and Egress. ingress[0].from must contain 2 or more items (for example, the web Pod and the monitoring namespace), and egress[0].to[0] is podSelector.matchLabels.app: db, with allowed port 5432.

Items of the from array are ORed. To allow two kinds of sources, split them into two items. On the outgoing side, only the database port is open.

Build a map of the policies that apply to each Pod

Create /root/ops/netpol/out/policy-map.json. The top-level keys are web, api, and db, and each value is an array of the names of the policies that select that Pod. A policy with an empty selector must go in all three Pods, and allow-web-to-api must go only in api (it must not go in web or db). allow-office-cidr goes in web, and api-full goes in api. In the end, netpol-lab must have 6 or more NetworkPolicies.

A policy with an empty selector applies to all Pods. Reflect in the map that what a policy selects is the receiving side.