Using Network Policies in EKS with Cilium

In this tutorial, we will deploy Cilium to an Amazon EKS cluster and limit traffic to Pods using Kubernetes Network Policies.

Network Policies are not available in Kubernetes out-of-the-box. To leverage Network Policies, you must use a network plugin that implements such a feature - here is where Cilium comes into play.

It’s worth noting that while this is the scope of this tutorial, Cilium is not limited to Network Policies and not even limited to Kubernetes. Check the Introduction to Cilium to learn more about it and the Kubernetes Integration doc about how Cilium works with Kubernetes.

Prerequisites

To follow this tutorial, you need an AWS Account and credentials to create and manage an EKS cluster properly provisioned and installed in your local environment. You also need the eksctl and kubectl CLIs installed.

Check the Getting started with Amazon EKS – eksctl Prerequisites section if you need help.

Create the EKS cluster

Quick note about minimal configuration

Note that in this tutorial (and all other tutorials in this blog, unless said the contrary), we will use minimal configuration to get things running quickly and focus on the feature we are evaluating. Never use this configuration to set up a production environment. Refer to the official installation guides linked in the tutorial for more thorough instructions.

Declare some variables that we’ll use throughout the tutorial.

cluster_name=le-cluster
cluster_region=us-east-1  

Create the cluster.

eksctl create cluster --name $cluster_name --region $cluster_region

Check the eksctl docs for more details about creating and managing EKS clusters.

Verify that your kube-config is currently pointing to the created cluster.

kubectl config current-context

You can also check the nodes for more details about your installation.

kubectl get nodes -o wide

Install Cilium

Download the Cilium CLI.

curl -L https://github.com/cilium/cilium-cli/releases/latest/download/cilium-darwin-amd64.tar.gz \
  -o cilium-darwin-amd64.tar.gz ; tar xzvf $_

Run the install command.

./cilium install
[...]
Auto-detected IPAM mode: eni
Auto-detected datapath mode: aws-eni
Custom datapath mode: aws-eni
[...]

The cilium install command uses your current kube context to detect the best configuration for your Kubernetes distribution and perform the installation. As you can see in the partial output listed above, Cilium is being installed on AWS ENI mode. It’s also possible to use Cilium with the AWS VPC CNI plugin - refer to the docs for more details.

When the installation finishes, you can check that Cilium is running with the CLI using:

./cilium status --wait

and validate the installation with:

./cilium connectivity test

To manually check that Cilium is running you can also check that the Cilium Pods are in running status.

kubectl get po -n kube-system

NAMESPACE     NAME                               READY   STATUS    RESTARTS   AGE
kube-system   cilium-9vnf8                       1/1     Running   0          3m43s
kube-system   cilium-bjdb4                       1/1     Running   0          3m43s
kube-system   cilium-operator-7c9b465f5c-blk4w   1/1     Running   0          3m43s
kube-system   coredns-7d74b564bd-2d2t9           1/1     Running   0          3m34s
kube-system   coredns-7d74b564bd-tkxgp           1/1     Running   0          3m19s
kube-system   kube-proxy-cnvph                   1/1     Running   0          23m
kube-system   kube-proxy-hzkt9                   1/1     Running   0          23m

Note that when running Cilium in an autoscaling cluster, you’ll want to add the node.cilium.io/agent-not-ready=true:NoSchedule taint to the nodes to prevent that Pods are scheduled in the node before Cilium is ready. Check the Cilium installation guide for more details.

Deploy a Demo Application

Let’s deploy a demo application to perform some tests.

git clone https://github.com/microservices-demo/microservices-demo

kubectl create namespace sock-shop

kubectl apply -f microservices-demo/deploy/kubernetes/complete-demo.yaml

Sock Shop is an application that implements an online socks shop and is usually used to demonstrate and test microservices and cloud-native technologies.

Wait until all the Pods are in running status.

watch 'kubectl get po -n sock-shop'
NAME                            READY   STATUS    RESTARTS   AGE
carts-b4d4ffb5c-t8w2r           1/1     Running   0          3m45s
carts-db-6c6c68b747-5wdm7       1/1     Running   0          3m45s
catalogue-5bd97b4988-dsg6b      1/1     Running   0          3m45s
catalogue-db-96f6f6b4c-dbwts    1/1     Running   0          3m45s
front-end-6649c54d45-p7c66      1/1     Running   0          3m44s
orders-7664c64d75-ffw4d         1/1     Running   0          3m44s
orders-db-659949975f-x7cqq      1/1     Running   0          3m44s
payment-dd67fc96f-rvrx4         1/1     Running   0          3m44s
queue-master-5f6d6d4796-vx4f5   1/1     Running   0          3m44s
rabbitmq-5bcbb547d7-z7bxf       2/2     Running   0          3m43s
session-db-7cf97f8d4f-w8mhg     1/1     Running   0          3m43s
shipping-7f7999ffb7-4bnhj       1/1     Running   0          3m43s
user-888469b9f-lcv4n            1/1     Running   0          3m43s
user-db-6df7444fc-77ht7         1/1     Running   0          3m43s

When the Pods are running, you can optionally test it locally by port-fowarding the frond-end service.

kubectl port-forward service/front-end -n sock-shop 8080:80

You can now access it on the browser at localhost:8080. Use the username and password user / password to login.

Lateral Movement

As you can see in the Architecture Diagram, this application has a few components. Among them is the user component that interacts with the user-db, which is a MongoDB instance used to store users’ information.

We will play the role of a malicious actor that has gained access to create a Pod in our cluster and use this access to connect to the user-db.

List the services and observe the users-db service responding on port 27017.

kubectl get svc -n sock-shop

Let’s launch a temporary Pod in interactive mode using the mongo image to execute commands against the user-db.

kubectl run rogue-1 --image=mongo:3.2 --restart=Never --rm -it -- /bin/bash

From inside the container run the following command to connect to the MongoDB instance:

root@rogue-1:/# mongo --host user-db.sock-shop.svc.cluster.local:27017

Now, from the mongo session, run:

> show dbs
local  0.000GB
users  0.000GB

> use users
switched to db users

> show collections
addresses
cards
customers

> db.cards.find()
{ "_id" : ObjectId("57a98d98e4b00679b4a830ae"), "longNum" : "5953580604169678", "expires" : "08/19", "ccv" : "678" }
{ "_id" : ObjectId("57a98d98e4b00679b4a830b1"), "longNum" : "5544154011345918", "expires" : "08/19", "ccv" : "958" }
{ "_id" : ObjectId("57a98d98e4b00679b4a830b4"), "longNum" : "0908415193175205", "expires" : "08/19", "ccv" : "280" }
{ "_id" : ObjectId("57a98ddce4b00679b4a830d2"), "longNum" : "5429804235432", "expires" : "04/16", "ccv" : "432" }

Kubernetes Network Policies

Let’s use a Kubernetes Network Policy to restrict ingress traffic to the users-db Pod to only traffic from the users Pod.

To create this Network Policy, we need to know the labels used for each of these Pods. We can check this by running:

kubectl describe deploy user-db -n sock-shop
Name:                   user-db
Namespace:              sock-shop
[...]
Pod Template:
  Labels:  name=user-db
  Containers:
[...]

kubectl describe deploy user -n sock-shop
Name:                   user
Namespace:              sock-shop
[...]
Pod Template:
  Labels:  name=user
  Containers:
[...]

Note the Pod labels name=user-db and name=user.

Create the Network Policy.

kubectl apply -f- << EOF
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: user-db-allow-from-user
  namespace: sock-shop
spec:
  policyTypes:
  - Ingress
  PodSelector:
    matchLabels:
      name: user-db
  ingress:
  - from:
    - PodSelector:
        matchLabels:
          name: user
EOF

Create a temporary Pod again and try to connect to the MongoDB instance.

kubectl run rogue-1 --image=mongo:3.2 --restart=Never --rm -it -- /bin/bash

root@rogue-1:/# mongo --host user-db.sock-shop.svc.cluster.local:27017
[...]

It should fail now.

A note on Kubernetes Security

The goal of this tutorial is to check how Cilium works with EKS allowing us to experiment with Network Policies and other features. The focus is not to provide best practices on how to secure your Kubernetes cluster. For instance, notice that a malicious actor, depending on the level of access that has been granted, can use different approaches to bypass this policy and get access to PII data or lead to service disruption. To properly secure your cluster, you should consider a combination of techniques such as using an Admission Controller (e.g. Gatekeeper or Kyverno); tuned RBAC permissions; proper monitoring and alerting on suspicious behavior (e.g. Falco); among others.

Access the application on the browser at localhost:8080 and verify that everything is still working fine.

Besides being able to use standard Kubernetes Network Policies, Cilium also allows you to use CiliumNetworkPolicy and CiliumClusterwideNetworkPolicy that extend the standard policies, providing more capabilities. See the Network Policies tutorials for examples. It’s also worth taking a look at the functionality overview doc for a list of Cilium features that go beyond Network Policies.

Clean up

eksctl delete cluster --name $cluster_name --region $cluster_region