Blog Zenika

#CodeTheWorld

DevOps

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 :

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

Auteur/Autrice

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.