Docker pour les développeurs Java : 5 conseils pour ne pas compromettre votre sécurité
20 novembre 2020
0 minutes de lectureDocker est la méthode la plus utilisée pour conteneuriser vos applications. Avec Docker Hub, il est facile de créer et de récupérer des images préconstruites. C’est très pratique : vous pouvez utiliser ces images Docker Hub pour créer rapidement une image pour votre application Java. Cependant, créer des images Docker personnalisées pour votre application Java sans précautions comporte de nombreux risques de sécurité. Alors, comment intégrer la sécurité au cœur de Docker pour les développeurs Java ?
Avant d’expliquer comment créer une image Docker Java de qualité pour votre application, examinons quelques questions fréquentes sur le sujet.
Comment conteneuriser des applications Java avec Docker ?
Vous pouvez exécuter votre application Java dans un conteneur Docker en copiant simplement le fichier .jar ou .war directement dans une image de base JRE. Mais quelques points sont à prendre en compte. Choisir les bons arguments JVM et adapter les paramètres d’exécution du conteneur ne représente que la moitié du travail. Le choix de l’image de base est essentiel du point de vue de la sécurité, car un mauvais choix peut introduire des vulnérabilités.
Cet article vous aidera à mieux comprendre l’impact du choix de l’image de base et à trouver l’image la plus sécurisée pour votre application.
En quoi Docker est-il utile aux développeurs Java ?
Empaqueter votre application Java dans un conteneur vous permet de définir l’application complète — JRE, paramètres de configuration, dépendances au niveau du système d’exploitation et artefacts de build — sous forme d’artefacts autonomes déployables appelés images de conteneur. Ces images sont définies sous forme de code, ce qui permet de les recréer de manière parfaitement reproductible et aux développeurs d’exécuter la même plateforme dans tous les environnements. Enfin, les conteneurs permettent aux développeurs de tester plus facilement de nouvelles versions de plateforme ou d’autres modifications directement sur leur poste, sans autorisations spéciales.
Choisissez la bonne image de base Docker pour votre application Java
Pour créer une image Docker, nous la basons sur une image que nous récupérons depuis Docker Hub. C’est ce qu’on appelle l’image de base. Elle constitue le fondement de la nouvelle image que vous allez créer pour votre application Java. L’image de base choisie est essentielle, car elle vous permet d’utiliser tout ce qu’elle contient. Mais cela a un coût : si une image de base présente une vulnérabilité, celle-ci se retrouvera aussi dans l’image que vous créez.
Dans les images de base, bon nombre de vulnérabilités se trouvent dans la couche du système d’exploitation (OS). Dans notre étude de 2019, Renforcer la sécurité Docker dès le début du cycle de développement, nous avons montré que les vulnérabilités introduites par cette couche peuvent varier considérablement selon la distribution choisie.

Rapport 2019 – Renforcer la sécurité Docker dès le début du cycle de développement
Prenons un ensemble populaire d’images de base Docker Java d’Adoptopenjdk, openjdk11. Avec la balise par défaut, cette image est basée sur une distribution Ubuntu. Vous pouvez toutefois choisir des balises correspondant à des versions précises basées, par exemple, sur Debian, Centos ou Alpine (à noter : Alpine n’est pas basée sur glibc et peut ne pas être compatible avec les applications qui effectuent des appels JNI natifs).

Le choix de la bonne image de base est donc essentiel pour la sécurité. Vous n’avez probablement pas besoin de tous les binaires fournis avec un système d’exploitation complet. Pour créer l’image Docker Java de votre application, mieux vaut partir d’une image de base minimale. Les binaires que vous n’avez pas ne peuvent pas vous nuire.
Outre l’aspect sécurité, une image de base minimale réduit la taille de l’image que vous créez. Une image Docker plus petite occupe également moins d’espace et démarre probablement plus vite. Vous pouvez aussi utiliser jib, qui crée une image Java minimale sans nécessiter de Dockerfile.
Utilisez un JRE, pas un JDK
Lors de la création d’une image Docker, nous devons lui attribuer uniquement les ressources nécessaires à son bon fonctionnement. Commencez donc par utiliser un environnement d’exécution Java (JRE) adapté à votre image de production, plutôt que le kit de développement Java complet (JDK). De plus, votre image de production ne doit pas inclure de système de build comme Maven ou Gradle. Le résultat du build — votre fichier jar, par exemple — devrait suffire.
Même si vous souhaitez créer votre application dans un conteneur Docker, vous pouvez facilement séparer l’image de build de l’image de production à l’aide d’un build en plusieurs étapes.
Par exemple :Je veux créer une image Docker Java pour mon application java-code-workshop. Il s’agit d’une application basée sur Spring Boot, compilée avec Maven et qui nécessite Java 8.
Voici une méthode simpliste pour créer cette image Docker Java :

J’ai choisi une image de base qui inclut Maven et OpenJDK 8, copié mon code source dans l’image, puis appelé Maven pour compiler et exécuter mon application. Cet exemple fonctionne très bien : mon application démarre et s’exécute sans problème. Toutefois, l’image Docker que je viens de créer pèse 631 Mo.
Modifions ce Dockerfile et utilisons un build en plusieurs étapes :

Je continue à utiliser l’image maven-openjdk8 pour compiler mon projet. Mais ce n’est pas l’image qui sera générée. Je crée une nouvelle image basée sur une image JRE Java 8 bien plus petite et n’y copie que le jar exécutable de Spring Boot. Il ne me reste plus qu’à exécuter le jar-file, et le tour est joué ! L’image Docker obtenue ne contient ni le JDK ni Maven, seulement le JRE. Sa taille passe ainsi à 132 Mo.
Les images plus petites sont non seulement plus faciles à envoyer et permettent de gagner du temps au démarrage, mais elles sont aussi beaucoup plus sûres. Imaginez ce qui pourrait se passer si, pour une raison ou une autre, un attaquant accédait à un conteneur en cours d’exécution contenant le JDK, votre code source et un outil de build.
Cette méthode est également utile lorsque vous devez inclure des secrets pour accéder à un dépôt privé. Vous ne voulez pas que ces secrets se retrouvent dans le cache de votre image de production. Comme l’image de build n’est pas utilisée en production, vous pouvez tout à fait y utiliser les secrets. Cette technique vous permet de sélectionner les éléments dont vous avez besoin dans d’autres images et de créer une image Docker finale qui ne contient que les ressources nécessaires.
N’exécutez pas votre conteneur Docker en tant que root
Par défaut, un conteneur Docker s’exécute en tant que root. C’est pratique en développement, mais à éviter dans vos images de production. Si, pour une raison ou une autre, un attaquant accède à un terminal ou peut exécuter du code, il dispose de privilèges importants sur le conteneur en cours d’exécution et peut aussi potentiellement accéder aux systèmes de fichiers de l’hôte via des montages de volumes avec des droits d’accès trop élevés.
Le moyen le plus simple d’éviter cela consiste à créer un utilisateur dédié, comme ici :

À la troisième ligne, je crée un groupe et y ajoute un utilisateur. Cet utilisateur système (-r) n’a ni mot de passe ni répertoire personnel. Je l’ajoute également au groupe que je viens de créer.
Ensuite, à la ligne 6, j’accorde à l’utilisateur des droits sur le dossier de l’application. N’oubliez pas la ligne 7 : j’y définis l’utilisateur à utiliser. La commande de la dernière ligne est ainsi exécutée par le nouvel utilisateur aux droits restreints.
Analysez votre image Docker et votre application Java pendant le développement
Créer une image Docker à partir d’un Dockerfile, et même en reconstruire une, peut introduire de nouvelles vulnérabilités dans votre système. L’analyse de vos images Docker pendant le développement devrait faire partie de votre workflow afin de détecter les vulnérabilités le plus tôt possible.
Vous pouvez facilement analyser votre image Docker avec la CLI Snyk. Utilisez-la sur votre machine locale, dans votre pipeline ou les deux. Après avoir installé et authentifié la CLI Snyk, il vous suffit d’exécuter cette commande pour analyser une image :
Pour analyser une image adoptopenjdk, comme dans la première section, les commandes seraient les suivantes :
Résultat :

Vous pouvez tester et surveiller l’image Docker. Pour la surveiller, utilisez snyk container monitor <image>. La surveillance prend un instantané et vérifie au fil du temps si de nouvelles vulnérabilités ou des correctifs sont disponibles pour votre image.
Lorsque vous analysez une image et disposez du Dockerfile (vous avez créé une image Docker Java), ajoutez l’option --Dockerfile=<dockerfile> à snyk container test ou à snyk container monitor. Vous obtiendrez ainsi des conseils de correction plus pertinents. Par exemple, vous serez informé si une image de base disponible permet de réduire le nombre de vulnérabilités.
Exemple :
Analysez votre application Java
L’image Docker Java que vous créez contient également votre application, qui constitue évidemment une cible d’attaque potentielle. Pour faire de Docker un choix sûr pour les développeurs Java, vous devez vous assurer dès le départ que votre application Java ne présente aucune vulnérabilité. Imaginez que votre application contienne une bibliothèque permettant l’exécution de code à distance lors d’un appel à un point de terminaison REST. Même si le reste de votre image ne présente aucune vulnérabilité, les conséquences peuvent être désastreuses.
La majeure partie du code binaire Java de votre image Docker provient probablement de code importé. Les bibliothèques et frameworks utilisés par votre application en sont des dépendances. La CLI Snyk permet de les vérifier facilement. Il s’agit de la même CLI que celle utilisée précédemment pour analyser notre image. Exécutez snyk test ou snyk monitor depuis votre dossier racine pour analyser ou surveiller les vulnérabilités de vos bibliothèques.
Pour le code que vous avez écrit, il est judicieux d’utiliser un outil d’analyse de code ou un linter comme SonarLint, PMD ou spotbugs. Ces outils généralistes aident à produire un code de meilleure qualité et à éviter les erreurs de sécurité les plus courantes.
Reconstruisez régulièrement
Créez votre application Java pour votre image Docker de manière à pouvoir la supprimer et la reconstruire à tout moment. Si vous constatez un problème avec votre conteneur en cours d’exécution, vous devriez pouvoir simplement l’arrêter et lancer une nouvelle instance. Pour cela, concevez des applications Java sans état, dont les données sont stockées en dehors du conteneur. Voici quelques points à prendre en compte :
n’exécutez pas de base de données dans votre conteneur ;
ne stockez pas de fichiers (journaux) dans votre conteneur ;
assurez-vous que votre cache se rétablit automatiquement, le cas échéant.
Si vous concevez votre application de sorte à pouvoir la supprimer et lancer une nouvelle instance à tout moment, vous pouvez également reconstruire l’image Docker entière en toute sécurité. Saviez-vous que pour 20 % des images Docker vulnérables, il suffit de reconstruire l’image pour corriger un ou plusieurs problèmes de sécurité ? Dans de nombreux cas, les images Docker sont basées sur la balise « latest » d’une image de base. Cette balise « latest » évolue au fil du temps et est remplacée par des versions plus récentes et améliorées. Il en va de même pour les binaires essentiels installés dans votre conteneur à l’aide de gestionnaires de paquets comme apt ou yum. Utiliser la dernière version est bien sûr préférable pour la sécurité, car vous bénéficiez automatiquement des derniers correctifs. Il faut toutefois tenir compte du fait que votre image de base évoluera avec le temps, ce qui rendra plus difficile la recréation de votre image dans un état précis.
Même si votre application n’a pas changé, reconstruisez régulièrement votre image Docker, éventuellement avec une balise de version plus récente ou la balise latest de l’image de base. Les améliorations des couches sous-jacentes, comme celle du système d’exploitation, peuvent améliorer la qualité de votre image et réduire les vulnérabilités.
https://www.youtube.com/watch?v=v2SkWn-ZRDg
Pour résumer, si vous souhaitez appliquer les bonnes pratiques de sécurité et créer des images Docker optimales, de manière générale ou pour des applications Java :
10 bonnes pratiques pour créer un conteneur Java avec Docker – un guide détaillé, étape par étape, pour créer des images Docker sécurisées et performantes pour vos applications Java
10 bonnes pratiques de sécurité Java – les pratiques de sécurité à suivre pour créer des applications Java dans n’importe quel environnement.
Lancez-vous dans les compétitions Capture The Flag
Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.
