Chapitre 1118 minIntermédiaire
Sécuriser Docker : images, secrets, rootless et socket
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- décrire les risques propres aux conteneurs et à l'accès au démon ;
- durcir un conteneur : utilisateur, capacités, lecture seule, limites ;
- analyser une image avec Trivy ou Docker Scout et traiter le rapport ;
- gérer les secrets et installer Docker en mode rootless.
4 exercices corrigés
Sécuriser Docker, c'est protéger trois choses : l'hôte (qui ne doit jamais tomber à cause d'un conteneur), les images (qui ne doivent contenir ni faille connue ni secret) et les conteneurs en marche (qui ne doivent avoir que les droits dont ils ont besoin). Ce chapitre décrit les risques propres aux conteneurs, explique pourquoi le socket Docker est la pièce la plus sensible, durcit un conteneur, analyse les images avec Trivy et Docker Scout, gère les secrets, puis présente le mode rootless.
Les risques d'un hôte Docker
| Risque | Principe | Conséquence |
|---|---|---|
| Évasion de conteneur | Une faille du noyau, ou une configuration trop permissive (conteneur privilégié, montage de /), permet à un processus de sortir de son isolation | Le processus agit sur l'hôte, souvent avec les droits root, et atteint tous les autres conteneurs |
| Accès au démon | Le socket Docker, ou l'API exposée sur le réseau sans authentification, permet de lancer n'importe quel conteneur | Prise de contrôle totale de l'hôte |
| Image piégée | Une image communautaire, ou au nom imitant une image connue (typosquatting), contient un mineur de cryptomonnaie ou une porte dérobée | Le code malveillant tourne dans l'entreprise |
| Image vulnérable | L'image contient des paquets avec des failles connues (CVE) jamais corrigées | Une faille publique exploitable dans un service exposé |
| Secret exposé | Mot de passe ou clé dans une image, une variable d'environnement ou un fichier versionné | Accès à la base, au registre, au cloud |
| Port publié par erreur | Un port publié contourne le pare-feu local (chapitre 2) | Un service d'administration ou une base exposés au réseau |
| Déni de service | Un conteneur sans limite consomme toute la mémoire, le processeur ou les numéros de processus | Tous les services de l'hôte tombent |
Le socket Docker : la clé de l'hôte
Le démon tourne en root et obéit à quiconque peut écrire sur son socket /var/run/docker.sock. Or demander au démon « lance un conteneur qui monte la racine de l'hôte » est une requête parfaitement normale pour lui. Accès au socket = root sur l'hôte. Trois situations courantes en découlent :
- les membres du groupe
dockersont desrootsans mot de passe (chapitre 2) ; - monter le socket dans un conteneur (
-v /var/run/docker.sock:/var/run/docker.sock), comme le demandent Traefik, Portainer, certains agents de supervision ou de CI, donne l'hôte à ce conteneur : si une faille le compromet, l'attaquant tient l'hôte. Le monter avec:rone change rien ::roempêche de modifier le fichier socket, pas d'envoyer des requêtes à l'API à travers lui ; - exposer l'API du démon sur le réseau (port TCP 2375, sans chiffrement ni authentification) revient à offrir l'hôte à tout le réseau : des robots parcourent Internet à la recherche de ce port ouvert pour y lancer des conteneurs de minage de cryptomonnaie.
Les parades :
| Parade | Comment |
|---|---|
| Ne jamais exposer l'API sur le réseau | Garder le socket Unix seul ; pour piloter un hôte à distance, passer par SSH (docker -H ssh://admin@srv-gestion ps, ou un contexte docker context create) |
Limiter le groupe docker | Seulement les administrateurs nominatifs du serveur |
| Éviter de monter le socket | Préférer un reverse proxy configuré par fichier (Caddy, Nginx) à un proxy qui lit l'API |
| Si un outil l'exige vraiment | Le faire passer par un proxy de socket qui n'autorise que les requêtes en lecture nécessaires ; ne jamais exposer ce conteneur sur Internet |
| Détecter | Surveiller les créations de conteneurs privilégiés et les docker exec (Wazuh, chapitre 10) |
Durcir un conteneur
Chaque conteneur ne doit avoir que les droits dont il a besoin. Voici le service api de sorbier-frais, durci :
services:
api:
image: ghcr.io/sorbier/sorbier-frais:2.4.0
user: "1000:1000"
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
pids_limit: 200
deploy:
resources:
limits:
memory: 512M
cpus: "1.0"
| Réglage | Effet |
|---|---|
user: "1000:1000" | Le processus ne tourne pas en root, même si l'image l'oublie |
read_only: true et tmpfs | Le système de fichiers du conteneur est en lecture seule : un attaquant ne peut ni déposer d'outil ni modifier le code ; /tmp reste inscriptible, en mémoire |
cap_drop: [ALL] | Retire toutes les capacités Linux ; une API web n'en a besoin d'aucune. On rajoute au cas par cas avec cap_add (par exemple NET_BIND_SERVICE pour ouvrir un port inférieur à 1024 sous un utilisateur ordinaire) |
no-new-privileges:true | Interdit de gagner des droits en cours de route (programmes setuid comme su ou sudo) |
pids_limit: 200 | Limite le nombre de processus : une boucle de création de processus ne bloque pas l'hôte |
deploy.resources.limits | Mémoire et processeur plafonnés par les cgroups ; au-delà de 512 Mo, le noyau tue le processus du conteneur, pas les autres services |
Et ce qu'il ne faut jamais faire sans une raison précise et documentée :
| Option | Pourquoi c'est dangereux |
|---|---|
--privileged (privileged: true) | Rend toutes les capacités, l'accès à tous les périphériques de l'hôte, désactive les protections : le conteneur est quasiment l'hôte |
-v /:/hote ou montage de /etc, /root | Le conteneur lit et modifie les fichiers de l'hôte |
--pid host, --network host | Supprime l'isolation des processus ou du réseau |
-v /var/run/docker.sock:… | Donne le contrôle du démon |
Analyser les images avec Trivy et Docker Scout
Une image contient un système de base et des dépendances, chacun avec ses failles connues. Un scanner d'images liste les paquets de l'image (son SBOM, la nomenclature logicielle) et les compare aux bases de vulnérabilités publiques.
Trivy, outil libre d'Aqua Security, s'installe sur un poste ou un serveur depuis le dépôt de son éditeur, ou s'utilise en conteneur. Analyse l'image de l'API en ne gardant que les failles graves pour lesquelles un correctif existe :
trivy image --severity HIGH,CRITICAL --ignore-unfixed ghcr.io/sorbier/sorbier-frais:2.4.0
Le rapport donne, pour chaque faille, le paquet concerné (Library), l'identifiant CVE (Vulnerability), la gravité (Severity), la version installée et la version qui corrige (Fixed Version). Avec --exit-code 1, Trivy se termine en erreur s'il trouve quelque chose : c'est ce qui fait échouer un pipeline (chapitre 12). Trivy cherche aussi les secrets oubliés dans les couches, et trivy config . vérifie les Dockerfile (conteneur en root, étiquette latest, ADD d'une URL…). Il n'analyse pas les fichiers compose.yaml : ceux-là se relisent avec la liste des réflexes de sécurité de l'aide-mémoire.
Docker Scout est intégré à Docker Desktop ; sur un serveur Linux avec Docker Engine, son extension de la commande docker ne fait pas partie des paquets du chapitre 2 et s'installe à part (dépôt GitHub docker/scout-cli). Il demande aussi un compte Docker (docker login) :
docker scout quickview ghcr.io/sorbier/sorbier-frais:2.4.0
docker scout cves --only-severity critical,high ghcr.io/sorbier/sorbier-frais:2.4.0
docker scout recommendations ghcr.io/sorbier/sorbier-frais:2.4.0
quickview résume le nombre de failles par gravité, en distinguant celles qui viennent de l'image de base ; cves les liste ; recommendations propose une image de base plus récente ou plus légère.
Que faire d'un rapport ?
- Mettre à jour l'image de base et reconstruire : la plupart des failles viennent du système de base et disparaissent avec une image récente.
- Mettre à jour les dépendances de l'application (
npm audit, Dependabot). - Réduire l'image :
slim,alpineou image durcie (chapitre 4). Moins de paquets, moins de failles. - Pour une faille sans correctif, évaluer si le paquet est réellement utilisé, documenter l'acceptation du risque, et surveiller.
Les secrets
Un secret (mot de passe, clé d'API, clé privée) ne doit apparaître ni dans l'image, ni dans le dépôt Git, ni dans les variables d'environnement si on peut l'éviter. Les variables d'environnement sont en effet lisibles par docker inspect, transmises aux processus enfants, et souvent recopiées dans les journaux d'erreur.
| Méthode | Où le secret est visible | Verdict |
|---|---|---|
ENV ou ARG dans le Dockerfile, COPY .env | Dans l'image, pour toujours, chez tous ceux qui la récupèrent | Interdit |
En clair dans compose.yaml | Dans Git, dans docker inspect | Interdit |
.env lu par Compose, passé par environment: | Fichier sur le serveur (à protéger), docker inspect, environnement du processus | Acceptable en développement |
Secret Compose (secrets:) | Un fichier monté en lecture seule dans /run/secrets/<nom>, absent de docker inspect et de l'environnement | Recommandé sur un serveur |
| Gestionnaire de secrets (HashiCorp Vault, coffre du cloud) | Récupéré par l'application au démarrage, avec traçabilité et rotation | Pour les organisations plus grandes |
Les secrets Compose sont détaillés et mis en œuvre au chapitre 9 : un fichier par secret sur l'hôte, lisible seulement par root et par l'utilisateur du conteneur, déclaré sous la clé secrets: et lu par l'application dans /run/secrets/. Les images officielles de MySQL, MariaDB et PostgreSQL acceptent les variables terminées par _FILE pour lire leurs mots de passe ainsi.
Le mode rootless
En mode rootless, le démon Docker et les conteneurs tournent sous un utilisateur ordinaire, grâce aux espaces de noms utilisateurs : le root d'un conteneur correspond, sur l'hôte, à un numéro d'utilisateur sans aucun privilège. Une évasion de conteneur ne donne alors que les droits de cet utilisateur, et l'accès au socket n'est plus un accès root.
Installation pour un utilisateur dédié, svc-gestion, sur un hôte où Docker Engine est installé :
sudo apt install uidmap docker-ce-rootless-extras
sudo loginctl enable-linger svc-gestion
Puis, connecté en tant que svc-gestion (par une vraie session, ssh par exemple, pas su) :
dockerd-rootless-setuptool.sh install
systemctl --user enable --now docker
docker info --format '{{.SecurityOptions}}'
Le script crée un service systemd propre à l'utilisateur et un contexte rootless pour la commande docker. loginctl enable-linger permet au service de démarrer avec la machine, sans session ouverte. docker info doit afficher rootless parmi les options de sécurité. L'utilisateur a besoin d'au moins 65 536 identifiants subordonnés dans /etc/subuid et /etc/subgid (créés automatiquement pour les nouveaux comptes sur Debian).
Les limites à connaître :
| Limite | Contournement |
|---|---|
| Pas de port inférieur à 1024 sur l'hôte (80, 443) | Abaisser le seuil (net.ipv4.ip_unprivileged_port_start=80 dans /etc/sysctl.d/), ou rediriger les ports avec le pare-feu de l'hôte |
| Réseau un peu plus lent (traduction en espace utilisateur) | Acceptable pour la plupart des applications de gestion |
| Limites de ressources (cgroups) | Fonctionnent avec cgroups v2 et systemd, le cas de Debian 13 |
| Données dans le dossier de l'utilisateur | ~/.local/share/docker à sauvegarder et à surveiller |
Garder l'ensemble à jour
| Quoi | Comment | Quand |
|---|---|---|
| Le système de l'hôte et Docker Engine | apt upgrade ; live-restore évite l'arrêt des conteneurs | Chaque semaine, et sans attendre pour une faille critique |
| Les images de base | Reconstruire les images de l'entreprise, même sans changement de code | Chaque semaine (tâche planifiée de la CI) |
| Les images tierces (MySQL, Caddy) | docker compose pull && docker compose up -d sur une étiquette qui suit les correctifs (mysql:8.4) | Chaque mois, ou à chaque alerte |
| L'inventaire | Liste des images en service et de leur version (docker compose images), versionnée avec le déploiement | En continu |
Les références : CIS et ANSSI
Deux références pour aller plus loin et justifier tes choix dans un dossier d'épreuve :
- le CIS Docker Benchmark, une liste de contrôles détaillés (configuration de l'hôte, du démon, des images, des conteneurs), que l'outil libre Docker Bench for Security vérifie automatiquement ;
- la fiche technique de l'ANSSI « Recommandations de sécurité relatives au déploiement de conteneurs Docker » (2020), qui insiste sur le cloisonnement, le durcissement de l'hôte et la limitation des privilèges.
Exercices
Exercice 1 sur 4 Analyse de risques type E7 Réussi À revoir
Lors d'un audit de srv-gestion, tu relèves les points suivants. Pour chacun, indique le risque, les critères de sécurité menacés (disponibilité, intégrité, confidentialité) et la mesure corrective.
- Le conteneur
portainer, publié sur9000:9000, monte/var/run/docker.sock. - Le conteneur
apiest lancé avecprivileged: true« parce que sinon ça ne marchait pas ». - Le fichier
compose.yaml, versionné dans un dépôt GitHub public, contientMYSQL_ROOT_PASSWORD: Sorbier2026. - Aucun conteneur n'a de limite de mémoire.
Voir la réponseMasquer la réponse de l'exercice 1
- Risque : Portainer pilote le démon par le socket ; quiconque accède à son interface (exposée sur tout le réseau, en HTTP) ou exploite une faille de Portainer contrôle l'hôte entier. Critères : confidentialité, intégrité et disponibilité de tout le serveur. Mesures : retirer Portainer, ou le restreindre au réseau d'administration, derrière le reverse proxy en HTTPS avec authentification forte, et à jour ; ne jamais l'exposer au réseau des postes.
- Risque : un conteneur privilégié a presque tous les droits de l'hôte ; une faille de l'API devient une prise de contrôle du serveur. Critères : C, I, D. Mesures : retirer
privileged, identifier le besoin réel (souvent une capacité précise, ou un problème de droits sur un dossier) et l'accorder seul (cap_add, propriétaire du dossier) ; tester. - Risque : le mot de passe
rootde la base est public, et il reste dans l'historique Git même si on le retire. Critère : confidentialité (puis intégrité des données). Mesures : changer immédiatement le mot de passe, passer à des secrets Compose hors du dépôt, vérifier les journaux de connexion de la base ; l'historique du dépôt doit être nettoyé, mais le secret reste considéré comme compromis. - Risque : un conteneur qui fuit ou une attaque par saturation consomme toute la mémoire ; le noyau tue des processus au hasard, y compris la base. Critère : disponibilité. Mesures :
deploy.resources.limits(mémoire, processeur) etpids_limitsur chaque service, supervision de la consommation.
Tu avais trouvé ?
Exercice 2 sur 4 Le socket en lecture seule Réussi À revoir
Un collègue affirme : « Je monte le socket avec -v /var/run/docker.sock:/var/run/docker.sock:ro, donc le conteneur ne peut que lire l'état de Docker. » Qu'en penses-tu ?
Voir la réponseMasquer la réponse de l'exercice 2
C'est faux. :ro interdit de modifier le fichier socket (le supprimer, changer ses droits), mais un socket ne se « lit » pas comme un fichier : on s'y connecte pour envoyer des requêtes à l'API. Avec ce montage, le conteneur peut toujours demander au démon de créer un conteneur privilégié qui monte la racine de l'hôte. Le seul moyen de limiter l'API à la lecture est un proxy de socket qui filtre les requêtes, ou mieux, de ne pas donner l'accès au socket.
Tu avais trouvé ?
Exercice 3 sur 4 Durcir un service Réussi À revoir
Complète la définition de ce service pour qu'il tourne sous l'utilisateur 101, sans aucune capacité Linux, avec un système de fichiers en lecture seule (sauf /tmp et /var/cache/nginx, en mémoire), sans possibilité de gagner des privilèges, limité à 256 Mo de mémoire et 100 processus.
services:
front:
image: ghcr.io/sorbier/sorbier-frais-front:2.4.0
Voir la réponseMasquer la réponse de l'exercice 3
services:
front:
image: ghcr.io/sorbier/sorbier-frais-front:2.4.0
user: "101:101"
read_only: true
tmpfs:
- /tmp
- /var/cache/nginx
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
pids_limit: 100
deploy:
resources:
limits:
memory: 256M
Teste ensuite le service : docker compose up -d front puis docker compose logs front. Si Nginx se plaint de ne pas pouvoir écrire un fichier, ajoute ce dossier précis en tmpfs plutôt que de retirer read_only.
Tu avais trouvé ?
Exercice 4 sur 4 Lire un rapport de scan Réussi À revoir
Trivy signale sur l'image de l'API 2 failles critiques et 14 hautes. 15 d'entre elles concernent des paquets du système de base Debian, avec une « Fixed Version » disponible ; une concerne une dépendance npm, sans correctif publié. Que proposes-tu ?
Voir la réponseMasquer la réponse de l'exercice 4
- Les 15 failles du système de base ont un correctif : reconstruire l'image sur une image de base à jour (
docker build --pull, qui force le téléchargement de la dernièrenode:24-slim), relancer Trivy pour le vérifier, puis déployer la nouvelle version. Si elles reviennent souvent, envisager une base plus petite (image durcie, oualpinesi l'application le permet). - La faille npm sans correctif : vérifier si la fonction vulnérable est réellement utilisée par l'API, chercher une version ou une bibliothèque de remplacement ; à défaut, documenter l'acceptation du risque (qui décide, jusqu'à quand), ajouter des mesures de réduction (l'API n'est joignable que par le proxy, en lecture seule, sans capacités) et surveiller la publication d'un correctif.
- Ajouter au pipeline une étape Trivy avec
--exit-code 1sur les failles critiques corrigeables, et une reconstruction hebdomadaire de l'image.
Tu avais trouvé ?