Post

Kubernetes network policies

Kubernetes NetworkPolicy examples covering default deny, frontend-to-backend rules, cross-namespace access to Cassandra, and DNS egress policies.

Kubernetes network policies

Here is an example of network policies

Image

First, remove the master taint so pods can be scheduled on the control plane node. Then create two pods (frontend and backend) and expose them as services.

1
2
3
4
5
6
7
k taint node scw-k8s-cks node-role.kubernetes.io/master-
k run frontend --image=nginx
k run backend --image=nginx
k get pods
k expose  pod frontend --port 80
k expose  pod backend --port 80
k get svc

If a pod has multiple network policies applied, all of them will be merged.

Default deny network policy

This policy blocks all ingress and egress traffic for every pod in the default namespace. After applying it, pods can no longer communicate with each other.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@scw-k8s-cks:~# cat default-deny.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-policy
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

# connection does not work anymore
root@scw-k8s-cks:~# k exec frontend -- curl backend
root@scw-k8s-cks:~# k exec backend -- curl frontend

Setting up following network policies

Image

There are two pods currently running in your cluster.

1
2
3
4
root@scw-k8s-cks:~# k get pods
NAME       READY   STATUS    RESTARTS   AGE
backend    1/1     Running   0          4h27m
frontend   1/1     Running   0          4h30m

Frontend part

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root@scw-k8s-cks:~# cat allow-frontend-to-talk-to-backend.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: frontend-talk-to-backend
  namespace: default
spec:
  podSelector:
    matchLabels:
      run: frontend
  policyTypes:
  - Egress
  egress:
  - to:
    - podSelector:
        matchLabels:
          run: backend
    ports:
    - protocol: TCP
      port: 80

Backend part

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root@scw-k8s-cks:~# cat backend-to-frontend.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-to-frontend
  namespace: default
spec:
  podSelector:
    matchLabels:
      run: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          run: frontend
    ports:
    - protocol: TCP
      port: 80

Now check whether we can connect from frontend -> backend

Verify that the frontend pod can reach the backend by curling its IP address directly. The connection should succeed because we created matching egress and ingress policies.

1
2
3
4
5
6
7
8
9
10
11
root@scw-k8s-cks:~# k get pods -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP          NODE          NOMINATED NODE   READINESS GATES
backend    1/1     Running   0          23h   10.32.0.5   scw-k8s-cks   <none>           <none>
frontend   1/1     Running   0          23h   10.32.0.4   scw-k8s-cks   <none>           <none>
root@scw-k8s-cks:~# k exec -it frontend -- curl 10.32.0.5
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>

Please notice that a direction from backend -> frontend does not work (as it is supposed to be)

Since we only created an egress policy from frontend to backend and an ingress policy allowing frontend into backend, the reverse direction (backend to frontend) is blocked and will time out.

1
2
3
root@scw-k8s-cks:~# k exec -it backend -- curl 10.32.0.4
...
# times out ...

One more extended use case with connection to cassandra database

Image

Create a dedicated namespace for Cassandra, label it appropriately, expose the pod as a service, and verify the pod IP address for later connectivity tests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
root@scw-k8s-cks:~# k create namespace namespace-cassandra
namespace/namespace-cassandra created

# create a label on pod <- this is not needed but it's good to see how to add label and remove label from a POD
root@scw-k8s-cks:~# k label pod --namespace namespace-cassandra cassandra ns=cassandra
pod/cassandra labeled
root@scw-k8s-cks:~# k label pod --namespace namespace-cassandra cassandra ns-


# create a label on a namespace
root@scw-k8s-cks:~# k label namespace namespace-cassandra ns=cassandra
namespace/namespace-cassandra labeled

# check labels
root@scw-k8s-cks:~# k get ns namespace-cassandra --show-labels
NAME                  STATUS   AGE   LABELS
namespace-cassandra   Active   12m   kubernetes.io/metadata.name=namespace-cassandra,ns=cassandra

root@scw-k8s-cks:~# k expose pod --namespace namespace-cassandra cassandra --port 80
service/cassandra exposed

# Check cassandra IPv4 address
root@scw-k8s-cks:~# k get pods -o wide -n namespace-cassandra
NAME        READY   STATUS    RESTARTS   AGE   IP          NODE          NOMINATED NODE   READINESS GATES
cassandra   1/1     Running   0          30m   10.32.0.6   scw-k8s-cks   <none>           <none>

Create one more policy

This egress policy allows the backend pod in the default namespace to send traffic to any pod in the namespace labeled ns: cassandra on TCP port 80.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
root@scw-k8s-cks:~# cat backend-to-cassandra.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-to-cassandra
  namespace: default
spec:
  podSelector:
    matchLabels:
      run: backend
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          ns: cassandra
    ports:
    - protocol: TCP
      port: 80


k create -f backend-to-cassandra.yaml
Check connection from backend to Cassandra

Test that the backend pod can now reach the Cassandra pod in the other namespace over HTTP.

1
2
3
4
5
6
7
8
root@scw-k8s-cks:~# k exec backend -- curl http://10.32.0.6
<!DOCTYPE html>
<html>
<head>
...
</body>
</html>
root@scw-k8s-cks:~#
Make it more complex - create default deny in namespace-cassandra to block all traffic

Apply a default deny policy in the cassandra namespace to block all ingress and egress. This will break the previously working connection from backend to cassandra until a new ingress rule is added.

1
2
3
4
5
6
7
8
9
10
11
12
13
root@scw-k8s-cks:~# cat default-deny-cassandra.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: cassandra-deny-policy
  namespace: namespace-cassandra
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

k create -f default-deny-cassandra.yaml

Now, the connection between backend and cassandra does not work anymore.

1
2
3
4
5
root@scw-k8s-cks:~# k exec backend -- curl http://10.32.0.6

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0^C

Label the default namespace and create an ingress policy in the cassandra namespace to allow traffic from the default namespace. This restores connectivity from backend to cassandra.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Create a new label on default namespace
root@scw-k8s-cks:~# k label namespaces default ns=default
namespace/default labeled


# Create a new network policy for Ingress at pod cassandra
root@scw-k8s-cks:~# cat from-backend-to-cassandra.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-to-cassandra
  namespace: namespace-cassandra
spec:
  podSelector:
    matchLabels:
      run: cassandra
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          ns: default

k create -f from-backend-to-cassandra.yaml
Check the connection now from backend to cassandra

Confirm that the backend pod can once again reach the cassandra pod now that the ingress rule is in place.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@scw-k8s-cks:~# k exec backend -- curl http://10.32.0.6
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   615  100   615    0     0   200k      0 --:--:-- --:--:-- --:--:--  200k
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
Allow DNS 53 from default namespace

This policy denies all egress and ingress by default but explicitly allows DNS traffic (TCP and UDP port 53) so that pod DNS resolution continues to work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat <<EOF > allow-dns-np.yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny
  namespace: default
spec:
  podSelector: {}
  policyTypes:
  - Egress
  - Ingress
  egress:
  - ports:
    - port: 53
      protocol: TCP
    - port: 53
      protocol: UDP
EOF
There are existing Pods in Namespace app.

We need a new default-deny NetworkPolicy named deny-out for all outgoing traffic of these Pods. It should still allow DNS traffic on port 53 TCP and UDP.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cat np.yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-out
  namespace: app
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    ports:
    - protocol: TCP
      port: 53
    - protocol: UDP
      port: 53
This post is licensed under CC BY 4.0 by the author.