Contactez-nous

Sécurité des images Docker

Apprenez à construire des images Docker sécurisées : choix des bases, scan de vulnérabilités (CVE), minimisation, Dockerfile sécurisé et signature d'images.

Introduction : l'image, fondation de la sécurité des conteneurs

Les images Docker sont les plans ou les modèles à partir desquels vos conteneurs sont créés. Elles contiennent le système de fichiers, les bibliothèques, les dépendances et le code de votre application. Par conséquent, la sécurité de vos conteneurs en cours d'exécution dépend intrinsèquement de la sécurité des images sur lesquelles ils sont basés. Une vulnérabilité présente dans une image sera répliquée dans chaque conteneur lancé à partir de celle-ci, multipliant ainsi les risques.

La sécurisation des images Docker n'est donc pas une étape optionnelle, mais une pratique fondamentale qui doit être intégrée dès le début du cycle de vie de développement logiciel (DevSecOps). Elle implique une série de bonnes pratiques allant du choix judicieux de l'image de base à l'analyse continue des vulnérabilités, en passant par la minimisation de la surface d'attaque et l'écriture de Dockerfiles sécurisés.

Ce chapitre explore en détail les stratégies et les outils essentiels pour construire et maintenir des images Docker aussi sûres que possible, réduisant ainsi considérablement les risques avant même que les conteneurs ne soient déployés.

Choisir judicieusement : l'importance des images de base

Toute image Docker commence par une instruction `FROM` qui spécifie une image de base. Le choix de cette image de base a un impact considérable sur la sécurité et la taille de votre image finale. Une image de base volumineuse ou non maintenue peut introduire des centaines de vulnérabilités connues (CVEs - Common Vulnerabilities and Exposures) avant même que vous n'ajoutiez votre propre code.

Privilégiez systématiquement les images officielles fournies par Docker Hub ou directement par les éditeurs de logiciels (par exemple, `python`, `node`, `nginx`). Ces images sont généralement bien maintenues et font l'objet de mises à jour de sécurité régulières. Méfiez-vous des images provenant de sources inconnues ou non vérifiées.

Optez pour des variantes d'images de base minimalistes lorsque c'est possible. Les tags comme `alpine` (basé sur Alpine Linux, très léger), `slim`, ou `distroless` (images de Google ne contenant que l'application et ses dépendances runtime, sans gestionnaire de paquets ni shell) réduisent considérablement la surface d'attaque en n'incluant que le strict nécessaire. Moins il y a de composants, moins il y a de vulnérabilités potentielles.

Vérifiez la fraîcheur et la politique de maintenance de l'image de base. Utilisez des tags spécifiques (par exemple, `python:3.11-slim`) plutôt que `latest`, qui peut changer sans préavis et introduire des régressions ou de nouvelles vulnérabilités. Intégrez la mise à jour régulière de vos images de base dans votre processus de maintenance.

Scanner pour prévenir : l'analyse de vulnérabilités

Même en choisissant une bonne image de base, des vulnérabilités peuvent être présentes ou introduites via les paquets et dépendances que vous ajoutez. L'analyse de vulnérabilités (scanning) des images Docker est donc une étape incontournable. Elle consiste à examiner les différentes couches de l'image pour identifier les composants logiciels (paquets OS, bibliothèques applicatives) et à les comparer à des bases de données de CVEs connues.

De nombreux outils open-source et commerciaux permettent d'effectuer ces scans :

  • Trivy : Un scanner de vulnérabilités populaire, simple et rapide, qui analyse les paquets OS (Alpine, Debian, Ubuntu, CentOS...) et les dépendances applicatives (npm, pip, Maven, Go...).
  • Grype : Un autre scanner open-source performant, développé par Anchore, avec des fonctionnalités similaires à Trivy.
  • Clair : Un projet open-source de la CoreOS (maintenant Red Hat) souvent utilisé comme moteur de scan dans les registries privés.
  • Docker Scout : Le service d'analyse intégré de Docker (disponible avec les abonnements Docker) qui fournit des analyses de vulnérabilités, des informations sur la supply chain logicielle et des recommandations directement dans Docker Hub et Docker Desktop.

L'intégration du scan de vulnérabilités dans votre pipeline CI/CD est une bonne pratique essentielle. Scannez vos images après chaque build. Vous pouvez configurer votre pipeline pour échouer si des vulnérabilités critiques ou de haute sévérité sont détectées, empêchant ainsi le déploiement d'images compromises.

Les registries d'images modernes (Docker Hub, ECR, GCR, ACR, Harbor...) intègrent souvent des fonctionnalités de scan automatique lors du push d'une image. Utilisez ces fonctionnalités pour une surveillance continue. Comprenez les rapports de scan, priorisez la correction des vulnérabilités les plus sévères et mettez en place un processus de remédiation (mise à jour des paquets, changement de base image).

Minimalisme et multi-stage builds : réduire la surface d'attaque

Le principe de moindre privilège s'applique aussi aux images : n'incluez que ce qui est strictement nécessaire à l'exécution de votre application. Chaque paquet, bibliothèque ou outil supplémentaire augmente potentiellement la surface d'attaque.

Les builds multi-étapes (multi-stage builds) sont une technique fondamentale pour atteindre cet objectif. Ils permettent d'utiliser une image de build plus complète (avec compilateurs, outils de build, SDK) dans une première étape pour compiler votre application ou installer des dépendances, puis de copier uniquement les artefacts résultants (l'exécutable compilé, les dépendances finales) dans une image finale minimaliste (basée sur Alpine, distroless, ou même `scratch` pour les binaires statiques).

# Etape 1: Build avec une image complète (ex: Go)
FROM golang:1.21 as builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# Construit un binaire statique
RUN CGO_ENABLED=0 go build -o myapp .

# Etape 2: Runtime avec une image minimale (scratch ou alpine)
FROM scratch
# Ou FROM alpine:latest
WORKDIR /app
# Copie UNIQUEMENT le binaire depuis l'étape 'builder'
COPY --from=builder /app/myapp .
# (Optionnel pour Alpine: installer les certificats CA si besoin)
# RUN apk --no-cache add ca-certificates

# Point d'entrée pour exécuter l'application
ENTRYPOINT ["/app/myapp"]

Cette approche garantit que l'image finale ne contient aucun outil de build, code source intermédiaire ou dépendance de développement, la rendant beaucoup plus légère et sécurisée. Supprimez également les gestionnaires de paquets (`apt`, `apk`, `yum`) de l'image finale si possible, ou nettoyez leur cache (`rm -rf /var/cache/apk/*`) pour réduire la taille.

Dockerfile sécurisé : bonnes pratiques d'écriture

Le Dockerfile lui-même doit être écrit en gardant la sécurité à l'esprit :

  • Utilisateur non-root (`USER`) : C'est l'une des recommandations les plus importantes. Evitez d'exécuter vos processus applicatifs en tant que `root` dans le conteneur. Utilisez l'instruction `USER` pour passer à un utilisateur non privilégié avant l'exécution de votre application (`CMD` ou `ENTRYPOINT`). Vous devrez peut-être créer cet utilisateur et groupe au préalable et ajuster les permissions sur les fichiers/répertoires nécessaires.
    # ... après avoir installé l'application
    RUN addgroup -S appgroup && adduser -S appuser -G appgroup
    USER appuser
    
    CMD ["node", "server.js"]
  • `COPY` vs `ADD` : Préférez `COPY` à `ADD` pour copier des fichiers et répertoires locaux. `ADD` possède des fonctionnalités supplémentaires (téléchargement depuis URL, extraction automatique d'archives) qui peuvent introduire des risques de sécurité (téléchargement de fichiers malveillants, vulnérabilités Zip Slip). N'utilisez `ADD` que si vous avez spécifiquement besoin de ces fonctionnalités, et avec précaution.
  • Gestion des secrets pendant le build : Ne copiez jamais de fichiers de secrets (clés privées, mots de passe) directement dans l'image et ne les passez pas via `ARG` ou `ENV` car ils resteraient visibles dans les couches de l'image ou l'historique. Utilisez les mécanismes de secrets de build de BuildKit :
    # Nécessite BuildKit (docker buildx build ... ou DOCKER_BUILDKIT=1)
    RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret > /etc/secret-file
    # Lancer le build avec : docker build --secret id=mysecret,src=mysecret.txt .
    Ou utilisez des builds multi-étapes pour que les secrets utilisés pendant le build ne se retrouvent pas dans l'image finale.
  • Minimiser le nombre de couches : Bien que moins critique pour la sécurité que pour la taille, regrouper des commandes `RUN` logiques peut réduire le nombre de couches et simplifier l'analyse.
  • Utiliser `HEALTHCHECK` : Définir une instruction `HEALTHCHECK` permet à Docker (et aux orchestrateurs) de surveiller l'état de santé réel de l'application dans le conteneur, améliorant ainsi la fiabilité et la gestion des déploiements.
  • Labels de métadonnées : Utilisez l'instruction `LABEL` pour ajouter des métadonnées utiles (mainteneur, lien vers le dépôt source, version, licence) qui peuvent aider à la traçabilité et à la gestion des images.

Signature et vérification d'images : garantir l'authenticité

Comment être sûr que l'image que vous téléchargez et exécutez est bien celle publiée par l'auteur légitime et qu'elle n'a pas été modifiée en transit ? La signature d'images apporte une réponse à ce problème d'intégrité et d'authenticité.

Docker Content Trust (DCT), basé sur le projet Notary, permet aux développeurs de signer numériquement leurs images lorsqu'ils les poussent vers un registry. Les utilisateurs peuvent ensuite configurer leur démon Docker pour vérifier ces signatures avant de télécharger ou d'exécuter une image.

Pour activer la vérification côté client, exportez la variable d'environnement `export DOCKER_CONTENT_TRUST=1`. Avec cette variable activée, le client Docker refusera de télécharger ou d'exécuter des images non signées ou dont la signature est invalide (pour les tags signés).

La mise en place de la signature par les éditeurs d'images demande un effort initial (gestion des clés de signature), mais elle fournit une garantie forte contre la falsification des images. Encouragez l'utilisation d'images signées lorsque c'est possible et activez la vérification dans vos environnements de production.

Gestion continue : dépendances et cycle de vie

La sécurité des images n'est pas une tâche ponctuelle. De nouvelles vulnérabilités sont découvertes chaque jour. Il est essentiel d'avoir un processus pour gérer les dépendances et le cycle de vie de vos images.

Mise à jour des dépendances : Intégrez des outils d'analyse de composition logicielle (SCA - Software Composition Analysis) pour surveiller les vulnérabilités dans les dépendances directes et transitives de votre application (bibliothèques npm, pip, Maven, etc.). Utilisez des fichiers de verrouillage (lock files comme `package-lock.json`, `Pipfile.lock`, `yarn.lock`) pour garantir des builds reproductibles et mettez régulièrement à jour ces dépendances.Reconstruction régulière : Planifiez la reconstruction régulière de vos images, même si votre code applicatif n'a pas changé. Cela permet d'incorporer les dernières mises à jour de sécurité de l'image de base et des paquets OS. Intégrez cela à votre pipeline CI/CD.Politique de rétention : Définissez une politique pour les images obsolètes ou vulnérables dans vos registries afin d'éviter leur utilisation accidentelle.

En adoptant une approche multicouche – choix de bases saines, minimisation, scan de vulnérabilités, Dockerfiles sécurisés, signature et maintenance continue – vous pouvez améliorer considérablement la posture de sécurité de vos applications conteneurisées dès leur fondation : l'image Docker.