# Testing Namespace Isolation using NetworkPolicy In my post [*Isolating namespaces with NetworkPolicy*](/k8s/isolating-namespaces-with-networkpolicy/), I presented a pattern for isolating namespaces from each other in a cluster using NetworkPolicy. Let’s put those concepts in to practice. ## Trying it out ### Cluster requirements This example is designed to run on GKE, using **DPv2** and **CloudDNS**. This is the default for Autopilot, so the following is enough to get you a working environment: ```shell gcloud container clusters create-auto autopilot-cluster-3 \ --region us-west1 ``` For the node-based version of GKE, enable both options. On the command line, add `--enable-dataplane-v2 --cluster-dns=clouddns` To run in other environments like GKE with Calico, you will need to tweak the ingress `ipBlock` rules as mentioned above to block the Pod IP ranges. If you’re using kubedns on either environment, you’ll need to allow DNS traffic to pods in `kube-system` as well. The same is true for any other connections to DaemonSet agents. ### Setup To setup, we’ll create 2 namespaces each with a public and ClusterIP service. Only one will have the isolation network policies applied Let’s test it out! First, create a namespace with a simple service ```shell # create namespace kubectl create namespace non-isolated kubectl config set-context --current --namespace=non-isolated # create 2 services kubectl create -f https://raw.githubusercontent.com/WilliamDenniss/kubernetes-for-developers/master/Chapter07/7.2_Ingress/timeserver-deploy-dns.yaml kubectl create -f https://raw.githubusercontent.com/WilliamDenniss/kubernetes-for-developers/master/Chapter07/7.1_InternalServices/timeserver-service.yaml kubectl create -f https://raw.githubusercontent.com/WilliamDenniss/kubernetes-for-developers/master/Chapter07/7.1_InternalServices/robohash-deploy.yaml kubectl create -f https://raw.githubusercontent.com/WilliamDenniss/kubernetes-for-developers/master/Chapter07/7.1_InternalServices/robohash-service.yaml ``` And another namespace, this one with our isolation NetworkPolicies, and a couple of services. ```shell # create namespace kubectl create namespace isolated kubectl config set-context --current --namespace=isolated # apply network policy kubectl create -f https://raw.githubusercontent.com/WilliamDenniss/autopilot-examples/master/networkpolicy/isolate-namespace/isolate-namespace-egress.yaml kubectl create -f https://raw.githubusercontent.com/WilliamDenniss/autopilot-examples/master/networkpolicy/isolate-namespace/isolate-namespace-ingress.yaml # create the same 2 services kubectl create -f https://raw.githubusercontent.com/WilliamDenniss/kubernetes-for-developers/master/Chapter07/7.2_Ingress/timeserver-deploy-dns.yaml kubectl create -f https://raw.githubusercontent.com/WilliamDenniss/kubernetes-for-developers/master/Chapter07/7.1_InternalServices/timeserver-service.yaml kubectl create -f https://raw.githubusercontent.com/WilliamDenniss/kubernetes-for-developers/master/Chapter07/7.1_InternalServices/robohash-deploy.yaml kubectl create -f https://raw.githubusercontent.com/WilliamDenniss/kubernetes-for-developers/master/Chapter07/7.1_InternalServices/robohash-service.yaml ``` ### Testing connectivity Get the service list, and note the ClusterIPs ```shell $ kubectl get svc --all-namespaces NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default kubernetes ClusterIP 34.118.224.1 443/TCP 85m gke-gmp-system alertmanager ClusterIP None 9093/TCP 13d gke-gmp-system gmp-operator ClusterIP 34.118.238.255 8443/TCP,443/TCP 13d isolated robohash-internal ClusterIP 34.118.232.141 80/TCP 12s isolated timeserver LoadBalancer 34.118.227.94 80:30902/TCP 13s kube-system antrea ClusterIP 34.118.238.71 443/TCP 13d kube-system default-http-backend NodePort 34.118.233.221 80:30416/TCP 13d kube-system kube-dns ClusterIP 34.118.224.10 53/UDP,53/TCP 13d kube-system metrics-server ClusterIP 34.118.232.23 443/TCP 13d non-isolated robohash-internal ClusterIP 34.118.239.213 80/TCP 2m11s non-isolated timeserver LoadBalancer 34.118.234.103 34.16.70.221 80:31834/TCP 3m ``` Now try the Pod in the isolated namespace. It should be able to access the other service in the namespace, and the internet, but not the Pod in the other namespaces. To test this, I conducted the following: - Attempt to curl google.com (succeded, PASS) - Attempt to connect to service in another namespace (denied, PASS) - Attempt to connect to service in own namespace (allowed, PASS) This is demonstrated by curl-ing google.com, and then trying to hit the cluster local namespace. Note that it can still access that local service via it’s public IP. ```shell $ kubectl exec -it deploy/timeserver -n isolated -- bash # curl "http://google.com" . 301 Moved

301 Moved

The document has moved here. # apt-get update && apt-get install host --yes # host timeserver.non-isolated.svc timeserver.non-isolated.svc.cluster.local has address 34.118.234.103 # curl "http://timeserver.non-isolated.svc" curl: (28) Failed to connect to timeserver.non-isolated.svc port 80 after 131135 ms: Couldn't connect to server # curl "http://34.118.234.103" curl: (28) Failed to connect to 34.118.234.103 port 80 after 131854 ms: Couldn't connect to server # curl http://34.16.70.221 curl: (28) Failed to connect to 34.16.70.221 port 80 after 128886 ms: Couldn't connect to server # curl "http://robohash-internal/robots.txt" User-agent: * Allow: / ``` Connecting to the external service in this namespace from your local machine should also work ```shell $ kubectl get svc timeserver -n isolated NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE timeserver LoadBalancer 34.118.227.94 34.135.225.117 80:30902/TCP 23m $ curl http://34.135.225.117 The time is 9:13 PM, UTC.% ``` Now, let’s try the same thing from the non-isolated namespace. Even though this namespace has no NetworkPolicy applied, it shouldn’t be able to connect to services in the isolated namespace either. ```shell $ kubectl exec -it deploy/timeserver -n non-isolated -- bash # apt-get update && apt-get install host --yes timeserver.isolated.svc.cluster.local has address 34.118.227.94 # host timeserver.isolated.svc # curl "http://timeserver.isolated.svc" curl: (28) Failed to connect to timeserver.isolated.svc port 80 after 129175 ms: Couldn't connect to server # curl "http://34.118.227.94" ``` Even accessing the \*public\* IP of the service is blocked, which is somewhat excessive. However, if you need to expose the service to internal usage, you can simply add a rule to allow it. ### Allowing traffic to a particular service in the isolated namespace If you want to allow traffic to a particular service in this isolated namespace, you can by adding a new rule for that, like the following ```yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-timeserver-ingress spec: # apply to Pods with the following label podSelector: matchLabels: pod: timeserver-pod policyTypes: - Ingress ingress: - from: # Allow all cluster traffic - namespaceSelector: {} ``` [allow-timeserver-ingress.yaml](https://github.com/WilliamDenniss/autopilot-examples/blob/master/networkpolicy/allow-timeserver-ingress.yaml) ```shell $ kubectl create -f https://raw.githubusercontent.com/WilliamDenniss/autopilot-examples/master/networkpolicy/allow-timeserver-ingress.yaml networkpolicy.networking.k8s.io/allow-timeserver-ingress created $ kubectl exec -it deploy/timeserver -n non-isolated -- bash # curl "http://timeserver.isolated" The time is 9:35 PM, UTC # curl "http://robohash-internal.isolated" curl: (28) Failed to connect to robohash-internal.isolated port 80 after 130659 ms: Couldn't connect to server ``` ### Allowing traffic from the isolated namespace now to allow pods in the isolated namespace to contact another internal service ```yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-timeserver-egress spec: # Apply to all pods in this namespace podSelector: {} policyTypes: - Egress egress: - to: # allow traffic to pod: timeserver-pod in ns non-isolated - namespaceSelector: matchLabels: kubernetes.io/metadata.name: non-isolated podSelector: matchLabels: pod: timeserver-pod ``` [allow-timeserver-egress.yaml](https://github.com/WilliamDenniss/autopilot-examples/blob/master/networkpolicy/allow-timeserver-egress.yaml) Note that to select the name of the namespace you use `kubernetes.io/metadata.name`, not `name`. You can find this with `kubectl describe namespace`. To try this out, create the policy then exec into the container in the isolated namespace, and see if you can access the service we just allowed. It should work, while trying to access another service we didn’t explicitly allow will be blocked, as shown here: ```shell $ kubectl create -f https://raw.githubusercontent.com/WilliamDenniss/autopilot-examples/master/networkpolicy/allow-timeserver-egress.yaml $ kubectl exec -it deploy/timeserver -n isolated -- bash root@timeserver-8669c964f8-x7ztn:/app# curl "http://timeserver.non-isolated.svc" The time is 9:34 PM, UTC.root@timeserver-8669c964f8-x7ztn:/app# # curl "http://robohash-internal.non-isolated.svc" ``` Hopefully now you have an understanding of how to create broad traffic rules, while also adding specific rules to permit allowed traffic. ### Cleanup To delete all the resources created for this demo, run the following ```shell kubectl delete namespace non-isolated kubectl delete namespace isolated ``` ### Discuss And there we have it. By allowing just traffic to and from Pods in the same namespace, and traffic to and from the internet (but not internal IPs) we have fully isolated this namespace from the other namespaces in the cluster, as if it was in a cluster of it’s own. If you want to share a comment or question, feel free to post on X tagging @WilliamDenniss.