Les conteneurs représentent une opportunité de déploiement rapide, flexible et efficace des applications.
En 2019, 84% des infrastructures de production utilisaient déjà des conteneurs[1]. Comme souvent, cette adoption massive s’est faite sans l’intégration des équipes CyberSécurité, parfois par méconnaissance de la technologie, parfois par une vision de simplicité et efficacité des équipes de développement.
Le besoin de sécurisation des conteneurs est plus présent que jamais, il est temps que les équipes Cyber comprennent la technologie pour définir les bonnes mesures de sécurité
Dans un premier temps, nous présenterons une comparaison entre les conteneurs et les machines virtuelles, puis nous reviendrons sur les raisons de l’émergence des conteneurs. Nous verrons ensuite comment les sécuriser tout au long de leur cycle de vie, étape par étape.
Machine virtuelle, conteneur : quelle différence ?
Mais pourquoi choisir un conteneur ? Pour comprendre cela, il faut tout d’abord regarder la différence entre une machine virtuelle et un conteneur
La différence principale entre une VM (Virtual Machine) et un conteneur réside dans les éléments inclus dans l’espace virtualisé. Un conteneur contient uniquement l’applicatif et les dépendances nécessaires pour son exécution alors qu’une VM contiendra un système d’exploitation sur lequel seront installées une ou plusieurs applications. Un conteneur ne possédant pas de système d’exploitation, il s’appuie sur celui de l’hôte sur lequel il s’exécute. Cette distinction permet de gagner en légèreté et complexité.
Mais alors, pourquoi les conteneurs ?
Les conteneurs n’ont pas été développés dans un objectif de renforcer le niveau de sécurité, mais plutôt pour des besoins d’infrastructure. Les principaux avantages sont :
- La consistance: les conteneurs peuvent être lancés sur n’importe quelle machine, ils fonctionneront de la même manière.
- L’économie: les conteneurs sont plus rapides et demandent moins de ressources que les VMs, ils coûtent donc moins cher.
- L’automatisation: il est beaucoup plus simple d’automatiser le déploiement d’un conteneur que la création d’une machine virtuelle (les technologies Cloud ont fait beaucoup de progrès sur ce point depuis).
Ces trois avantages, combinés à la popularisation de l’approche DevOps au sein des entreprises, ont fait exploser l’utilisation de conteneurs. Sans pour autant être mise de côté, la sécurité n’a pas été un objectif dans la conception des conteneurs. Ainsi, les bonnes pratiques de sécurité se sont mises en place au fur et à mesure du développement et de l’utilisation de cette technologie.
Les modèles d’exécution
Les avantages des conteneurs sont liés à un mode de fonctionnement spécifique qui s’appuie sur une cinématique d’exécution très particulière. Regardons à présent quels sont les modèles d’exécution des conteneurs.
Un conteneur peut être exécuté sur une machine hébergée on-premise ou dans le Cloud. Comme expliqué précédemment, un conteneur contient uniquement un applicatif et ses dépendances. Il ne possède pas de système d’exploitation et s’appuie ainsi sur les fonctionnalités de l’hôte. Par conséquent, un conteneur nécessitant des fonctionnalités Linux, devra fonctionner sur une machine au système d’exploitation Linux. Réciproquement, un conteneur requérant des fonctionnalités Windows s’exécutera sur une machine Windows. Cependant, des procédés de virtualisation, comme Hyper-V pour Windows, permettent de s’affranchir de ces contraintes.
Pour exécuter un conteneur sur une machine, il faudra simplement installer un logiciel de gestion de conteneurs (un container runtime). Parmi les plateformes de conteneurs, Docker, lxd et Containerd sont les plus utilisées.
Il est ainsi simple d’exécuter un unique conteneur sur une machine. Cependant, les entreprises possèdent souvent de nombreux d’applicatifs. Un problème se pose alors pour la gestion et la mise à l’échelle des conteneurs à déployer.
C’est à ce moment-là que les orchestrateurs de conteneurs entrent en jeu. Un orchestrateur va permettre de gérer facilement le déploiement, la surveillance, le cycle de vie, la mise à l’échelle ainsi que la mise en réseau des conteneurs. Ces orchestrateurs peuvent être configurés sur des machines on-premise ou à travers des services mis à disposition par les fournisseurs Cloud. Dans ce second cas, leur mise en place et leur configuration seront facilitées, car elles sont gérées par le fournisseur Cloud. La technologie la plus répandue d’orchestrateur au sein des entreprises est Kubernetes. Il existe aussi de nombreux produits se basant dessus, comme OpenShift par exemple. D’autres alternatives comme Docker Swarn permettent également cette orchestration.
L’infographie suivante résume les modèles d’exécutions ainsi que le nom des technologies ou services :
Cette grande variété des modes de déploiement permet d’adapter au mieux le conteneur au besoin métier.
Focus sur l’orchestrateur Kubernetes
Comme énoncé précédemment, Kubernetes et les produits basés sur cette technologie pour l’orchestration sont les plus répandus. Kubernetes sera ainsi utilisé pour illustrer le fonctionnement d’un orchestrateur. Pour le schématiser simplement, prenons l’analogie d’un port à conteneurs.
Considérons tout d’abord les nœuds worker. Ce seront nos bateaux porte-conteneurs. Ils ont pour vocations de porter la charge, c’est-à-dire d’exécuter les conteneurs de l’orchestrateur.
Kubernetes introduit ensuite le concept de pods. Un pod sera les conteneurs sur les bateaux. Un pod est généralement composé d’un unique conteneur. C’est ce composant qui fait tourner l’applicatif à déployer.
Ensuite, nous avons le control plane constitué des nœuds master. Ils sont représentés par les grues qui vont dispatcher les conteneurs d’un bateau à un autre selon la charge que chaque bateau peut accueillir. En termes techniques Kubernetes, le nœud master va décider sur quel(s) nœud(s) worker exécuter les pods. Le nœud master est le point central du cluster. Il contient toute l’intelligence du cluster. C’est également avec lui qu’on interagit pour administrer le cluster et c’est avec lui que les nœuds worker interagissent pour savoir quelles actions effectuer selon les pods qu’ils exécutent (créer de nouveau, les détruire…).
Pour finir, on aura un équilibreur de charge qui sera, dans cette analogie, représenté par les camions acheminant les conteneurs. L’équilibreur de charge permet de répartir la charge de flux entrants entre les pods. Par exemple, si trois pods hébergent le même applicatif, l’équilibreur de charge fera en sorte de répartir les requêtes entre les 3 pour ne pas en surcharger un. L’équilibreur de charge est l’interface entre le cluster et l’extérieur, tout comme les camions font le lien avec l’extérieur du port.
Plus classiquement, voici le schéma technique reprenant les différents éléments[2] :
Comment sécuriser le container à toutes les étapes de son cycle de vie ?
Maintenant que nous avons abordé les notions de base, voyons comment on peut sécuriser tout cela. La sécurité doit s’appliquer à chacun des stades du cycle de vie d’un conteneur. En effet, chaque stade présente des défis et des impacts de sécurité associés.
L’image est d’abord construite
La première étape du cycle de vie des conteneurs est de choisir une image de base. Une image de conteneur est un ensemble de logiciels légers et de fichiers qui comprend tout ce qui est nécessaire à l’exécution d’une application : le code, le moteur d’exécution, les outils système, les bibliothèques système et les paramètres. Le plus souvent, on va récupérer cette image sur internet. Il existe donc un risque d’utiliser une image d’une source inconnue qui serait déjà compromise (avec une backdoor par exemple).
Ainsi, dans cette première étape, il est primordial de bien choisir la source de son image pour s’assurer de prendre une image de confiance (« Trusted image »). Pour cela, on peut s’appuyer sur des sources de référence comme Docker Hub ou encore proposer un catalogue d’images propre à son entreprise. Dans ce dernier cas, les images sont vérifiées et validées en amont par les équipes sécurité de l’entreprise, on les appelle des « golden images ».
La seconde étape est d’installer une application sur l’image. Il existe donc un risque classique de vulnérabilité dans le code de l’application. Les scans de vulnérabilités, la sensibilisation des développeurs et le respect des bonnes pratiques de développement s’imposent ici pour éviter qu’une vulnérabilité ne se glisse dans le code de l’application.
La troisième étape est la configuration des images. Il s’agit de configurations par défaut appliquées lors du déploiement des conteneurs. À titre d’exemple, un conteneur est exécuté avec le compte root (ou administrateur système) par défaut : ne pas modifier cette configuration représente un risque si le conteneur venait à être compromis. De plus, le fait de mettre le système de fichier du conteneur en lecture seule, permet également de limiter les impacts en cas de compromission. En effet, avec ces deux configurations, un attaquant aura moins de champ libre pour ses actions.
L’image est ensuite stockée dans un dépôt de conteneurs
Une fois l’image construite, il faut la stocker pour pouvoir y accéder et la déployer autant de fois qu’on le souhaite. Pour cela on utilise un dépôt de conteneurs qu’il est nécessaire de sécuriser également. En effet, si un attaquant pousse une image vérolée dans le dépôt de conteneur, cette dernière peut être déployée en production.
Plusieurs mesures de sécurité peuvent être mises en place pour sécuriser le dépôt de conteneurs :
- Restreindre les droits et les permissions des utilisateurs ou des ressources sur le dépôt pour réduire le risque : seules les personnes ou ressources ayant besoin de « pousser » ou « retirer » une image du dépôt doivent être en droit de le faire.
- Restreindre l’exposition réseau.
- Signer les images déposées pour assurer leur intégrité.
- Conserver une trace des actions effectuées sur le dépôt de conteneurs.
S’ensuit la phase de déploiement de l’image
Une fois l’image construite et stockée, il faut désormais la déployer pour la rendre accessible.
Lorsque l’on déploie un conteneur, on va déterminer des configurations selon les cas d’usage. Certaines configurations permettent ainsi de réduire l’isolation logique existante entre les conteneurs et l’hôte. Par exemple il est possible d’autoriser un conteneur à lister les processus de l’hôte ou encore partager la même carte réseau. La configuration privileged, permet même de faire tomber ces barrières d’isolation et donne aux conteneurs un accès à l’ensemble des fonctionnalités de l’hôte. Ces configurations, certaines dangereuses, peuvent aboutir à des échappements de conteneur : c’est-à-dire un attaquant qui est sur un conteneur peut utiliser ces privilèges pour s’échapper et parvenir au système d’exploitation. Une fois sur le système d’exploitation, un attaquant pourra obtenir des informations à partir de fichiers de l’hôte ou initier des déplacements latéraux. En d’autres termes, c’est un pas de plus dans le système d’information.
En termes de recommandation sur le déploiement, il sera ainsi nécessaire dans un premier temps de limiter les dépôts de conteneurs à une liste connue et de confiance. Par la suite, des configurations comme AppArmor, Seccomp ou la désactivation des capabilities Linux permettent de restreindre les appels système et les ressources qui seront utilisées par les conteneurs. Pour finir, il conviendra de configurer le système de fichiers du conteneur en lecture seule (ReadOnly) et d’appliquer le principe de moindre privilège sur les configurations passées aux conteneurs. En d’autres termes, il est nécessaire de limiter l’utilisation de la configuration privileged ou encore la rupture de certaines isolations (process, réseau, etc.).
Enfin le conteneur est exécuté
Pour ce qui est de l’exécution, nous allons nous concentrer sur les méthodes privilégiées par les entreprises. C’est-à-dire les orchestrateurs, avec souvent Kubernetes, ou bien les services d’hébergement de conteneurs sur le Cloud, les CaaS.
Dans le cas de l’orchestration par Kubernetes, l’objectif sera d’abord de vérifier la conformité des déploiements de conteneurs afin d’éviter le déploiement de conteneurs dangereux privilégiés. Ils peuvent provenir d’attaque ou simplement d’erreurs d’administration. Selon les plateformes, on parle des , des SecurityContextConstraint ou encore d’outils externes comme OPA Gatekeeper.
Il est également recommandé de limiter les flux réseau au sein du cluster, entre les conteneurs, et sortants du cluster afin de restreindre les déplacements latéraux. Cette restriction peut être appliquée avec des NetworkPolicy ou à nouveau avec des outils externes de micro-segmentation. Pour finir, il sera nécessaire d’avoir une gestion fine des rôles et utilisateurs ainsi que d’appliquer un durcissement suffisant sur les machines virtuelles servant de nœuds.
Dans le cas des CaaS, l’infrastructure est gérée par le fournisseur Cloud. En tant qu’utilisateur, le durcissement pourra se faire uniquement par l’activation ou la désactivation de certaines options. Une analyse de chaque solution sera nécessaire pour définir des recommandations précises car Azure, Google Cloud Platform ou encore Amazon Web Services proposent des options différentes.
Pour finir, surveiller l’ensemble des étapes
Le monitoring des conteneurs est important pour des besoins de debug ou encore de récupération de preuves en cas d’incident. Malheureusement, contrairement à une machine virtuelle, un conteneur est éphémère. Ses journaux le sont aussi par conséquent… Dans ce cas, comment faire ?
La supervision pourra se faire à trois niveaux :
- Au niveau des conteneurs en externalisant les journaux (pour lutter contre le côté éphémère des conteneurs et de ses journaux)
- Au niveau de la charge de travail des conteneurs (workload)
- Au niveau des infrastructures (les nœuds du cluster par exemple)
Cette journalisation collectée pourra être gérée par des équipes SOC Cloud dédiées ou centraliser dans le SIEM de l’entreprise. Par la suite, des scénarios de détection pourront être créés
Il est important de mentionner que les solutions CaaS ainsi que les Kubernetes managés par un fournisseur Cloud (AKS, EKS, GKE, …) permettent de faciliter la centralisation et l’externalisation de ces journaux.
Cette partie a permis d’aborder les bonnes pratiques à respecter et les risques associés à chaque étape du cycle de vie d’un conteneur. Le schéma ci-dessous permet d’en faire un résumé :
CWPP, la solution à nos problèmes ?
CWPP, Cloud Workload Protection Platform, est un nouvel outil dont on entend beaucoup parler en ce moment. Mais que permet-il ?
Un CWPP est un outil permettant de surveiller et de détecter les menaces sur les workloads, i.e l’ensemble des services exécutés sur le cloud, et en particulier les conteneurs. Il permet d’assurer une partie de la sécurité tout au long du cycle de vie présenté précédemment. Il est notamment utile pour la détection de secrets et de vulnérabilités dans les librairies des applications, la réalisation d’une revue des accès au dépôt, la vérification des configurations ou encore la gestion de la partie monitoring (collecte des logs, détection et remédiation).
Comme tout outil, le CWPP ne sera pas magique. Il sera nécessaire de le déployer avec ou sans agent suivant les cas de figure que l’on souhaite couvrir. Mais au-delà de l’aspect technique du déploiement, il sera nécessaire de l’intégrer dans le processus de l’entreprise pour que tous les acteurs aient un outil leur permettant d’optimisant la sécurité. Il ne faut donc pas minimiser la charge nécessaire de définition de stratégie, de nouveaux processus et d’accompagnement au changement des acteurs ainsi que l’intégration de l’outil avec les outils utilisés par les développeurs. Par exemple, un développeur souhaitera être informé qu’il doit réaliser une remédiation sur un conteneur sur son outil de gestion des incidents (JIRA, issue dans le Git projet…) et être en mesure de pouvoir tester son nouveau conteneur depuis sa machine avant même de l’avoir poussé dans le dépôt de conteneur. Les fonctionnalités d’un CWPP sont bien souvent déjà partiellement ou totalement couvertes par des outils existants, sa mise en place peut permettre de centraliser la vision et parfois d’optimiser les coûts de licences.
Les éléments clés de la sécurisation des conteneurs
Vous l’aurez compris avec cet article, le conteneur est né pour des besoins d’infrastructure. Leur aspect léger et flexible en fait un atout parfait pour les besoins applicatifs actuels. Cette démocratisation induit de nouvelles surfaces d’attaques à protéger et impose donc la prise en compte de la sécurité des conteneurs.
Malheureusement, il n’existe pas un outil ou une unique bonne pratique à respecter. En effet, comme l’article l’illustre, c’est un ensemble d’éléments qui permettent de sécuriser ces boites applicatives. Parmi les bonnes pratiques à respecter, les 5 points suivants sont les éléments clés à retenir :
- Contrôler les images : en utilisant une image de confiance durcie, en sécurisant le code source et en réalisant des scans de vulnérabilités
- Sécuriser l’isolation des conteneurs : en évitant les configurations dangereuses lors du déploiement des conteneurs et via le durcissement des images
- Assurer la segmentation réseau : en s’assurant de restreindre l’exposition externe du cluster, les flux au sein du cluster et en sortie du cluster
- Monitoring et détection : en récupérant des journaux à 3 niveaux différents et en mettant en place des scénarios de détection
- Sécuriser l’accès IAM : en appliquant une gestion fine de l’IAM sur le cluster ou sur le fournisseur Cloud. Cette gestion peut s’accompagner de revue périodique.
[1] https://www.lemondeinformatique.fr/actualites/lire-l-usage-des-containers-en-production-bondit-a-84-78347.html
[2] https://kubernetes.io/docs/concepts/overview/components/