infra / docker
Docker rootless
Explication
Ce que vous allez apprendre
- Comprendre pourquoi le démon Docker tournant en root reste un angle mort de sécurité
- Comprendre comment les user namespaces permettent à un conteneur de croire tourner en root sans l'être réellement côté hôte
- Installer et vérifier une installation Docker en mode rootless
- Connaître les limitations pratiques du mode rootless (réseau,
--privileged, ports privilégiés) - Identifier les contextes où le rootless est particulièrement recommandé (CI, multi-utilisateurs)
Dans quel contexte ?
Une plateforme d'intégration continue partagée exécute des jobs de nombreuses équipes différentes, dont certaines exécutent du code non maîtrisé (des dépendances tierces, par exemple). Donner un accès root au démon Docker à chaque job serait un risque de sécurité majeur : un job malveillant ou compromis pourrait potentiellement compromettre toute la machine hôte partagée. En passant les runners en mode rootless, chaque job reste confiné aux droits d'un utilisateur ordinaire, même en cas de compromission complète du démon.
Un angle mort de la sécurité classique
Même en appliquant toutes les bonnes pratiques vues dans la leçon sur la sécurité des conteneurs (utilisateur non-root à l'intérieur du conteneur, capacités réduites...), un point reste souvent ignoré : le démon Docker lui-même (dockerd), celui qui orchestre tous les conteneurs, tourne par défaut avec les droits root sur la machine hôte. Si un attaquant parvient à s'évader d'un conteneur et à atteindre ce démon, il hérite potentiellement des pleins pouvoirs sur toute la machine.
| Mode | Démon Docker tourne en | Risque en cas de compromission |
|---|---|---|
| Standard | root | Accès root potentiel à l'hôte entier |
| Rootless | Utilisateur ordinaire | Limité aux droits de cet utilisateur |
Prérequis
Cette leçon suppose que tu es à l'aise avec les bonnes pratiques de sécurité des conteneurs (utilisateur non-root, capabilities) vues dans une leçon précédente : le rootless va plus loin, au niveau du démon lui-même.
L'idée du mode rootless
Le mode rootless va plus loin que la simple bonne pratique du conteneur non-root : il fait tourner le DÉMON DOCKER LUI-MÊME sous un utilisateur ordinaire, sans aucun privilège spécial. Même en cas de compromission totale du démon, l'attaquant reste limité aux droits de cet utilisateur ordinaire sur la machine hôte — une différence de nature, pas seulement de degré.
Comment un conteneur "root" peut tourner sans vrai root
Cela peut sembler paradoxal : à l'intérieur d'un conteneur rootless, l'utilisateur "root" (uid 0) existe toujours et fonctionne normalement pour l'application. Le tour de passe-passe repose sur les user namespaces : ce root interne est en réalité mappé vers un identifiant utilisateur non privilégié et différent sur la machine hôte. L'application croit avoir les pleins pouvoirs dans son bac à sable, alors que côté hôte, ce n'est qu'un utilisateur ordinaire parmi d'autres.
Le prix à payer
Ce niveau de sécurité supplémentaire a un coût : le réseau rootless passe par une couche logicielle supplémentaire (slirp4netns) plus lente que l'accès réseau natif habituel, et certaines opérations bas niveau (comme utiliser --privileged) restent limitées par nature, puisqu'un utilisateur non privilégié ne peut jamais accorder plus de droits qu'il n'en possède lui-même.
Un cas d'usage particulièrement adapté
Les environnements où plusieurs utilisateurs ou équipes partagent une même infrastructure — notamment les runners d'intégration continue — sont le terrain d'élection du mode rootless : ils évitent de devoir accorder un accès root au démon Docker à des jobs potentiellement non fiables.
Commandes & code
Docker rootless
# Par défaut, le démon Docker tourne en ROOT — un conteneur compromis avec une faille
# d'évasion peut potentiellement obtenir les droits root sur l'HÔTE.
# Le mode rootless fait tourner le démon ET les conteneurs sous un utilisateur non privilégié.# --- Installation du mode rootless ---
curl -fsSL https://get.docker.com/rootless | sh
# Ajoute les variables d'environnement nécessaires au shell (affiché à la fin de l'installation)
export PATH=/home/ned/bin:$PATH
export DOCKER_HOST=unix:///run/user/1000/docker.sock
systemctl --user start docker # démarre le démon rootless comme service UTILISATEUR
systemctl --user enable docker # démarrage automatique à la connexion
loginctl enable-linger ned # permet au démon de tourner même sans session ouverte
# --- Vérifier que tout tourne bien en non-root ---
docker info | grep -i rootless # confirme le mode actif
ps -eo user,pid,cmd | grep dockerd # le process dockerd tourne sous "ned", pas "root"
docker run --rm alpine id # à l'intérieur, root du conteneur = uid mappé, pas root réel
# --- Comment ça fonctionne : user namespaces + rootlesskit ---
# root DANS le conteneur (uid 0) est mappé vers un uid NON privilégié sur l'hôte (ex: 100000)
cat /etc/subuid | grep ned # plage d'UID déléguée à cet utilisateur pour le mapping
cat /etc/subgid | grep ned # idem pour les GID# --- Limitations à connaître avant d'adopter le rootless en prod ---
# - Pas de --network host natif de la même façon (nécessite slirp4netns ou VPNKit, plus lent)
# - --privileged limité : un conteneur rootless ne peut pas obtenir plus de droits que son utilisateur hôte
# - Certains ports < 1024 nécessitent une configuration supplémentaire (cap_net_bind_service déléguée)
# - Performance réseau légèrement réduite par la couche slirp4netns (userspace networking)# --- Autoriser les ports privilégiés (< 1024) en rootless ---
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80
# Permanent : ajouter "net.ipv4.ip_unprivileged_port_start = 80" dans /etc/sysctl.d/99-rootless.conf
# --- Choisir un réseau plus performant que le slirp4netns par défaut ---
dockerd-rootless-setuptool.sh install --skip-iptables
sudo apt install -y slirp4netns fuse-overlayfs # fuse-overlayfs = storage driver adapté au rootless
# --- Vérifier le storage driver utilisé (overlay2 nécessite root, fuse-overlayfs ne l'exige pas) ---
docker info | grep -i "storage driver"
# --- Migrer une installation Docker root existante vers rootless (ou l'inverse) ---
dockerd-rootless-setuptool.sh install
sudo systemctl disable --now docker # désactive le démon root pour éviter les conflits de socket
# --- Rootless dans un pipeline CI (gitlab-runner par exemple) ---
# Le runner CI lui-même tourne déjà en non-root -> rootless devient la seule option viable sans
# donner d'accès root au job, réduisant fortement la surface d'attaque d'un job CI compromisRésumé
- Le mode rootless fait tourner
dockerdET les conteneurs sous un utilisateur non privilégié, via des user namespaces (uid 0 du conteneur ≠ root hôte). slirp4netns/fuse-overlayfsremplacent les mécanismes bas niveau qui nécessitaient normalement root, au prix d'un léger surcoût de performance.- Particulièrement adapté aux runners CI et environnements multi-utilisateurs où donner un accès root au démon Docker est inacceptable.
Exercices pratiques
Mission : sécuriser un runner CI partagé entre équipes non fiables
Objectif : Migrer un démon Docker vers le mode rootless et comprendre ses limitations pratiques.
Contexte
Une plateforme CI partagée exécute des jobs de code non maîtrisé venant de plusieurs équipes. Le démon Docker tourne actuellement en root sur la machine hôte : une compromission d'un seul job pourrait potentiellement affecter toute la machine.