Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

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

RisquePrincipeConséquence
Évasion de conteneurUne faille du noyau, ou une configuration trop permissive (conteneur privilégié, montage de /), permet à un processus de sortir de son isolationLe processus agit sur l'hôte, souvent avec les droits root, et atteint tous les autres conteneurs
Accès au démonLe socket Docker, ou l'API exposée sur le réseau sans authentification, permet de lancer n'importe quel conteneurPrise de contrôle totale de l'hôte
Image piégéeUne image communautaire, ou au nom imitant une image connue (typosquatting), contient un mineur de cryptomonnaie ou une porte dérobéeLe code malveillant tourne dans l'entreprise
Image vulnérableL'image contient des paquets avec des failles connues (CVE) jamais corrigéesUne 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 erreurUn port publié contourne le pare-feu local (chapitre 2)Un service d'administration ou une base exposés au réseau
Déni de serviceUn conteneur sans limite consomme toute la mémoire, le processeur ou les numéros de processusTous 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 docker sont des root sans 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 :ro ne change rien : :ro empê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 :

ParadeComment
Ne jamais exposer l'API sur le réseauGarder 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 dockerSeulement les administrateurs nominatifs du serveur
Éviter de monter le socketPréférer un reverse proxy configuré par fichier (Caddy, Nginx) à un proxy qui lit l'API
Si un outil l'exige vraimentLe 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étecterSurveiller 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 :

yaml
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églageEffet
user: "1000:1000"Le processus ne tourne pas en root, même si l'image l'oublie
read_only: true et tmpfsLe 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:trueInterdit de gagner des droits en cours de route (programmes setuid comme su ou sudo)
pids_limit: 200Limite le nombre de processus : une boucle de création de processus ne bloque pas l'hôte
deploy.resources.limitsMé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 :

OptionPourquoi 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, /rootLe conteneur lit et modifie les fichiers de l'hôte
--pid host, --network hostSupprime 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 :

bash
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) :

bash
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 ?

  1. 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.
  2. Mettre à jour les dépendances de l'application (npm audit, Dependabot).
  3. Réduire l'image : slim, alpine ou image durcie (chapitre 4). Moins de paquets, moins de failles.
  4. 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éthodeOù le secret est visibleVerdict
ENV ou ARG dans le Dockerfile, COPY .envDans l'image, pour toujours, chez tous ceux qui la récupèrentInterdit
En clair dans compose.yamlDans Git, dans docker inspectInterdit
.env lu par Compose, passé par environment:Fichier sur le serveur (à protéger), docker inspect, environnement du processusAcceptable en développement
Secret Compose (secrets:)Un fichier monté en lecture seule dans /run/secrets/<nom>, absent de docker inspect et de l'environnementRecommandé sur un serveur
Gestionnaire de secrets (HashiCorp Vault, coffre du cloud)Récupéré par l'application au démarrage, avec traçabilité et rotationPour 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é :

bash
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) :

bash
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 :

LimiteContournement
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

QuoiCommentQuand
Le système de l'hôte et Docker Engineapt upgrade ; live-restore évite l'arrêt des conteneursChaque semaine, et sans attendre pour une faille critique
Les images de baseReconstruire les images de l'entreprise, même sans changement de codeChaque 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'inventaireListe des images en service et de leur version (docker compose images), versionnée avec le déploiementEn 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.

  1. Le conteneur portainer, publié sur 9000:9000, monte /var/run/docker.sock.
  2. Le conteneur api est lancé avec privileged: true « parce que sinon ça ne marchait pas ».
  3. Le fichier compose.yaml, versionné dans un dépôt GitHub public, contient MYSQL_ROOT_PASSWORD: Sorbier2026.
  4. Aucun conteneur n'a de limite de mémoire.
Voir la réponseMasquer la réponse de l'exercice 1
  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.
  2. 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.
  3. Risque : le mot de passe root de 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.
  4. 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) et pids_limit sur 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.

yaml
services:
  front:
    image: ghcr.io/sorbier/sorbier-frais-front:2.4.0
Voir la réponseMasquer la réponse de l'exercice 3
yaml
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
  1. 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ère node: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, ou alpine si l'application le permet).
  2. 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.
  3. Ajouter au pipeline une étape Trivy avec --exit-code 1 sur les failles critiques corrigeables, et une reconstruction hebdomadaire de l'image.

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Docker dans la CI/CD : construire, publier et déployer une imageRevoir le sommaire du cours