Cilium : un firewall pour les conteneurs
Dans un précédent article, nous vous avons présenté eBPF. Ce mécanisme du kernel basé sur une machine virtuelle minimaliste permet d’intercepter des événements du kernel afin de tracer des appels de fonctions internes ou encore de filtrer ou modifier des paquets réseaux.
Cette technologie est utilisée par le projet Cilium, qui permet de mettre en place du filtrage réseau dans le monde des conteneurs. Dans cet article, nous vous proposons de découvrir un cas concret en vous présentant sa mise en place et son utilisation sur un cluster Kubernetes.
Cet article a été co-écrit avec Éric Briand
Le modèle réseau de Kubernetes
Lorsqu’un cluster Kubernetes est construit, le réseau des Pods doit être vu comme à plat. C’est à dire, que chaque Pods doit pouvoir contacter les autres via leur IP sans NAT. Cela signifie que par défaut, il n’y a pas de filtrage réseau et que le réseau n’est pas cloisonné.
Néanmoins, en fonction de la solution réseau déployée, il est possible de mettre en oeuvre les NetworkPolicies qui permettent de mettre en place du firewalling.
La manière dont les NetworkPolicies s’appliquent est un peu particulière. Par défaut, tout le trafic sortant et entrant d’un Pod sera autorisé jusqu’à ce qu’une NetworkPolicy s’applique à lui. À partir de ce moment là, le trafic autre que celui explicitement autorisé est bloqué.
L’exemple suivant permet de s’assurer que seuls les Pods ayant un label “app=bookstore” puissent accéder au Pod ayant les labels “app=bookstore” et “role=api”.
kind: NetworkPolicy apiVersion: networking.k8s.io/v1 metadata: name: api-allow spec: podSelector: matchLabels: app: bookstore role: api ingress: - from: - podSelector: matchLabels: app: bookstore
Exemple de NetworkPolicy – source : https://github.com/ahmetb/kubernetes-network-policy-recipes/blob/master/02-limit-traffic-to-an-application.md
Pour en savoir plus sur les NetworkPolicy et consulter des exemples illustrés vous pouvez vous référer au dépôt github kubernetes-network-policy-recipes qui est assez complet et très didactique.
Au passage, si vous utilisez déjà les NetworkPolicies, notez qu’à partir de Kubernetes 1.16 le groupe d’API à utiliser est obligatoirement “networking.k8s.io/NetworkPolicy”, l’ancien “extensions.k8s.io/NetworkPolicy” ne sera plus supporté.
Ce mécanisme est pratique lorsque l’on reste à une granularité au Pod, mais les NetworkPolicies ne permettent pas de faire du filtrage sur les ressources d’une API REST :
L’exemple est le suivant :
- l’internal frontend doit pouvoir accéder à toutes les ressources REST
- l’events-frontend doit seulement accéder à la ressource REST heroes mais pas à la ressource REST identities
Nous allons voir comment le projet Cilium permet de réaliser ce type de filtrage.
Présentation du projet
Le projet Cilium a été créé par Thomas Graf (CTO et co-fondateur de Isovalent) en 2015. Le projet est open-source et hébergé sur github : https://github.com/cilium/cilium
Il est développé en utilisant les langages go et C. Le langage C étant principalement utilisé pour la partie eBPF.
Leur travail sur Cilium les a amené à maintenir la documentation officielle d’eBPF et XDP ainsi qu’à produire de nombreuses contributions pour le noyau Linux sur le sujet.
Déploiement dans un cluster Kubernetes
Cilium est un projet qui peut se déployer avec Docker, Mesos et Kubernetes. Vous pouvez retrouvez le détail des différents modes de déploiement dans la documentation officielle.
Le projet Kubernetes indique comment l’installer sur un cluster créé avec kubeadm.
Vous pouvez également consulter ce projet où nous avons mis en place Cilium pour illustrer le propos.
Le projet Cilium se déploie sous la forme d’un Operator Kubernetes. C’est un type de composant spécifique qui va gérer toute la complexité de l’applicatif déployé. Dans le cas de Cilium, cela veut dire que l’opérateur créera :
- les CRDs nécessaires à Cilium
- le cluster etcd dans lequel Cilium stocke son état
- le DaemonSet de déploiement de l’agent Cilium sur chacun des noeuds du cluster.
Utilisation
Une fois Cilium déployé, il est possible de créer des CiliumNetworkPolicy. Celles-ci se présentent comme des NetworkPolicies Kubernetes classiques, avec en plus la possibilité de définir des règles complémentaires.
Par exemple, pour implémenter les règles de filtrages présentées dans l’exemple précédent, il faut créer les deux NetworkPolicies ci-dessous :
apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: allow-heroes-identity-api namespace: api spec: endpointSelector: matchLabels: app: heroes-api ingress: - fromEndpoints: - matchLabels: k8s:io.kubernetes.pod.namespace: internal toPorts: - ports: - port: "80" protocol: TCP rules: http: - method: "GET" path: "/heroes/?.*" - method: "GET" path: "/identities/?.*" --- apiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: allow-only-heroes-api namespace: api spec: endpointSelector: matchLabels: app: heroes-api ingress: - fromEndpoints: - matchLabels: k8s:io.kubernetes.pod.namespace: events toPorts: - ports: - port: "80" protocol: TCP rules: http: - method: "GET" path: "/heroes/?.*"
A la différence des NetworkPolicies, les sélecteurs des CiliumNetworkPolicies ne sont pas sur les pods mais sur des endpoints (que nous allons définir juste après). Cilium les identifie en se basant sur un système de label agnostique de l’orchestrateur. Pour pouvoir sélectionner un endpoint par rapport à un label spécifique à Kubernetes (comme le namespace), on doit préfixer le label par k8s:
k8s:io.kubernetes.pod.namespace: events
Ce qui nous intéresse surtout dans ce cas là ce sont les règles suivantes autorisant les requêtes GET sur les ressources identities et heroes.:
rules: http: - method: "GET" path: "/heroes/?.*" - method: "GET" path: "/identities/?.*"
On peut avoir le même type de règles pour kafka :
rules: kafka: - role: produce topic: events - role: consume topic: events
Celle-ci autorise la production et la consommation de message sur le topic events.
Fonctionnement du projet
En pratique, Cilium recense l’ensemble des Pods déployés sur le cluster et leur associe la notion de Endpoint.
Un Endpoint sera identifié par son IP et portera des informations complémentaires liées au pod qu’il représente (namespace, labels, …)
Ces informations seront stockées et mise à disposition des programmes BPF sous forme de map eBPF partagées entre l’espace utilisateur et le noyau de chacun des noeuds.
L’exploitation de ces informations et des règles définies par les NetworkPolicies permettent aux programmes BPF de filtrer les paquets.
Si vous souhaitez en savoir plus, les programmes BPF de Cilium sont disponibles dans le répertoire bpf du projet.
— Source : https://cilium.readthedocs.io/en/v1.6/architecture/#endpoint-to-endpoint
En complément, les règles de la couche 7 (http, kafka, …) s’appuient sur un proxy envoy qui met en place les règles de filtrage spécifiques.
Outillage
Le projet Cilium n’est pas fait que pour Kubernetes, ainsi il fournit un CLI qui permet d’interagir avec le démon Cilium, que celui-ci soit déployé sur un cluster Kubernetes, Mesos Marathon, …
Ce CLI permet notamment de simplifier le débogage de NetworkPolicy. L’exemple ci-dessous permet de connaître les règles qui s’appliquent pour le trafic entre deux Pods :
cilium policy trace --src-k8s-pod NAMESPACE:POD_FROM --dst-k8s-pod NAMESPACE:POD_TO
Exemple de sortie :
root@worker-1:~# cilium policy trace --src-k8s-pod events:events-frontend-c5f46d89d-w7qnb --dst-k8s-pod api:heroes-api-6f8ffbc95-rpbdp --------------------- Tracing From: [k8s:io.kubernetes.pod.namespace=events, k8s:io.cilium.k8s.policy.serviceaccount=default, k8s:io.cilium.k8s.policy.cluster=default, k8s:app=events-frontend, k8s:team=events] == To: [k8s:io.cilium.k8s.policy.cluster=default, k8s:app=heroes-api, k8s:team=api, k8s:io.kubernetes.pod.namespace=api, k8s:io.cilium.k8s.policy.serviceaccount=default] * Rule {"matchLabels":{"any:app":"heroes-api","k8s:io.kubernetes.pod.namespace":"api"}}: selected Allows from labels {"matchLabels":{"k8s:io.kubernetes.pod.namespace":"internal"}} Labels [k8s:io.kubernetes.pod.namespace=events k8s:io.cilium.k8s.policy.serviceaccount=default k8s:io.cilium.k8s.policy.cluster=default k8s:app=events-frontend k8s:team=events] not found * Rule {"matchLabels":{"any:app":"heroes-api","k8s:io.kubernetes.pod.namespace":"api"}}: selected Allows from labels {"matchLabels":{"k8s:io.kubernetes.pod.namespace":"events"}} Found all required labels Rule restricts traffic to specific L4 destinations; deferring policy decision to L4 policy stage 2/6 rules selected Found no allow rule Label verdict: undecided Final verdict: DENIED
Pour en savoir plus, vous pouvez consulter la documentation disponible ici.
Dernières évolutions
Cilium vient de passer en 1.6 et apporte quelques nouvelles fonctionnalités très intéressantes. Nous retiendrons :
- La possibilité de remplacer kube-proxy de Kubernetes par Cilium
- Minimiser la compilation de programmes BPF sur les noeuds en revoyant la façon dont ils sont générés
- La possibilité de coupler Cilium avec un autre network addon CNI
Vous pouvez retrouver les releases notes la version 1.6 ici : https://cilium.io/blog/2019/08/20/cilium-16/#16Highlights
Dans le futur, le projet va encore évoluer et apporter encore plus de stabilité en s’assurant que Cilium fonctionne sur des clusters Kubernetes de grande taille (5k Nodes, 20k Pods, 10k Services)
Références
- https://github.com/ebriand/conf-cilium
- https://cilium.io/
- http://www.brendangregg.com/ebpf.html
- https://jvns.ca/blog/2017/06/28/notes-on-bpf—ebpf/
- https://www.youtube.com/watch?v=_Iq1xxNZOAo
- https://cilium.io/blog/2018/12/03/cni-performance
- https://itnext.io/benchmark-results-of-kubernetes-network-plugins-cni-over-10gbit-s-network-updated-april-2019-4a9886efe9c4