Bonnes pratiques pour durcir les images Docker et réduire la surface d'attaque
Durcissez les images Docker en utilisant des utilisateurs non root, des bases plus petites, des constructions multi-étapes, une gestion des secrets et des analyses de vulnérabilités.
Bonnes pratiques pour durcir les images Docker et réduire la surface d'attaque
Durcir les images Docker commence par une question simple : qu'y a-t-il dans cette image dont votre application n'a pas réellement besoin ? Les utilisateurs supplémentaires, les shells, les gestionnaires de paquets, les outils de construction et les secrets divulgués augmentent tous les dégâts qu'un conteneur compromis peut causer.
Utilisez ces pratiques lorsque vous écrivez ou révisez un Dockerfile, surtout avant qu'une image n'atteigne un registre partagé ou un cluster de production.
Exécuter les conteneurs en tant qu'utilisateurs non root
L'un des principes de sécurité les plus fondamentaux est le principe du moindre privilège. Par défaut, les processus dans un conteneur Docker s'exécutent en tant qu'utilisateur root. Cela leur accorde des privilèges étendus, qui peuvent être exploités par des attaquants si le conteneur est compromis. Exécuter votre application en tant qu'utilisateur non root réduit considérablement les dégâts potentiels qu'un attaquant peut infliger dans le conteneur.
Créer un utilisateur non root
Vous pouvez créer un nouvel utilisateur et un nouveau groupe dans votre Dockerfile, puis basculer vers cet utilisateur avant d'exécuter votre application.
# Utiliser un runtime Python officiel comme image parente
FROM python:3.9-slim
# Définir le répertoire de travail
WORKDIR /app
# Copier le contenu du répertoire actuel dans le conteneur à /app
COPY . /app
# Installer les paquets nécessaires spécifiés dans requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Créer un utilisateur et un groupe non root
RUN addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 --ingroup appgroup appuser
# Basculer vers l'utilisateur non root
USER appuser
# Rendre le port 80 disponible à l'extérieur de ce conteneur
EXPOSE 80
# Définir la variable d'environnement
ENV NAME World
# Exécuter app.py au lancement du conteneur
CMD ["python", "app.py"]
Considérations pour les utilisateurs non root
- Permissions : Assurez-vous que l'utilisateur non root dispose des permissions de lecture et d'écriture nécessaires pour les répertoires et fichiers requis par votre application. Vous devrez peut-être utiliser
chownpour définir la propriété de manière appropriée. - Liaison de port : Les utilisateurs non root ne peuvent généralement lier que des ports supérieurs à 1024. Si votre application doit se lier à un port privilégié (par exemple, 80 ou 443), envisagez d'utiliser un proxy inverse (comme Nginx ou Traefik) s'exécutant sur l'hôte ou dans un autre conteneur avec les permissions appropriées, ou configurez les capacités Linux.
Minimiser les paquets et dépendances installés
Chaque paquet installé dans votre image Docker augmente sa taille et, plus important encore, sa surface d'attaque. Chaque paquet peut avoir ses propres vulnérabilités que les attaquants peuvent exploiter. Par conséquent, il est crucial de n'inclure que ce qui est absolument nécessaire.
Bonnes pratiques pour la gestion des paquets :
- Utiliser des images de base minimales : Envisagez des images
slim, distroless ou basées sur Alpine lorsqu'elles correspondent à votre environnement d'exécution. Les images plus petites ont tendance à inclure moins de paquets, mais testez toujours la compatibilité car Alpine utilise musl libc et peut se comporter différemment des images Debian ou Ubuntu. - Nettoyer après l'installation : Après avoir installé des paquets, nettoyez le cache du gestionnaire de paquets ou les fichiers temporaires. Cela réduit non seulement la taille de l'image, mais supprime également les zones de stockage potentielles pour les attaquants.
# Exemple pour les images basées sur Debian/Ubuntu RUN apt-get update && apt-get install -y --no-install-recommends some-package && \ rm -rf /var/lib/apt/lists/* # Exemple pour les images basées sur Alpine RUN apk add --no-cache some-package - Constructions multi-étapes : C'est une technique puissante pour garder votre image finale légère. Vous utilisez une étape pour construire votre application (installation d'outils de construction, compilateurs, etc.) et une deuxième étape propre pour copier uniquement les artefacts nécessaires de l'étape de construction. Cela empêche les dépendances de construction de se retrouver dans votre image de production.
# --- Étape de construction --- FROM golang:1.18-alpine AS builder WORKDIR /app COPY . . RUN go build -o myapp # --- Étape de production --- FROM alpine:latest WORKDIR /app COPY --from=builder /app/myapp . CMD ["./myapp"] - Mettre à jour régulièrement les dépendances : Gardez vos dépendances d'application et vos images de base à jour pour intégrer les correctifs de sécurité.
Implémenter des vérifications de santé robustes
Les vérifications de santé sont cruciales pour surveiller l'état de vos conteneurs. Docker peut utiliser ces vérifications pour déterminer si un conteneur fonctionne correctement et pour redémarrer ou supprimer automatiquement les conteneurs défaillants. Une vérification de santé bien définie aide à garantir que votre application est non seulement en cours d'exécution, mais aussi réactive et fonctionne comme prévu.
Définir des vérifications de santé
Une instruction HEALTHCHECK dans votre Dockerfile spécifie une commande que Docker exécutera périodiquement à l'intérieur du conteneur pour tester son état de santé. Si la commande se termine avec un statut non nul, le conteneur est considéré comme défaillant.
# Exemple pour une application web
FROM nginx:latest
# ... autres instructions ...
# Vérifier si le processus Nginx est en cours d'exécution et écoute sur le port 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:80/ || exit 1
# ... autres instructions ...
Bonnes pratiques pour les vérifications de santé :
- Gardez-les simples : La commande de vérification de santé doit être légère et rapide à exécuter. Évitez les logiques complexes qui pourraient ralentir la vérification ou introduire leurs propres points de défaillance.
- Testez les fonctionnalités clés : La vérification devrait idéalement tester la fonctionnalité principale de votre application, pas seulement si un processus est en cours d'exécution. Pour un serveur web, cela pourrait signifier vérifier s'il peut répondre à une requête HTTP de base.
- Configurez
start-period: Pour les applications qui prennent du temps à s'initialiser, utilisez l'optionstart-periodpour leur donner le temps de démarrer avant que les vérifications de santé ne commencent à échouer.
Gérer de manière sécurisée les secrets et données sensibles
N'intégrez jamais de secrets tels que des clés API, des mots de passe ou des certificats directement dans votre Dockerfile ou votre image. Ces secrets feront partie de la couche de l'image et seront facilement découvrables. Utilisez plutôt les secrets Docker ou les variables d'environnement gérées par votre plateforme d'orchestration (comme Kubernetes ou Docker Swarm) pour les informations sensibles.
Secrets Docker en mode Swarm
Docker Swarm fournit un mécanisme natif pour gérer les secrets. Vous pouvez créer des secrets et les monter en tant que fichiers dans les conteneurs.
# Créer un secret
docker secret create my_api_key api_key.txt
# Déployer un service en utilisant le secret
docker service create --secret my_api_key my_web_app
Variables d'environnement avec prudence
Bien que les variables d'environnement soient pratiques, elles sont également visibles lors de l'inspection d'un conteneur en cours d'exécution (docker inspect). Utilisez-les pour les données de configuration non sensibles. Pour les données sensibles, les secrets Docker ou les systèmes de gestion de secrets externes sont préférés.
Utiliser des tags d'image spécifiques
Lorsque vous référencez des images de base ou d'autres images dans votre Dockerfile (par exemple, FROM ubuntu:latest), utilisez toujours des tags de version spécifiques au lieu de latest. L'utilisation de latest peut entraîner des constructions imprévisibles, car le tag latest peut changer avec le temps, introduisant potentiellement des changements cassants ou même des vulnérabilités de sécurité à votre insu.
# Évitez ceci :
# FROM ubuntu:latest
# Préférez ceci :
FROM ubuntu:22.04
Scanner les images pour les vulnérabilités
Scannez régulièrement vos images Docker pour les vulnérabilités connues. Plusieurs outils peuvent vous aider, à la fois dans votre pipeline CI/CD et dans votre registre.
Outils de scan populaires
- Trivy : Un scanner de vulnérabilités simple et complet pour les conteneurs. Il scanne les paquets OS et les dépendances d'application.
trivy image your-image-name:tag - Clair : Un outil d'analyse statique open-source pour détecter les vulnérabilités dans les images de conteneurs.
- Docker Scout : Un service de Docker qui analyse les images de conteneurs pour les vulnérabilités et fournit des recommandations.
Intégrer ces scans dans votre processus de construction garantit que vous êtes conscient des problèmes de sécurité potentiels et que vous pouvez les résoudre avant de déployer vos images.
Comprendre les couches d'image
Les images Docker sont construites en couches. Lorsque vous apportez une modification à votre Dockerfile, une nouvelle couche est créée. Comprendre comment fonctionnent les couches peut vous aider à optimiser votre Dockerfile à la fois pour la taille et la sécurité. Placez les instructions qui changent moins fréquemment (comme l'installation de paquets de base) plus tôt dans le Dockerfile, et les instructions qui changent plus fréquemment (comme la copie du code de l'application) plus tard. Cela utilise efficacement le cache de construction de Docker et peut accélérer les constructions.
Plus important encore pour la sécurité, les informations sensibles ou les expositions accidentelles dans les couches antérieures peuvent persister. Assurez-vous que tous les fichiers ou commandes sensibles sont traités de manière à ne pas rester dans les couches finales de l'image s'ils ne sont plus nécessaires.
Faire du durcissement une routine
Commencez par les Dockerfiles qui sont envoyés en production. Supprimez les outils de construction des images finales, exécutez en tant qu'utilisateur non root, épinglez les images de base et scannez chaque construction. Ensuite, traitez le durcissement des images comme une partie normale de la revue de code, et non comme un nettoyage ponctuel.