Comment protéger son cluster Kubernetes ?

Publié le

Lorsqu'on utilise un cluster Kubernetes en production, la sécurité doit être une préoccupation majeure. Gérer la sécurité d'une infrastructure n'est pas facile, en particulier lorsqu'on utilise des outils complexes comme Kubernetes.

Dans cet article, nous abordons quelques règles de bonne pratique à suivre pour renforcer la sécurité d'un cluster Kubernetes.

Bien utiliser RBAC

Par défaut, les Pods d'un cluster Kubernetes reçoivent un ServiceAccount dont le rôle est d'autoriser certains appels d'API à Kubernetes. Si le ServiceAccount hérite de privilèges élevés, il donne la possibilité de découvrir des informations critiques depuis des Secrets ou d'autres objets. Un attaquant qui aurait accès au système de fichiers ou à un shell dans un container pourrait profiter de cette situation.

En utilisant correctement RBAC (Role Based Access Control), il est possible de mettre en place une politique de moindre privilège, en définissant des droits d'accès minimum pour un ServiceAccount. RBAC est activé par défaut dans Kubernetes.

Pour utiliser RBAC, nous avons à notre disposition plusieurs types d'objets :

  • Un Role définit des autorisations au niveau d'un namespace

  • Un ClusterRole définit des autorisations au niveau du cluster tout entier

  • RoleBinding et ClusterRoleBinding lient respectivement un Role et un ClusterRole à un ServiceAccount.

Mettre en place un objet NetworkPolicy

Les objets de type NetworkPolicy agissent comme des règles de pare-feu. Par exemple, l'accès à un service de base de données pourrait être limité à certains Pods uniquement. La définition de ces règles de pare-feu se fait avec les labels des Pods concernés.

Un exemple de NetworkPolicy tiré de la documentation :

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - ipBlock:
        cidr: 172.17.0.0/16
        except:
        - 172.17.1.0/24
    - namespaceSelector:
        matchLabels:
          project: myproject
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 6379
  egress:
  - to:
    - ipBlock:
        cidr: 10.0.0.0/24
    ports:
    - protocol: TCP
      port: 5978

Ce NetworkPolicy définit des règles pour le trafic entrant (Ingress) et sortant (Egress) depuis le champ policyTypes.

La règle d'Ingress autorise l'accès aux Pods qui contiennent un label role=db depuis:

  • les Pods avec le label role=frontend,
  • les namespaces avec le label project=myproject,
  • toutes les adresse IPs dans le réseau 172.17.0.0/16, sauf celle du bloc 172.17.1.0/24.

La règle d'Egress autorise les pods avec le label role=db, à accéder au port 5978 de toutes les adresses IPs du réseau 10.0.0.0/24.

Les ressources impactées par cette définition sont dans le même namespace que l'objet NetworkPolicy, ainsi que d'autres namespaces qui correspondent aux labels de namespaceSelector (si ce champ est défini).

Pour qu'un objet NetworkPolicy fonctionne, il est nécessaire que le cluster utilise un plugin CNI compatible, comme Calico ou Cilium. Attention à Flannel, qui n'implémente pas ces définitions.

Restreindre l'accès à l'API Server

Si l'accès à l'API Server est limité aux personnes ou aux processus concernés par son utilisation, on réduit considérablement la surface d'attaque.

Une bonne pratique est d'implémenter ce type de sécurité avec des règles de pare-feu dont le rôle est de n'autoriser que certains réseaux ou adresses IP à accéder à l'API Server. Dans le cas où les utilisateurs du cluster auraient une adresse IP dynamique, une solution consiste à rajouter un VPN englobant les clients et l'API Server.

Protéger l'accès à Etcd

Etcd est une base de données qui contient tout l'état d'un cluster Kubernetes. L'accès à ce service à travers le réseau permet de consulter les définitions de tous les objets du cluster. Un attaquant qui aurait accès à Etcd pourrait déployer ce qu'il voudrait ou modifier les ressources existantes.

De plus, dans un cluster qui n'implémente pas le chiffrement des secrets (encryption at rest), ce même attaquant a la possibilité de récupérer toutes ces informations parfois critiques (clés d'API, mots de passe, certificats, etc.).

Pour prévenir ces risques, il est essentiel de définir des règles de pare-feu qui n'autorisent l'accès à Etcd que depuis le control plane. Il est aussi très intéressant de mettre en place l'encryption at rest pour protéger les Secrets. On peut le faire soit avec du chiffrement standard, soit avec un fournisseur KMS.

Mieux isoler les containers

Dans le passé, des failles de sécurité ont permis de s'échapper de l'isolation d'un container. Pour s'en protéger, une bonne pratique est de mettre à jour les composants du cluster le plus souvent possible.

Il est toujours possible de sortir d'un container, si celui-ci est privilégié, et ce n'est pas une faille de sécurité. La solution à ce problème est de réduire l'étendue des privilèges d'un Pod en définissant son contexte de sécurité, par exemple :

apiVersion: v1
kind: Pod
metadata:
  name: security-context-demo
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
    readOnlyRootFilesystem: true
  volumes:
  - name: sec-ctx-vol
    emptyDir: {}
  containers:
  - name: sec-ctx-demo
    image: busybox
    command: [ "sh", "-c", "sleep 1h" ]
    volumeMounts:
    - name: sec-ctx-vol
      mountPath: /data/demo
    securityContext:
      allowPrivilegeEscalation: false

Ce qui est important ici, ce sont :

  • les champs spec.securityContext.runAsUser et spec.securityContext.runAsGroup, qui spécifient avec quel utilisateur (uid) et quel groupe primaire (gid) les processus sont executés.

  • le champ spec.containers.*.securityContext.allowPrivilegeEscalation avec la valeur false qui empêche un processus d'avoir plus de droits que son parent.

  • le paramètre spec.securityContext.readOnlyRootFilesystem à true qui rend impossible la modification du système de fichiers du container. Dans le cas où une faille de sécurité serait exploitée, un attaquant ne pourrait pas facilement altérer l'environnement d'exécution dans un container.

Quelques options supplémentaires

  • Utiliser un module de sécurité tel que AppArmor ou SELinux
  • Forcer les Pods à être conforme à un ensemble de règles avec les PodSecurityPolicy. Ce mécanisme fonctionne avec RBAC
  • Mettre en place des règles (notament de sécurité) avec Open Policy Agent. Kubernetes se connecte à ce service avant de planifier l'exécution d'un Pod, pour s'assurer du respect de ces règles. Dans le cas contraire, la plannification est suspendue
  • Utiliser un outil de gestion de services tel qu'istio pour sécuriser les communications entre les services du cluster, et faciliter leur observabilité
  • Avec un système de sandbox, on bénéficie de deux niveaux d'isolation : le mécanisme des containers, et le sandboxing lui même. Une des solutions possibles est d'exécuter les containers au travers de Kata Containers qui les lance dans une machine virtuelle très légère
  • Vérifier qu'un cluster Kubernetes est conforme aux recommandations du CIS benchmark avec un outil comme kube-bench d'Aqua Security
  • Limiter les droits d'accès des processus qui intéragissent avec Kubernetes (kubectl sur les postes de travail, dans les pipelines de CI/CD, etc.). Il serait par exemple illogique de leur attribuer les droits cluster-admin dans un environnement de production.

Conclusion

Il existe de nombreuses techniques qui peuvent être mises en place pour sécuriser un cluster Kubernetes. Je n'ai abordé ici que les plus courantes, tant les outils sont nombreux dans ce domaine.

D'une façon générale, ce sont les grands principes qu'il faut garder à l'esprit : limiter les privilèges, isoler au mieux les containers de leurs hôtes, empêcher ou restreindre les accès aux composants critiques du cluster (API Server, Etcd), chiffrer les communications quand c'est possible, etc.

Autres références

Remerciements

Cet article est en grande partie basé sur l'excellente présentation de Ian Lewis au Kubernetes Forum de Séoul (2019).

PHILIPPE CHEPY

Administrateur Système et Développeur