12k
All articles

Sécuriser Docker sur une machine Linux

L’accès au groupe Docker donne les privilèges root. Sécurisez Docker sous Linux avec le mode sans privilèges, sudo, Podman, le durcissement des conteneurs et une checklist.

OpenReplay Team
OpenReplay Team
Sécuriser Docker sur une machine Linux

Ajouter un utilisateur au groupe docker lui confère les droits root sur la machine. Pas « presque root », mais root au sens plein, car quiconque peut communiquer avec le socket Docker peut monter le système de fichiers de l’hôte dans un conteneur et le modifier.

Vous avez exécuté usermod -aG docker $USER pour ne plus avoir à taper sudo, et c’est le guide de post-installation de Docker lui-même qui vous l’a conseillé. Cette même page de post-installation de Docker Engine pour Linux avertit pourtant, dans un encadré que la plupart des lecteurs survolent, que l’appartenance à ce groupe équivaut à des droits root. Si vous découvrez encore les bases, le guide du débutant sur les images et conteneurs Docker les présente. Cet article démontre le risque en une seule commande. Il détaille ensuite les correctifs, du moins robuste au plus robuste, et se termine par une checklist pour auditer une machine dont vous avez hérité.

Points clés

  • Tout membre du groupe docker, ainsi que tout processus exécuté sous son identité, peut obtenir un shell root sur l’hôte avec docker run --rm -it -v /:/host alpine chroot /host sh.
  • Docker rootless exécute à la fois dockerd et vos conteneurs dans un espace de noms utilisateur (user namespace). Une compromission du démon ou une évasion de conteneur aboutit alors à un UID non privilégié sur l’hôte, et non à root.
  • Une règle sudoers restreinte à /usr/bin/docker reste équivalente à root, mais elle impose un mot de passe avant chaque utilisation de Docker et journalise chaque appel.
  • Podman fonctionne sans démon et s’exécute par défaut en mode rootless lorsqu’il est lancé par un utilisateur standard. Son CLI étant largement compatible avec celui de Docker, alias docker=podman couvre la plupart des usages quotidiens.
  • Sur un hôte hérité, vérifiez qui appartient au groupe docker, quel utilisateur exécute dockerd, les permissions du socket et si le démon écoute sur le port TCP 2375.

Pourquoi le groupe docker donne-t-il les droits root ?

Le groupe docker équivaut à root parce que le démon Docker s’exécute en tant que root et que ce groupe contrôle l’accès en écriture à son socket Unix. Pouvoir écrire sur ce socket, c’est pouvoir ordonner n’importe quoi à un processus root. Pour comprendre les mécanismes sous-jacents, le guide des permissions de fichiers sous Linux explique comment les permissions de groupe d’un fichier déterminent qui peut l’ouvrir. Dans le cas de Docker, un seul détail compte : le socket est accessible en écriture au groupe docker.

En voici la preuve :

docker run --rm -it -v /:/host alpine chroot /host sh

Cette commande monte la racine / de l’hôte dans le conteneur (bind mount), puis effectue un chroot dedans. Vous disposez alors d’un shell root sur le véritable système de fichiers de l’hôte. À partir de là, vous pouvez modifier /etc/shadow ou /etc/sudoers, ou encore ajouter des clés SSH dans /root. Aucun mot de passe ni aucun exploit n’est nécessaire. N’importe quel script, dépendance ou extension d’éditeur compromise s’exécutant sous votre utilisateur peut en faire autant. La documentation Docker aborde ce point dans la section surface d’attaque du démon Docker.

Mettre en place Docker rootless

Le mode rootless exécute dockerd et l’ensemble des conteneurs dans un espace de noms utilisateur appartenant à votre compte, ce qui en fait la solution directe au problème du groupe docker. Le socket se trouve dans $XDG_RUNTIME_DIR/docker.sock et non dans /var/run/docker.sock. C’est RootlessKit qui crée l’espace de noms. Les utilitaires setuid newuidmap et newgidmap associent l’UID 0 de l’espace de noms à votre propre UID, et associent une plage définie dans /etc/subuid et /etc/subgid aux autres UID des conteneurs. La couche réseau repose sur une pile réseau en espace utilisateur, comme slirp4netns, gvisor-tap-vsock ou pasta.

Installez les prérequis, puis vérifiez vos plages d’identifiants subordonnés. La documentation du mode rootless exige une plage d’au moins 65 536 identifiants subordonnés pour votre utilisateur, dans /etc/subuid comme dans /etc/subgid.

sudo apt-get install -y uidmap docker-ce-rootless-extras
grep ^$(whoami): /etc/subuid /etc/subgid

Désactivez le démon root, puis lancez l’outil de configuration avec votre utilisateur standard :

sudo systemctl disable --now docker.service docker.socket
dockerd-rootless-setuptool.sh install
systemctl --user enable --now docker
sudo loginctl enable-linger $USER
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock

La ligne enable-linger maintient votre démon actif après votre déconnexion. Une fois Docker rootless opérationnel, retirez-vous de l’ancien groupe avec sudo gpasswd -d $USER docker. La documentation officielle du mode rootless couvre les autres distributions. Si le client ne parvient plus à joindre le démon, le guide de dépannage des erreurs « Docker daemon not running » détaille les vérifications à effectuer sur le socket et sur systemd.

Le mode rootless nécessite des espaces de noms utilisateur non privilégiés. Ubuntu 24.04 et versions ultérieures les restreignent par défaut via AppArmor. Le paquet apparmor d’Ubuntu fournit déjà le profil requis par rootlesskit : une installation de docker-ce-rootless-extras via apt fonctionne donc sans étape supplémentaire. Si vous passez plutôt par le script get.docker.com/rootless, vous devez ajouter ce profil vous-même. Ne désactivez pas cette restriction à l’échelle du système avec kernel.apparmor_restrict_unprivileged_userns=0. La page de dépannage du mode rootless couvre les autres cas d’échec.

Quelles sont les contreparties du mode rootless ?

Voici les principaux compromis par rapport à un démon rootful :

LimitationSolution de contournement
Impossible par défaut de se lier aux ports inférieurs à 1024sysctl net.ipv4.ip_unprivileged_port_start=80, ou un reverse proxy
--privileged n’accorde aucun privilège réel sur l’hôteSeules les capabilities au sein de l’espace de noms utilisateur s’appliquent
Le réseau en espace utilisateur est plus lent que le pilote bridgeLe pilote expérimental lxc-user-nic, ou des benchmarks avant la mise en production
Les noyaux antérieurs à 5.11 nécessitent fuse-overlayfsLes noyaux 5.11+ prennent en charge overlay2 nativement

La page de dépannage en mentionne quelques autres, comme l’absence de prise en charge d’AppArmor, des checkpoints et des réseaux overlay.

Utiliser sudo avec une règle restreinte

Exiger sudo pour utiliser Docker, plutôt que l’appartenance au groupe docker, constitue un compromis lorsque le mode rootless ne convient pas à votre charge de travail. Cette approche reste équivalente à root, puisque sudo docker permet d’exécuter la même commande chroot. Vous y gagnez en revanche une demande de mot de passe et une entrée dans le journal d’audit pour chaque appel. Le code qui s’exécute silencieusement sous votre utilisateur n’a plus accès au socket.

sudo gpasswd -d alice docker
sudo visudo -f /etc/sudoers.d/docker
alice ALL=(root) /usr/bin/docker

Cette règle n’autorise que le binaire Docker, et non des commandes arbitraires. Consultez l’historique d’utilisation avec journalctl _COMM=sudo. La documentation de post-installation décrit également une méthode basée sur newgrp, qui place un mot de passe de groupe devant Docker tout en conservant l’exécution du CLI sous votre propre utilisateur.

Passer à Podman

Podman fonctionne sans démon et, lorsqu’il est exécuté par un utilisateur standard, il est rootless par défaut. Les conteneurs s’exécutent comme des processus enfants de votre utilisateur, au sein d’un espace de noms utilisateur, en s’appuyant sur les mêmes correspondances /etc/subuid. Son CLI reproduit celui de Docker suffisamment fidèlement pour que la plupart des commandes fonctionnent telles quelles.

sudo apt-get install -y podman
alias docker=podman
podman run --rm -p 8080:80 docker.io/library/nginx

Les différences se situent à la marge : noms d’images pleinement qualifiés, prise en charge de Compose et outils qui s’attendent à trouver un socket Docker. Testez vos scripts avant de faire migrer toute une équipe.

Durcir le conteneur lui-même

Quel que soit le démon utilisé, limitez ce que le processus peut faire à l’intérieur du conteneur. Quatre paramètres couvrent l’essentiel du risque : un USER non root, aucune capability au-delà de celles dont l’application a besoin, un système de fichiers racine en lecture seule et aucune élévation de privilèges via des binaires setuid.

FROM node:22-alpine
WORKDIR /app
COPY --chown=node:node . .
USER node
CMD ["node", "server.js"]
docker run -d \
  --cap-drop=ALL \
  --read-only --tmpfs /tmp \
  --security-opt no-new-privileges \
  -p 8080:8080 myapp

L’instruction USER du Dockerfile définit l’UID utilisé à l’exécution. --cap-drop=ALL supprime toutes les capabilities. Si une application en a réellement besoin d’une, rajoutez-la avec --cap-add. Cette application écoute sur le port 8080 et n’en nécessite donc aucune. La combinaison de --read-only et --tmpfs fait de /tmp le seul emplacement accessible en écriture. no-new-privileges empêche les binaires setuid d’élever les privilèges. Toutes ces options sont documentées dans la référence de docker run.

Auditer une machine héritée

Sur un VPS configuré par quelqu’un d’autre, répondez à quatre questions : qui appartient au groupe docker, quel utilisateur exécute dockerd, qui peut écrire sur le socket, et le démon écoute-t-il sur le réseau ?

getent group docker
ps -eo user,cmd | grep [d]ockerd
ls -l /var/run/docker.sock
sudo ss -ltnp | grep -E ':2375|:2376'
systemctl cat docker | grep -- '-H'
grep -s hosts /etc/docker/daemon.json

Chaque nom affiché par getent dispose d’un accès root. Si ps indique root, le démon est rootful. Le socket doit afficher srw-rw---- root docker ; tout bit d’écriture accordé à tous les utilisateurs doit être corrigé immédiatement. Un processus en écoute sur le port 2375 signifie que l’API Docker est exposée sans TLS. Les versions actuelles de Docker Engine refusent de démarrer avec cette configuration, mais un moteur plus ancien sur un hôte hérité peut encore l’utiliser. Quiconque peut joindre ce port obtient les droits root. Un point d’écoute TLS sur le port 2376 est plus sûr, mais toute personne détenant les clés client dispose toujours des droits root. Suivez le guide de protection du socket du démon pour passer à SSH ou à TLS mutuel. Recherchez également les conteneurs qui montent le socket, car chacun d’eux constitue une porte d’accès root :

docker ps -q | xargs -r docker inspect \
  --format '{{.Name}} {{range .Mounts}}{{.Source}} {{end}}' | grep docker.sock

L’accès au socket Docker est un accès root, un point c’est tout. Commencez par exécuter getent group docker sur chaque machine que vous utilisez. Ensuite, migrez ces utilisateurs vers Docker rootless ou Podman, ou placez le socket derrière une règle sudo journalisée.

FAQ

Quelle est la différence entre Docker rootless et userns-remap ?

Avec userns-remap, dockerd continue de s'exécuter en tant que root et seuls les conteneurs s'exécutent sous des UID subordonnés remappés, par exemple ceux de l'utilisateur dockremap créé lorsque userns-remap est défini sur 'default' dans /etc/docker/daemon.json. Cela ne comble pas la faille du groupe docker : un membre du groupe peut passer --userns=host pour désactiver le remappage sur un conteneur donné et exécuter l'évasion par chroot. Le mode rootless, lui, exécute le démon lui-même sous votre utilisateur.

Docker rootless et Docker rootful peuvent-ils cohabiter sur la même machine ?

Oui. Les deux démons utilisent des sockets distincts : /var/run/docker.sock pour le mode rootful et $XDG_RUNTIME_DIR/docker.sock pour le mode rootless. L'exécution de dockerd-rootless-setuptool.sh install crée un contexte CLI nommé 'rootless' et bascule dessus. Utilisez docker context use default pour cibler le démon root et docker context use rootless pour revenir au mode rootless. Tant que le démon rootful reste actif, quiconque peut écrire sur son socket dispose toujours des droits root.

Mes images et conteneurs existants sont-ils repris par Docker rootless ?

Non. Le démon rootless utilise son propre répertoire de données, ~/.local/share/docker par défaut, distinct du répertoire /var/lib/docker utilisé par le démon root. Les images, conteneurs et volumes créés sous Docker rootful ne lui sont pas visibles. Récupérez à nouveau les images, ou exportez-les avec docker save depuis le démon rootful, puis importez-les avec docker load dans le démon rootless. Mettez à jour les tâches de sauvegarde qui ne couvrent que /var/lib/docker.

Pourquoi les fichiers écrits par des conteneurs rootless ont-ils des propriétaires inattendus sur l'hôte ?

Docker rootless fait correspondre les UID des conteneurs à des UID de l'hôte. Root à l'intérieur du conteneur correspond à votre propre UID : les fichiers qu'il écrit dans un bind mount vous appartiennent donc. Tous les autres UID du conteneur sont associés à votre plage /etc/subuid. Avec une plage commençant à 100000, l'UID 1000 du conteneur devient l'UID 100999 sur l'hôte, que votre utilisateur ne peut pas modifier directement. Exécuter un chown vers 0:0 à l'intérieur d'un conteneur vous rend la propriété des fichiers.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.