
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Modèle de menaces : comprendre les vecteurs d’attaque Docker
- Isolation : namespaces, cgroups, capabilities
- Rootless mode : Docker sans privilèges root
- Profils de sécurité : seccomp, AppArmor, SELinux
- Images sécurisées : scan vulnérabilités, provenance, signatures
- Runtime : read-only filesystem, no-new-privileges, user namespaces
Introduction
Section intitulée « Introduction »Sécuriser Docker en production nécessite de comprendre que les conteneurs ne sont pas des VMs. Un conteneur partage le kernel de l’hôte, ce qui offre des performances excellentes mais crée une surface d’attaque différente. Ce guide vous accompagne dans la mise en œuvre des bonnes pratiques de sécurité, du principe du moindre privilège aux profils seccomp, en passant par le mode rootless.
Prérequis
Section intitulée « Prérequis »- Connaissances Linux de base (utilisateurs, permissions)
- Accès root ou sudo sur le système
- Docker 20.10+ recommandé
Comprendre le modèle de menaces Docker
Section intitulée « Comprendre le modèle de menaces Docker »Avant de sécuriser, comprenez ce que vous protégez et contre quoi.
Le daemon Docker tourne en root
Section intitulée « Le daemon Docker tourne en root »Par défaut, le daemon Docker (dockerd) s’exécute en tant que root. Toute personne pouvant communiquer avec le socket Docker (/var/run/docker.sock) peut :
- Lancer des conteneurs avec
--privileged - Monter n’importe quel répertoire de l’hôte
- Accéder à toutes les données du système
Conséquence : Ajouter un utilisateur au groupe docker équivaut à lui donner les privilèges root.
# Cette commande donne un accès root à l'hôte !docker run -it --rm -v /:/host alpine chroot /hostConteneurs ≠ VMs
Section intitulée « Conteneurs ≠ VMs »| Caractéristique | Conteneur | VM |
|---|---|---|
| Kernel | Partagé avec l’hôte | Propre kernel |
| Isolation | Namespaces + cgroups | Hyperviseur |
| Surface d’attaque | Kernel hôte | Hyperviseur |
| Overhead | Minimal | Significatif |
| Escape | Plus facile | Rare |
Un conteneur n’est qu’un processus Linux isolé par des namespaces (vue restreinte du système) et des cgroups (limites de ressources). Cette isolation est logicielle, contrairement à la virtualisation matérielle des VMs.
Vecteurs d’attaque principaux
Section intitulée « Vecteurs d’attaque principaux »- Image malveillante : téléchargement d’une image compromise
- Escape conteneur : exploitation d’une faille kernel pour sortir de l’isolation
- Mauvaise configuration :
--privileged, socket exposé, secrets en clair - Vulnérabilités applicatives : CVE dans les packages de l’image
- Déni de service : conteneur consommant toutes les ressources
Ne jamais utiliser —privileged
Section intitulée « Ne jamais utiliser —privileged »L’option --privileged désactive toutes les protections :
- Accès à tous les devices de l’hôte
- Toutes les capabilities Linux
- Pas de profil seccomp/AppArmor
- Possibilité de modifier le kernel
# ❌ INTERDIT en productiondocker run --privileged nginx
# Ce conteneur peut :# - Charger des modules kernel# - Modifier les règles iptables# - Monter des filesystems# - Accéder aux disques brutsAlternatives à —privileged
Section intitulée « Alternatives à —privileged »| Besoin | Alternative sécurisée |
|---|---|
| Accéder à un device | --device /dev/xxx |
| Capability spécifique | --cap-add CAP_XXX |
| Port < 1024 | --cap-add NET_BIND_SERVICE |
| Docker-in-Docker | Sysbox runtime ou Podman |
Linux Capabilities
Section intitulée « Linux Capabilities »Les capabilities divisent les privilèges root en permissions granulaires (~40 au total). Au lieu de donner “tous les pouvoirs” avec root, Docker retire les capabilities dangereuses par défaut.
Capabilities par défaut de Docker
Section intitulée « Capabilities par défaut de Docker »Docker accorde ces capabilities par défaut :
| Capability | Usage |
|---|---|
CHOWN | Changer le propriétaire des fichiers |
DAC_OVERRIDE | Ignorer les permissions fichiers |
FSETID | Conserver les bits setuid/setgid |
FOWNER | Ignorer les vérifications de propriétaire |
MKNOD | Créer des fichiers spéciaux |
NET_RAW | Utiliser RAW et PACKET sockets (ping) |
SETGID | Changer le GID |
SETUID | Changer l’UID |
SETFCAP | Définir les file capabilities |
SETPCAP | Modifier les capabilities |
NET_BIND_SERVICE | Binder ports < 1024 |
SYS_CHROOT | Utiliser chroot |
KILL | Envoyer des signaux |
AUDIT_WRITE | Écrire dans le log d’audit kernel |
Capabilities dangereuses (retirées par défaut)
Section intitulée « Capabilities dangereuses (retirées par défaut) »Certaines capabilities sont tellement puissantes qu’elles permettent d’échapper à l’isolation du conteneur. Docker les retire par défaut, mais certaines images mal configurées les réactivent.
| Capability | Risque |
|---|---|
CAP_SYS_ADMIN | Presque équivalent à root (mount, quotas, etc.) |
CAP_NET_ADMIN | Modifier les règles réseau (iptables) |
CAP_SYS_PTRACE | Débugger d’autres processus |
CAP_SYS_MODULE | Charger des modules kernel |
Réduire les capabilities
Section intitulée « Réduire les capabilities »Bonne pratique : retirez toutes les capabilities puis ajoutez uniquement celles nécessaires.
# Retirer toutes les capabilitiesdocker run --cap-drop ALL nginx
# Retirer tout puis ajouter le minimumdocker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginxEn Docker Compose :
services: web: image: nginx:alpine cap_drop: - ALL cap_add: - NET_BIND_SERVICE - CHOWN - SETUID - SETGIDExemple : conteneur avec capabilities minimales
Section intitulée « Exemple : conteneur avec capabilities minimales »Pour une application qui n’a besoin d’aucun privilège spécial :
docker run -d \ --name app-secure \ --cap-drop ALL \ --security-opt no-new-privileges:true \ --read-only \ --user 1000:1000 \ myapp:latestVérification : Inspectez les capabilities effectives :
# Dans le conteneurdocker exec app-secure cat /proc/1/status | grep Cap
# CapEff: 0000000000000000 = aucune capabilityCapabilities disponibles
Section intitulée « Capabilities disponibles »Liste complète des capabilities Linux utiles pour Docker :
| Capability | Description | Cas d’usage |
|---|---|---|
CAP_NET_BIND_SERVICE | Binder ports < 1024 | Serveurs web sur 80/443 |
CAP_NET_ADMIN | Administration réseau | Conteneurs VPN, load balancers |
CAP_NET_RAW | Paquets raw (ping) | Monitoring réseau |
CAP_SYS_PTRACE | Debug processus | Profiling, debugging |
CAP_SYS_ADMIN | Administration système | Éviter absolument |
CAP_DAC_READ_SEARCH | Lire tous fichiers | Backup tools |
CAP_CHOWN | Changer propriétaires | Apps multi-user |
CAP_FOWNER | Bypass propriétaire | Gestionnaires fichiers |
Profils Seccomp
Section intitulée « Profils Seccomp »Seccomp (Secure Computing Mode) filtre les appels système (syscalls) au niveau kernel. Un syscall non autorisé tue immédiatement le processus.
Profil par défaut
Section intitulée « Profil par défaut »Docker applique automatiquement un profil seccomp bloquant ~44 syscalls dangereux :
| Syscall bloqué | Risque si autorisé |
|---|---|
reboot | Redémarrer l’hôte |
kexec_load | Charger un nouveau kernel |
mount / umount | Monter des filesystems |
swapon / swapoff | Modifier le swap |
init_module | Charger des modules kernel |
acct | Comptabilité processus |
settimeofday | Modifier l’heure système |
Vérification que seccomp est actif :
docker run --rm alpine cat /proc/1/status | grep Seccomp# Seccomp: 2 (2 = mode filter, 0 = désactivé)Profils personnalisés
Section intitulée « Profils personnalisés »Pour les applications sensibles, créez un profil plus restrictif.
Étape 1 : Générez un profil de base avec OCI spec :
# Utiliser un générateur de profildocker run --rm -it \ -v /var/run/docker.sock:/var/run/docker.sock \ docker/compose-cli:latest seccomp-profile my-appÉtape 2 : Créez un profil JSON restrictif (strict-seccomp.json) :
{ "defaultAction": "SCMP_ACT_ERRNO", "architectures": ["SCMP_ARCH_X86_64"], "syscalls": [ { "names": [ "read", "write", "close", "fstat", "mmap", "mprotect", "munmap", "brk", "access", "getpid", "openat", "exit_group", "arch_prctl", "set_tid_address", "set_robust_list", "futex", "nanosleep", "clock_gettime", "sigaltstack", "rt_sigaction", "rt_sigprocmask", "getuid", "getgid", "geteuid", "getegid", "getcwd", "readlink", "getrandom" ], "action": "SCMP_ACT_ALLOW" } ]}Étape 3 : Appliquez le profil :
docker run --security-opt seccomp=strict-seccomp.json myappAppArmor et SELinux
Section intitulée « AppArmor et SELinux »Ces systèmes de Mandatory Access Control (MAC) restreignent ce que les processus peuvent faire au-delà des permissions Unix traditionnelles.
AppArmor est actif par défaut sur Ubuntu/Debian. Docker applique automatiquement le profil docker-default.
Vérifier l’état AppArmor :
# Profils chargéssudo aa-status
# Profil du conteneurdocker inspect --format '{{.AppArmorProfile}}' my-containerProfil par défaut (docker-default) :
- Interdit l’écriture dans
/procet/sys - Restreint les montages
- Bloque l’accès à certains devices
Créer un profil personnalisé (/etc/apparmor.d/docker-nginx) :
#include <tunables/global>
profile docker-nginx flags=(attach_disconnected,mediate_deleted) { #include <abstractions/base>
# Accès en lecture aux fichiers web /var/www/** r, /etc/nginx/** r,
# Écriture des logs /var/log/nginx/** rw,
# Socket et PID /var/run/nginx.pid rw, /var/run/nginx/*.sock rw,
# Refuser tout le reste deny /proc/** w, deny /sys/** w,}Appliquer le profil :
# Charger le profilsudo apparmor_parser -r /etc/apparmor.d/docker-nginx
# Lancer le conteneur avec ce profildocker run --security-opt apparmor=docker-nginx nginxSELinux utilise des contextes de sécurité (labels) pour contrôler les accès.
Vérifier l’état SELinux :
# Mode actuelgetenforce# Enforcing, Permissive, ou Disabled
# Contexte d'un conteneurdocker inspect --format '{{.ProcessLabel}}' my-containerLabels SELinux Docker :
container_t: type par défaut des conteneurscontainer_file_t: fichiers accessibles aux conteneurscontainer_var_run_t: fichiers runtime
Configurer SELinux pour Docker :
# Activer le support SELinux dans daemon.json{ "selinux-enabled": true}
# Redémarrer Dockersudo systemctl restart dockerMonter un volume avec le bon contexte :
# :z = contexte partagé entre conteneursdocker run -v /data:/data:z myapp
# :Z = contexte privé (ce conteneur uniquement)docker run -v /data:/data:Z myappRootless mode
Section intitulée « Rootless mode »Le mode rootless exécute le daemon Docker et tous les conteneurs en tant qu’utilisateur non-root. Même une escape de conteneur ne donne pas accès root à l’hôte.
Prérequis
Section intitulée « Prérequis »- Kernel 5.11+ (ou 4.18+ avec configuration)
newuidmapetnewgidmapinstallés- Délégation cgroup v2
# Vérifier les prérequisdockerd-rootless-setuptool.sh check
# Installer les outilssudo apt install uidmap dbus-user-sessionInstallation rootless
Section intitulée « Installation rootless »-
Installer le mode rootless
Fenêtre de terminal # En tant qu'utilisateur normal (pas root)dockerd-rootless-setuptool.sh install -
Configurer l’environnement
Ajoutez à votre
~/.bashrc:Fenêtre de terminal export PATH=/usr/bin:$PATHexport DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock -
Activer le démarrage automatique
Fenêtre de terminal systemctl --user enable dockerloginctl enable-linger $(whoami) -
Vérifier le fonctionnement
Fenêtre de terminal docker run --rm hello-world
Limitations du mode rootless
Section intitulée « Limitations du mode rootless »Le mode rootless apporte une sécurité accrue, mais avec certaines contraintes. Connaître ces limitations vous aidera à décider si ce mode convient à votre cas d’usage.
| Fonctionnalité | Rootless | Solution alternative |
|---|---|---|
| Ports < 1024 | ❌ Non | Utiliser des ports > 1024 + reverse proxy |
| Overlay network | ⚠️ Limité | VPN ou réseau externe |
| Cgroup v1 | ❌ Non | Migrer vers cgroup v2 |
| AppArmor/SELinux | ⚠️ Limité | Profils utilisateur |
--privileged | ❌ Non | Pas nécessaire |
User namespaces
Section intitulée « User namespaces »Les user namespaces mappent l’UID 0 (root) du conteneur sur un UID non privilégié de l’hôte. Même si un processus est root dans le conteneur, il n’a aucun privilège sur l’hôte.
Principe du mapping
Section intitulée « Principe du mapping »Le mapping traduit les UIDs du conteneur vers des UIDs non privilégiés sur l’hôte. Ainsi, même une compromission complète du conteneur ne donne aucun accès privilégié au système hôte.
| Dans le conteneur | Sur l’hôte |
|---|---|
| UID 0 (root) | UID 100000 |
| UID 1 | UID 100001 |
| … | … |
| UID 65535 | UID 165535 |
Configuration userns-remap
Section intitulée « Configuration userns-remap »-
Créer l’utilisateur de mapping
Fenêtre de terminal # Créer un utilisateur dédiésudo useradd -r -s /bin/false dockremap -
Configurer les mappings UID/GID
/etc/subuid echo "dockremap:100000:65536" | sudo tee -a /etc/subuid# /etc/subgidecho "dockremap:100000:65536" | sudo tee -a /etc/subgid -
Activer dans daemon.json
{"userns-remap": "dockremap"} -
Redémarrer Docker
Fenêtre de terminal sudo systemctl restart docker -
Vérifier le mapping
Fenêtre de terminal # Lancer un conteneurdocker run -d --name test alpine sleep 3600# Vérifier l'UID sur l'hôteps aux | grep "sleep 3600"# L'UID devrait être 100000, pas 0
Sécuriser les images
Section intitulée « Sécuriser les images »Scanner les vulnérabilités
Section intitulée « Scanner les vulnérabilités »Trivy est le scanner open-source de référence :
# Scanner une image localetrivy image nginx:latest
# Scanner avec seuil de sévéritétrivy image --severity HIGH,CRITICAL nginx:latest
# Ignorer les vulnérabilités non corrigéestrivy image --ignore-unfixed nginx:latest
# Export JSON pour CI/CDtrivy image -f json -o results.json nginx:latestIntégration CI/CD (GitHub Actions) :
- name: Scan image uses: aquasecurity/trivy-action@master with: image-ref: 'myapp:${{ github.sha }}' format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL,HIGH'Alternatives à Trivy :
| Outil | Éditeur | Particularité |
|---|---|---|
| Grype | Anchore | Léger, rapide |
| Snyk | Snyk | Intégration IDE |
| Clair | Quay | Registry-native |
| Docker Scout | Docker | Intégré à Docker Desktop |
Vérifier la provenance
Section intitulée « Vérifier la provenance »Docker Content Trust (DCT) garantit l’intégrité des images :
# Activer DCT (refuser les images non signées)export DOCKER_CONTENT_TRUST=1
# Vérifier les signaturesdocker trust inspect nginx:latest
# Signer une image (nécessite une clé)docker trust sign myregistry/myapp:v1.0Sigstore/cosign pour les attestations SLSA :
# Installer cosignbrew install cosign # ou go install
# Vérifier une imagecosign verify ghcr.io/sigstore/cosign:latest \ --certificate-identity keyless@sigstore \ --certificate-oidc-issuer https://accounts.google.comImages de base sécurisées
Section intitulée « Images de base sécurisées »Le choix de l’image de base impacte directement votre surface d’attaque. Moins l’image contient de packages, moins il y a de vulnérabilités potentielles.
| Image | Taille | Shell | Package manager | CVE potentiels |
|---|---|---|---|---|
| Ubuntu | ~75 Mo | ✅ | apt | Élevé |
| Debian slim | ~30 Mo | ✅ | apt | Moyen |
| Alpine | ~5 Mo | ✅ | apk | Faible |
| Distroless | ~20 Mo | ❌ | ❌ | Très faible |
| Scratch | 0 Mo | ❌ | ❌ | Application seule |
Recommandations :
- Production : Distroless ou Alpine
- Debug nécessaire : Alpine
- Binaires statiques : Scratch
# Multi-stage avec distrolessFROM golang:1.21 AS builderWORKDIR /appCOPY . .RUN CGO_ENABLED=0 go build -o /myapp
FROM gcr.io/distroless/static:nonrootCOPY --from=builder /myapp /myappUSER nonroot:nonrootENTRYPOINT ["/myapp"]Bonnes pratiques runtime
Section intitulée « Bonnes pratiques runtime »Read-only filesystem
Section intitulée « Read-only filesystem »Le filesystem en lecture seule empêche l’écriture de malwares ou backdoors :
# Filesystem en lecture seuledocker run --read-only nginx
# Avec tmpfs pour les fichiers temporairesdocker run --read-only \ --tmpfs /tmp:rw,noexec,nosuid,size=100m \ --tmpfs /var/cache/nginx:rw,size=50m \ nginxDocker Compose :
services: web: image: nginx:alpine read_only: true tmpfs: - /tmp:size=100m - /var/cache/nginxNo-new-privileges
Section intitulée « No-new-privileges »Empêche l’acquisition de nouveaux privilèges via setuid/setgid :
docker run --security-opt no-new-privileges:true myappDocker Compose :
services: app: image: myapp security_opt: - no-new-privileges:trueLimiter les ressources
Section intitulée « Limiter les ressources »Mémoire :
# Limite à 256 Mo RAMdocker run --memory 256m myapp
# Limite RAM + swapdocker run --memory 256m --memory-swap 512m myappCPU :
# Limiter à 0.5 CPUdocker run --cpus 0.5 myapp
# Affinité CPUdocker run --cpuset-cpus "0,1" myappProcessus (fork bomb protection) :
docker run --pids-limit 100 myappExemple complet :
docker run -d \ --name app-secure \ --memory 256m \ --memory-swap 256m \ --cpus 0.5 \ --pids-limit 100 \ --read-only \ --tmpfs /tmp:size=50m \ --security-opt no-new-privileges:true \ --cap-drop ALL \ --user 1000:1000 \ myapp:latestPas de réseau si inutile
Section intitulée « Pas de réseau si inutile »Pour les tâches de calcul sans besoin réseau :
# Aucune interface réseau (sauf loopback)docker run --network none myapp process-files.shAudit et conformité
Section intitulée « Audit et conformité »Docker Bench Security
Section intitulée « Docker Bench Security »Script officiel vérifiant la conformité au CIS Docker Benchmark :
# Exécution du benchmarkdocker run --rm -it \ --net host \ --pid host \ --userns host \ --cap-add audit_control \ -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \ -v /var/lib:/var/lib:ro \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -v /usr/lib/systemd:/usr/lib/systemd:ro \ -v /etc:/etc:ro \ docker/docker-bench-securityCatégories vérifiées :
| Section | Contenu |
|---|---|
| 1 | Configuration hôte |
| 2 | Configuration daemon |
| 3 | Fichiers de configuration |
| 4 | Images et Dockerfiles |
| 5 | Runtime conteneurs |
| 6 | Opérations de sécurité |
| 7 | Docker Swarm |
Recommandations CIS prioritaires
Section intitulée « Recommandations CIS prioritaires »Parmi les dizaines de règles CIS, voici celles qui apportent le plus grand gain de sécurité pour l’effort investi.
| ID | Recommandation | Impact |
|---|---|---|
| 2.1 | Activer --live-restore | Continuité de service |
| 2.2 | Désactiver userland-proxy | Performance + sécurité |
| 4.1 | Créer un user non-root dans les images | Isolation |
| 5.1 | Ne pas utiliser --privileged | Critique |
| 5.2 | Limiter les capabilities | Moindre privilège |
| 5.4 | Restreindre les montages | Intégrité hôte |
| 5.10 | Limiter la mémoire | DoS prevention |
Dépannage sécurité
Section intitulée « Dépannage sécurité »Problèmes courants et solutions
Section intitulée « Problèmes courants et solutions »Les mesures de sécurité peuvent parfois bloquer des fonctionnalités légitimes. Voici les erreurs les plus fréquentes et comment les résoudre sans compromettre la sécurité.
| Symptôme | Cause probable | Solution |
|---|---|---|
permission denied dans le conteneur | Filesystem read-only | Ajouter --tmpfs /path pour les fichiers temporaires |
operation not permitted | Capability manquante | --cap-add la capability requise |
| Conteneur tué par OOM | Limite mémoire atteinte | Augmenter --memory ou optimiser l’app |
| Port binding failed | Rootless + port < 1024 | Utiliser port > 1024 ou ajouter capability |
| Volume permission denied | User namespace actif | Ajuster UID/GID ou --userns=host |
Commandes de diagnostic
Section intitulée « Commandes de diagnostic »# Vérifier les capabilities effectivesdocker exec mycontainer cat /proc/1/status | grep -E '^Cap'
# Vérifier le profil seccompdocker inspect --format '{{.HostConfig.SecurityOpt}}' mycontainer
# Lister les syscalls bloqués (debug)docker run --security-opt seccomp=unconfined strace -f myapp
# Vérifier les limites de ressourcesdocker stats --no-stream mycontainerÀ retenir
Section intitulée « À retenir »- Le daemon Docker tourne en root : protégez le socket, utilisez rootless si possible
- Conteneurs ≠ VMs : le kernel partagé est un vecteur d’attaque
- Jamais
--privileged: utilisez--cap-addpour les capabilities spécifiques - Capabilities minimales :
--cap-drop ALLpuis--cap-adduniquement le nécessaire - Seccomp actif : le profil par défaut bloque 44 syscalls dangereux
- Images minimales : Distroless ou Alpine réduisent la surface d’attaque
- Limites de ressources :
--memory,--cpus,--pids-limitprotègent l’hôte - Audit régulier : Docker Bench for Security vérifie la conformité CIS
Contrôle des connaissances
Section intitulée « Contrôle des connaissances »Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
📋 Récapitulatif de vos réponses
Vérifiez vos réponses avant de soumettre. Cliquez sur une question pour la modifier.