Working with K8S Network Policy

Working with K8S Network Policy
Photo by Kyle Glenn / Unsplash

When to use network policy?
👉 Control traffic flow at the IP and port level (OSI layer 3 or 4).

The quick way to get started with network policy is to use minikube with Calico plugin. See my previous post to configure minikube.

minikube start \
--network-plugin=cni \
--cni=calico
💡
CNI is required

Network policies need a network plugin which supports Network Policy. Without a properly configured network plugin, the network policies applied to the cluster have no effects (not affect any traffics).

Verify calico plugin running

kubectl -n kube-system get po -l=k8s-app=calico-node
kubectl -n kube-system get po -l=k8s-app=calico-kube-controllers

The output should look like this

NAME                READY   STATUS    RESTARTS   AGE
calico-node-lk7sz   1/1     Running   0          3m7s

NAME                                       READY   STATUS    RESTARTS   AGE
calico-kube-controllers-85578c44bf-lgzf5   1/1     Running   0          3m23s

Network Policy 101

Network Policy can control:
- Pods that are allowed to communicate with other pods
- Namespaces that are allowed (all pods in that namespace are allowed)
- IP range are allowed

Network Policy uses selector and CIDR range to evaluate policy rules.
- For pods and namespaces: selector is used for identifying the resources which the network policy will apply to.
- For IP range restrictions, CIDR range will be used.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
  ingress: {}
  egress: {}

A sample network policy

Network Policy key notes

  • Namespace scope: Network Policies are scoped to the namespace, which means it will affect the traffic of the pods in the namespace at which the policy is applied.
  • Allow All: By default the pod allows all ingress and egress. It means it has no restrictions for both inbound and outbound traffic.
  • No deny rules: Network policy only allows to define specific traffic to be allowed (the rest is denied).
  • Empty selector: An empty selector means everything. For example, if podSelector: {} means all pods in the namespace in which the policy applied to.
  • 'OR' rule: If multiple policies are applied to a single pod, all the policies are OR’ed.

Network Policy resource definition explained

podSelector

Determines pods which the policy will be applied to. An empty podSelector: {} means the policy will be applied to all pod in the namespace the network policy created (remember network policy is namespace scoped).

To select specific pods in the namespace use .podSelector.matchLabels

...
podSelector:
  matchLabels:
    # selects pods in the namespace with label "app=wordpress"
    app: wordpress
...

policyTypes

Indicates the traffic flow direction. It can be Ingress,Egress, or both. If no policyTypes are defined in a NetworkPolicy then by default Ingress will always be set and Egress will be set if the NetworkPolicy has any egress rules.

Rules: ingress, egress

Each network policy has a rules section named ingress and egress based on the policy type mentioned in policyTypes.

  • ingress: contains ingress-allowed rules. It controls which pods/namespaces/CIDR are allowed (.ingress.from) and which ports are allowed (.ingress.ports)
  • egress: contains egress-allowed rules. It controls to which pods/namespaces/CIDR traffic are allowed at which ports.

Network Policy In Use

Suppose we have a redis db in namespace shared-db, we would like to share redis db with some micro-services in namespace service.

service labels
ui app=demo role=ui
api app=demo role=api

Let's get started

kubectl create namespace shared-db
kubectl create namespace service

Run redis db in shared-db namespace

kubectl -n shared-db run db \
--image=redis:4 \
--labels="app=demo,role=db" \
--expose --port=6379 

Verify redis db running

kubectl -n shared-db get po,svc

The output should look like this

NAME     READY   STATUS    RESTARTS   AGE
pod/db   1/1     Running   0          25s

NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/db   ClusterIP   10.106.74.138   <none>        6379/TCP   25s

Now let's define a network policy

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: redis-allow-services
  namespace: shared-db
spec:
  # select pods in the namespace with match labels
  podSelector:
    matchLabels:
      app: demo
      role: db
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: service
      podSelector:
        matchLabels:
          app: demo
          role: ui
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: service
      podSelector:
        matchLabels:
          app: demo
          role: api

Create network policy in namespace shared-db.

kubectl apply -f redis-allow-services.yaml

Try it out

Pod with matched labels can connect to redis db in shared-db namespace

kubectl -n service run test-$RANDOM \
--labels="app=demo,role=ui" \
--rm -it \
--image=alpine -- sh

The above command let us access to container shell where we can use netcat (nc) command to verify connection to redis db

/ # nc -v -w2 db.shared-db 6379
db.shared-db (10.106.74.138:6379) open

Pods with labels not matched the network policy cannot connect!

kubectl -n service run test-$RANDOM \
--labels="app=other,role=ui" \
--rm -it \
--image=alpine -- sh
/ # nc -v -w2 db.shared-db 6379

nc: db.shared-db (10.106.74.138:6379): Operation timed out

Pods with matched labels but in wrong namespace cannot connect!

kubectl -n default run test-$RANDOM \
--labels="app=demo,role=ui" \
--rm -it \
--image=alpine -- sh
/ # nc -v -w 2 db.shared-db 6379
nc: db.shared-db (10.106.74.138:6379): Operation timed out

Resources