Aller au contenu principal
DockerSécuritéDevSecOps

Sécurité Docker : rootless et isolation

30 min de lecture Apprendre Docker — Chapitre 9

Sécurise tes conteneurs avec le mode rootless, Seccomp et AppArmor pour une isolation maximale.

Un conteneur Docker partage le kernel de l’hôte. C’est sa force — performance, légèreté — mais aussi son talon d’Achille. Si un attaquant s’échappe du conteneur, il atterrit directement sur ta machine. Et si le process tournait en root… c’est game over.

Dans ce cours, tu vas apprendre à verrouiller tes conteneurs avec trois mécanismes complémentaires : le mode rootless, les filtres seccomp et les profils AppArmor. Trois couches de défense qui, combinées, réduisent drastiquement ta surface d’attaque.

Pourquoi la sécurité Docker est non-négociable

Par défaut, Docker est permissif. Le daemon tourne en root, les conteneurs aussi, et rien ne filtre les appels système. C’est pratique pour développer, mais en production c’est une bombe à retardement.

🔥 Cas réel : En 2019, la faille CVE-2019-5736 permettait à un conteneur malveillant d’écraser le binaire runc de l’hôte et d’exécuter du code en root. Toutes les installations Docker non patchées étaient vulnérables. Un simple docker run d’une image piégée suffisait.

Les trois vecteurs d’attaque principaux sont :

  • Escalade de privilèges — un process root dans le conteneur peut exploiter des failles kernel pour atteindre l’hôte
  • Appels système dangereux — sans filtrage, un conteneur peut appeler mount(), reboot() ou kexec_load()
  • Accès aux ressources sensibles — le socket Docker monté (/var/run/docker.sock) donne le contrôle total du daemon

🧠 À retenir : La sécurité Docker, ce n’est pas UN mécanisme. C’est un empilement de couches : image minimale → user non-root → seccomp → AppArmor → réseau isolé → daemon protégé.

Comprendre les trois piliers de l’isolation

Le mode rootless

Le mode rootless fait tourner le daemon Docker sans privilèges root. Le process Docker et tous les conteneurs s’exécutent sous ton utilisateur normal. Même en cas d’évasion, l’attaquant n’a que les droits d’un utilisateur lambda.

Il existe deux approches complémentaires :

  • User namespace remapping (userns-remap) — le daemon reste root, mais le UID 0 dans le conteneur est mappé à un UID élevé (ex: 100000) sur l’hôte
  • Docker Rootless Mode — le daemon lui-même ne tourne plus en root, c’est la solution la plus sûre

Seccomp (Secure Computing Mode)

Seccomp filtre les appels système (syscalls) qu’un conteneur peut exécuter. Docker applique un profil par défaut qui bloque environ 44 syscalls dangereux comme mount, reboot, kexec_load ou unshare. Tu peux créer des profils sur mesure pour n’autoriser que le strict nécessaire.

AppArmor

AppArmor est un module de sécurité Linux qui contrôle l’accès aux ressources : fichiers, réseau, capabilities. Là où seccomp dit “tu ne peux pas appeler mount()”, AppArmor dit “tu ne peux pas lire /etc/shadow”. Les deux sont complémentaires.

💡 Tip DevOps : Seccomp filtre les actions (syscalls), AppArmor filtre les cibles (fichiers, réseau). Utilise les deux ensemble pour une défense en profondeur.

Commandes essentielles

Commençons par observer le comportement par défaut et configurer le mode rootless. Ce bloc montre la différence entre un conteneur root et un conteneur restreint, puis comment activer le user namespace remapping :

# Conteneur par défaut = root (dangereux)
docker run --rm alpine id
# uid=0(root) gid=0(root)

# Conteneur avec utilisateur non-root (sécurisé)
docker run --rm --user 1000:1000 alpine id
# uid=1000 gid=1000

# Activer le user namespace remapping dans /etc/docker/daemon.json :
# { "userns-remap": "default" }
sudo systemctl restart docker

# Vérifier : root dans le conteneur = UID 100000 sur l'hôte
docker run --rm alpine cat /proc/self/uid_map
# 0     100000      65536

Pour aller plus loin avec le mode rootless complet, installe et configure le daemon sans privilèges :

# Installer les prérequis et le mode rootless
sudo apt-get install -y uidmap dbus-user-session
dockerd-rootless-setuptool.sh install

# Configurer le socket utilisateur
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock
systemctl --user start docker && systemctl --user enable docker

# Survivre au logout
sudo loginctl enable-linger $(whoami)

⚠️ Attention : Le mode rootless a des limitations. Les ports < 1024 nécessitent sysctl net.ipv4.ip_unprivileged_port_start=0, et certains storage drivers ne sont pas supportés.

Pour vérifier les mécanismes de sécurité actifs et appliquer des profils custom :

# Vérifier seccomp (2 = mode filter actif)
docker run --rm alpine grep Seccomp /proc/self/status
# Seccomp:    2

# Vérifier AppArmor
docker run --rm alpine cat /proc/self/attr/current
# docker-default (enforce)

# Lancer avec profil seccomp ou AppArmor custom
docker run --security-opt seccomp=/path/to/profile.json myapp
docker run --security-opt apparmor=mon-profil-nginx myapp

Cas concret : sécuriser un microservice en production

🔥 Cas réel : Une fintech déploie une API de paiement en conteneurs Docker. Les exigences PCI-DSS imposent une isolation maximale. Voici le Dockerfile qu’ils utilisent, combinant multi-stage build, utilisateur non-root et healthcheck :

FROM python:3.12-slim AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt

FROM python:3.12-slim
RUN groupadd -r appuser && \
    useradd -r -g appuser -d /app -s /sbin/nologin appuser
WORKDIR /app
COPY --from=builder /install /usr/local
COPY --chown=appuser:appuser . .
USER appuser
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
EXPOSE 8000
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Et le docker run en production combine toutes les couches de sécurité — filesystem read-only, capabilities droppées, limites de ressources :

docker run -d \
  --name payment-api \
  --user 1000:1000 \
  --read-only \
  --tmpfs /tmp:noexec,nosuid,size=64m \
  --security-opt no-new-privileges:true \
  --security-opt seccomp=/etc/docker/seccomp-api.json \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --memory 512m --cpus 1.0 --pids-limit 100 \
  payment-api:latest

💡 Tip DevOps : Le flag --no-new-privileges empêche un process d’acquérir de nouveaux privilèges via setuid ou setgid. Combine-le toujours avec --cap-drop ALL et n’ajoute que les capabilities strictement nécessaires.

Pièges fréquents et exercice pratique

Monter le socket Dockerdocker run -v /var/run/docker.sock:/var/run/docker.sock donne au conteneur le contrôle total du daemon. C’est l’équivalent d’un accès root sur l’hôte. Si tu en as besoin (CI/CD, monitoring), utilise un proxy filtrant comme Tecnativa docker-socket-proxy.

Désactiver seccomp--security-opt seccomp=unconfined supprime tout filtrage syscall. Certains tutos le recommandent pour “résoudre” des erreurs. C’est comme désactiver l’alarme incendie parce qu’elle sonne. Trouve le syscall manquant et ajoute-le au profil.

Oublier --no-new-privileges — Sans ce flag, un binaire avec le bit setuid dans l’image peut escalader les privilèges même si tu utilises USER non-root dans le Dockerfile.

Images latest non scannées — Utilise toujours un tag précis et scanne avec Trivy avant chaque déploiement : trivy image monapp:v1.2.3.

⚠️ Attention : Le USER dans le Dockerfile ne suffit pas. Un attaquant qui exploite une faille peut toujours escalader si seccomp et les capabilities ne sont pas verrouillés. Les trois couches sont nécessaires.

Exercice

  1. Lance un conteneur Alpine en root (docker run --rm alpine id) puis avec --user 1000:1000. Essaie d’écrire dans /etc/ dans les deux cas et observe la différence.

  2. Vérifie que seccomp est actif : docker run --rm alpine grep Seccomp /proc/self/status. Puis teste un syscall bloqué : docker run --rm alpine unshare -r id. Compare avec --security-opt seccomp=unconfined.

  3. Défi : Écris un Dockerfile pour une app Node.js qui respecte toutes les bonnes pratiques vues dans ce cours (multi-stage, user non-root, healthcheck). Lance-le avec --read-only, --cap-drop ALL et --no-new-privileges.

À retenir

🧠 Les 5 réflexes sécurité Docker :

  • Jamais root — utilise USER dans le Dockerfile ET --user au runtime
  • Seccomp actif — ne le désactive jamais, crée un profil custom si nécessaire
  • Capabilities minimales--cap-drop ALL puis ajoute uniquement ce qu’il faut
  • Read-only filesystem--read-only + --tmpfs pour les écritures temporaires
  • Pas de socket Docker — si indispensable, utilise un proxy filtrant

La sécurité Docker n’est pas un one-shot. C’est un process continu : scanner les images, auditer les configs, mettre à jour les profils. Intègre ces vérifications dans ta CI/CD et tu dormiras mieux la nuit.


➡️ La suite : Dans le prochain chapitre, on aborde le scanning d’images avec Trivy, la signature avec Docker Content Trust et l’audit CIS. On continue ! 🚀

🖥️ Pratique sur ton propre serveur

Pour suivre Apprendre Docker en conditions réelles, tu as besoin d'un VPS. DigitalOcean offre 200$ de crédit gratuit pour démarrer.

Obtenir 200$

Articles liés