Skip to main content

Conseils et bonnes pratiques pour créer des images de conteneur sécurisées

Écrit par
blog feature build secure containers

6 juillet 2021

0 minutes de lecture

Lorsque vous commencez à analyser vos images de conteneur, découvrir qu’elles présentent un grand nombre de vulnérabilités peut être déconcertant. Voici le résultat d’une analyse que j’ai effectuée la semaine dernière sur une image Node vulnérable que j’avais créée. Il s’agit d’un exemple assez extrême, mais vous pouvez voir que cette image, telle quelle, présente plus de 800 vulnérabilités.

Analyse des vulnérabilités d’une image de conteneur indiquant 899 vulnérabilités dans différents packages, notamment bzip2, curl, file, git et glib2.0

Face à cela, beaucoup d’entre nous restent figés, comme un animal pris dans les phares d’une voiture, devant une longue liste de CVE, surtout si notre domaine est le développement d’applications et non l’administration système. Que suis-je censé faire de ces informations ? Par où commencer ? Je voulais simplement une image pour exécuter mon application Node, et me voilà déjà face à une tâche colossale pour la sécuriser.

La chose la plus importante à retenir, c’est que corriger ces problèmes dans des conteneurs ne revient pas à les corriger dans un système d’exploitation. Il ne s’agit pas de mettre à niveau des paquets individuels ni de gérer tout le système. Avec les conteneurs, nous devons comprendre comment les vulnérabilités se sont retrouvées dans nos images avant de réfléchir aux stratégies à adopter pour les corriger, comme celles de notre liste des 10 bonnes pratiques de sécurité Docker.

Que contient mon image de conteneur ?

Dans ce contexte, il est d’abord utile de comprendre comment les images que nous utilisons sont construites. À moins de créer nos images de zéro, il est probable que nous partions d’une image de base dans notre Dockerfile. Même si nous l’appelons image de base, celle que nous utilisons est probablement elle-même construite à partir d’une image parente, dans laquelle des logiciels ont été installés pendant le processus de build. L’image parente a, à son tour, été construite d’une manière ou d’une autre, peut-être à partir d’une autre image parente ou à l’aide d’un outil de création de système de fichiers racine. Comprendre comment les logiciels que nous analysons se sont retrouvés dans nos images est essentiel pour définir une stratégie de réduction des vulnérabilités.

Prenons l’exemple de l’image Nginx officielle sur Docker Hub. En examinant le Dockerfile de cette image, nous voyons qu’elle repose sur l’image Debian Buster slim, à laquelle sont ensuite ajoutés des logiciels et une configuration lors du build de l’image Nginx.

FROM debian:buster-slim

À son tour, l’image Debian Buster est créée à partir d’un autre Dockerfile, qui part d’une image scratch et y ajoute une archive tar.

FROM scratch
ADD rootfs.tar.xz /
CMD ["bash"]

En cherchant comment cette archive tar est créée, on découvre qu’elle est produite par l’outil debuerreotype, un ensemble de scripts utilisés par le projet Debian pour créer des systèmes de fichiers racine. C’est ainsi que procède Debian, mais les systèmes d’exploitation généralement utilisés comme images de base sont construits de différentes manières.

Tout cela montre que, même en nous limitant aux images de base, les logiciels qui y sont intégrés peuvent suivre un processus long et potentiellement complexe, difficile à retracer si l’on ne connaît pas ces différents paradigmes.

Scratch

Certains diront qu’il suffit d’utiliser scratch et de créer ses propres images à partir de zéro, c’est-à-dire d’un système de fichiers vide. Cette approche est valable dans certaines circonstances et peut bien fonctionner pour les binaires compilés de langages sans dépendances — Go ou C, par exemple. Mais pour la plupart des autres cas, vous devrez assurer la maintenance de tout ce qui entre dans l’image, ce qui représente une charge importante sur le long terme. Si nous créons de nombreuses images de conteneur différentes, cette charge peut rapidement devenir écrasante et le coût de maintenance risque de dépasser les avantages d’une surface d’attaque réduite.

Faire confiance ou non

Pour gérer les vulnérabilités dans les images, la confiance accordée à l’image de base est donc un facteur essentiel. Il faut trouver un équilibre entre faire confiance à l’image en amont et décider de maîtriser l’ensemble du processus de build de nos images, en assumant ainsi la responsabilité de tous les logiciels qui y sont installés.

Comme nous l’avons vu, nous devons également faire confiance à toute la chaîne de processus de build qui a servi à créer l’image que nous utilisons, ce qui peut être difficile à retracer. De nombreuses images présentes dans les registres publics sont mal construites ou ne sont plus maintenues, et, en général, ces registres n’assurent pas la qualité de la plupart des images qu’ils hébergent. Bien sûr, ce n’est pas différent de la façon dont nous utilisons la majorité des logiciels open source, et bon nombre des mêmes critères de qualité s’appliquent. Le logiciel est-il maintenu et mis à jour régulièrement ? Dispose-t-il d’une large communauté d’utilisateurs ? Des entreprises le prennent-elles en charge ? Toutes ces informations sont disponibles en ligne : prenez le temps de vérifier ce que vous utilisez réellement. Snyk Advisor est un excellent outil pour mener vos recherches.

Si nous décidons de faire confiance à notre image de base en amont, nous devons, en cas de problème lié à celle-ci, chercher une correction en amont plutôt que de maintenir notre propre version dérivée de l’image de base avec des paquets mis à niveau. Par nature, les conteneurs sont conçus pour être immuables. Si nous commençons à mettre à niveau des paquets pendant le processus de build de nos conteneurs, nous remettons en cause le principe même de l’utilisation d’une image de base. Cette stratégie deviendra rapidement ingérable, car nous finirons par être les mainteneurs de fait de notre image.

Mais choisir une image de base n’est pas toujours aussi simple qu’il y paraît. Par exemple, l’image de base Python « officielle » sur Docker Hub contient de nombreuses vulnérabilités et est très volumineuse. C’est assez courant pour les images d’exécution officielles, qui sont conçues pour répondre à tous les cas d’usage. Nous pourrions opter pour la version slim, plus légère et moins vulnérable, ou en chercher une autre — mais le dépôt contient un très grand nombre de tags. Comment choisir ?

Tout d’abord, le tag générique latest d’une image de framework de langage n’est probablement pas adapté à la production : il est difficile de savoir quelle version du framework est utilisée et elle pourrait changer à l’avenir. Mais slim n’est pas automatiquement le meilleur choix non plus : vous aurez peut-être moins de vulnérabilités, mais vous devrez alors peut-être gérer les dépendances de build.

Et la gagnante est… la construction multi-étapes

La bonne pratique consiste à utiliser des builds multi-étapes : nous utilisons l’image la plus volumineuse et la plus générique pour construire nos logiciels, puis nous copions les artefacts générés dans notre version slim pour le déploiement en production. Nous n’avons ainsi pas à gérer les dépendances de build, tout en profitant de la taille réduite et du nombre inférieur de vulnérabilités de la version slim. Nous devrions également utiliser des versions d’exécution précises, afin de savoir exactement quel environnement d’exécution nous utilisons et d’éviter qu’il ne change sans que nous le sachions.

Bonnes pratiques pour choisir des images de base

Voici quelques recommandations générales pour choisir vos images de base.

  • Faites confiance à un fournisseur en amont pour gérer les tâches les plus lourdes et corriger les vulnérabilités.  Ses équipes sont plus importantes et ont donc bien plus de chances de corriger rapidement les problèmes.

  • Épinglez vos applications à des images avec une version précise — au minimum la version majeure, mais probablement aussi la version mineure. Vous éviterez ainsi que la situation change sans prévenir.

  • Adoptez les builds multi-étapes. Vous pourrez ainsi déployer des images slim tout en profitant de combinaisons éprouvées lors du build.

  • Reconstruisez souvent. Cela vous permettra souvent d’intégrer des correctifs de sécurité pendant le processus de build.

  • Pensez à effectuer des mises à niveau de temps en temps. Les nouvelles versions apportent également des correctifs de sécurité.

Analysez toujours vos images à la recherche de vulnérabilités

En analysant vos images et vos Dockerfiles avec Snyk, vous pouvez découvrir quelles images de base alternatives choisir pour réduire le nombre total de vulnérabilités. Snyk peut également créer automatiquement des pull requests dans vos Dockerfiles pour remplacer l’image de base. Et le meilleur dans tout ça : vous pouvez l’utiliser gratuitement.

Tableau de bord de sécurité des conteneurs affichant les détails d’une image Docker et des recommandations pour mettre à niveau l’image de base, avec le nombre de vulnérabilités par niveau de gravité.

Dans la deuxième partie de cette série d’articles, nous examinerons les logiciels que nous ajoutons nous-mêmes aux images de base et verrons comment commencer à corriger les vulnérabilités qu’ils présentent. Revenez bientôt ou suivez @snyksec sur Twitter pour être informé de sa publication.

La sécurité des conteneurs, pensée pour les développeurs

Snyk détecte et corrige automatiquement les vulnérabilités dans les images de conteneurs et les workloads Kubernetes.