Aller au contenu
Conteneurs & Orchestration medium

Sécuriser Docker en production

22 min de lecture

logo docker

  • 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

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.

  • Connaissances Linux de base (utilisateurs, permissions)
  • Accès root ou sudo sur le système
  • Docker 20.10+ recommandé

Avant de sécuriser, comprenez ce que vous protégez et contre quoi.

Couches de sécurité Docker : kernel partagé, daemon root, isolation, conteneurs sécurisés vs vulnérables

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.

Fenêtre de terminal
# Cette commande donne un accès root à l'hôte !
docker run -it --rm -v /:/host alpine chroot /host
CaractéristiqueConteneurVM
KernelPartagé avec l’hôtePropre kernel
IsolationNamespaces + cgroupsHyperviseur
Surface d’attaqueKernel hôteHyperviseur
OverheadMinimalSignificatif
EscapePlus facileRare

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.

  1. Image malveillante : téléchargement d’une image compromise
  2. Escape conteneur : exploitation d’une faille kernel pour sortir de l’isolation
  3. Mauvaise configuration : --privileged, socket exposé, secrets en clair
  4. Vulnérabilités applicatives : CVE dans les packages de l’image
  5. Déni de service : conteneur consommant toutes les ressources

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
Fenêtre de terminal
# ❌ INTERDIT en production
docker run --privileged nginx
# Ce conteneur peut :
# - Charger des modules kernel
# - Modifier les règles iptables
# - Monter des filesystems
# - Accéder aux disques bruts
BesoinAlternative 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-DockerSysbox runtime ou Podman

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.

Docker accorde ces capabilities par défaut :

CapabilityUsage
CHOWNChanger le propriétaire des fichiers
DAC_OVERRIDEIgnorer les permissions fichiers
FSETIDConserver les bits setuid/setgid
FOWNERIgnorer les vérifications de propriétaire
MKNODCréer des fichiers spéciaux
NET_RAWUtiliser RAW et PACKET sockets (ping)
SETGIDChanger le GID
SETUIDChanger l’UID
SETFCAPDéfinir les file capabilities
SETPCAPModifier les capabilities
NET_BIND_SERVICEBinder ports < 1024
SYS_CHROOTUtiliser chroot
KILLEnvoyer des signaux
AUDIT_WRITEÉcrire dans le log d’audit kernel

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.

CapabilityRisque
CAP_SYS_ADMINPresque équivalent à root (mount, quotas, etc.)
CAP_NET_ADMINModifier les règles réseau (iptables)
CAP_SYS_PTRACEDébugger d’autres processus
CAP_SYS_MODULECharger des modules kernel

Bonne pratique : retirez toutes les capabilities puis ajoutez uniquement celles nécessaires.

Fenêtre de terminal
# Retirer toutes les capabilities
docker run --cap-drop ALL nginx
# Retirer tout puis ajouter le minimum
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE nginx

En Docker Compose :

services:
web:
image: nginx:alpine
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
- CHOWN
- SETUID
- SETGID

Pour une application qui n’a besoin d’aucun privilège spécial :

Fenêtre de terminal
docker run -d \
--name app-secure \
--cap-drop ALL \
--security-opt no-new-privileges:true \
--read-only \
--user 1000:1000 \
myapp:latest

Vérification : Inspectez les capabilities effectives :

Fenêtre de terminal
# Dans le conteneur
docker exec app-secure cat /proc/1/status | grep Cap
# CapEff: 0000000000000000 = aucune capability

Liste complète des capabilities Linux utiles pour Docker :

CapabilityDescriptionCas d’usage
CAP_NET_BIND_SERVICEBinder ports < 1024Serveurs web sur 80/443
CAP_NET_ADMINAdministration réseauConteneurs VPN, load balancers
CAP_NET_RAWPaquets raw (ping)Monitoring réseau
CAP_SYS_PTRACEDebug processusProfiling, debugging
CAP_SYS_ADMINAdministration systèmeÉviter absolument
CAP_DAC_READ_SEARCHLire tous fichiersBackup tools
CAP_CHOWNChanger propriétairesApps multi-user
CAP_FOWNERBypass propriétaireGestionnaires fichiers

Seccomp (Secure Computing Mode) filtre les appels système (syscalls) au niveau kernel. Un syscall non autorisé tue immédiatement le processus.

Docker applique automatiquement un profil seccomp bloquant ~44 syscalls dangereux :

Syscall bloquéRisque si autorisé
rebootRedémarrer l’hôte
kexec_loadCharger un nouveau kernel
mount / umountMonter des filesystems
swapon / swapoffModifier le swap
init_moduleCharger des modules kernel
acctComptabilité processus
settimeofdayModifier l’heure système

Vérification que seccomp est actif :

Fenêtre de terminal
docker run --rm alpine cat /proc/1/status | grep Seccomp
# Seccomp: 2 (2 = mode filter, 0 = désactivé)

Pour les applications sensibles, créez un profil plus restrictif.

Étape 1 : Générez un profil de base avec OCI spec :

Fenêtre de terminal
# Utiliser un générateur de profil
docker 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 :

Fenêtre de terminal
docker run --security-opt seccomp=strict-seccomp.json myapp

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 :

Fenêtre de terminal
# Profils chargés
sudo aa-status
# Profil du conteneur
docker inspect --format '{{.AppArmorProfile}}' my-container

Profil par défaut (docker-default) :

  • Interdit l’écriture dans /proc et /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 :

Fenêtre de terminal
# Charger le profil
sudo apparmor_parser -r /etc/apparmor.d/docker-nginx
# Lancer le conteneur avec ce profil
docker run --security-opt apparmor=docker-nginx nginx

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.

  • Kernel 5.11+ (ou 4.18+ avec configuration)
  • newuidmap et newgidmap installés
  • Délégation cgroup v2
Fenêtre de terminal
# Vérifier les prérequis
dockerd-rootless-setuptool.sh check
# Installer les outils
sudo apt install uidmap dbus-user-session
  1. Installer le mode rootless

    Fenêtre de terminal
    # En tant qu'utilisateur normal (pas root)
    dockerd-rootless-setuptool.sh install
  2. Configurer l’environnement

    Ajoutez à votre ~/.bashrc :

    Fenêtre de terminal
    export PATH=/usr/bin:$PATH
    export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
  3. Activer le démarrage automatique

    Fenêtre de terminal
    systemctl --user enable docker
    loginctl enable-linger $(whoami)
  4. Vérifier le fonctionnement

    Fenêtre de terminal
    docker run --rm hello-world

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éRootlessSolution alternative
Ports < 1024❌ NonUtiliser des ports > 1024 + reverse proxy
Overlay network⚠️ LimitéVPN ou réseau externe
Cgroup v1❌ NonMigrer vers cgroup v2
AppArmor/SELinux⚠️ LimitéProfils utilisateur
--privileged❌ NonPas nécessaire

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.

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 conteneurSur l’hôte
UID 0 (root)UID 100000
UID 1UID 100001
UID 65535UID 165535
  1. Créer l’utilisateur de mapping

    Fenêtre de terminal
    # Créer un utilisateur dédié
    sudo useradd -r -s /bin/false dockremap
  2. Configurer les mappings UID/GID

    /etc/subuid
    echo "dockremap:100000:65536" | sudo tee -a /etc/subuid
    # /etc/subgid
    echo "dockremap:100000:65536" | sudo tee -a /etc/subgid
  3. Activer dans daemon.json

    {
    "userns-remap": "dockremap"
    }
  4. Redémarrer Docker

    Fenêtre de terminal
    sudo systemctl restart docker
  5. Vérifier le mapping

    Fenêtre de terminal
    # Lancer un conteneur
    docker run -d --name test alpine sleep 3600
    # Vérifier l'UID sur l'hôte
    ps aux | grep "sleep 3600"
    # L'UID devrait être 100000, pas 0

Trivy est le scanner open-source de référence :

Fenêtre de terminal
# Scanner une image locale
trivy image nginx:latest
# Scanner avec seuil de sévérité
trivy image --severity HIGH,CRITICAL nginx:latest
# Ignorer les vulnérabilités non corrigées
trivy image --ignore-unfixed nginx:latest
# Export JSON pour CI/CD
trivy image -f json -o results.json nginx:latest

Inté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ÉditeurParticularité
GrypeAnchoreLéger, rapide
SnykSnykIntégration IDE
ClairQuayRegistry-native
Docker ScoutDockerIntégré à Docker Desktop

Docker Content Trust (DCT) garantit l’intégrité des images :

Fenêtre de terminal
# Activer DCT (refuser les images non signées)
export DOCKER_CONTENT_TRUST=1
# Vérifier les signatures
docker trust inspect nginx:latest
# Signer une image (nécessite une clé)
docker trust sign myregistry/myapp:v1.0

Sigstore/cosign pour les attestations SLSA :

Fenêtre de terminal
# Installer cosign
brew install cosign # ou go install
# Vérifier une image
cosign verify ghcr.io/sigstore/cosign:latest \
--certificate-identity keyless@sigstore \
--certificate-oidc-issuer https://accounts.google.com

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.

ImageTailleShellPackage managerCVE potentiels
Ubuntu~75 MoaptÉlevé
Debian slim~30 MoaptMoyen
Alpine~5 MoapkFaible
Distroless~20 MoTrès faible
Scratch0 MoApplication seule

Recommandations :

  • Production : Distroless ou Alpine
  • Debug nécessaire : Alpine
  • Binaires statiques : Scratch
# Multi-stage avec distroless
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN CGO_ENABLED=0 go build -o /myapp
FROM gcr.io/distroless/static:nonroot
COPY --from=builder /myapp /myapp
USER nonroot:nonroot
ENTRYPOINT ["/myapp"]

Le filesystem en lecture seule empêche l’écriture de malwares ou backdoors :

Fenêtre de terminal
# Filesystem en lecture seule
docker run --read-only nginx
# Avec tmpfs pour les fichiers temporaires
docker run --read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=100m \
--tmpfs /var/cache/nginx:rw,size=50m \
nginx

Docker Compose :

services:
web:
image: nginx:alpine
read_only: true
tmpfs:
- /tmp:size=100m
- /var/cache/nginx

Empêche l’acquisition de nouveaux privilèges via setuid/setgid :

Fenêtre de terminal
docker run --security-opt no-new-privileges:true myapp

Docker Compose :

services:
app:
image: myapp
security_opt:
- no-new-privileges:true

Mémoire :

Fenêtre de terminal
# Limite à 256 Mo RAM
docker run --memory 256m myapp
# Limite RAM + swap
docker run --memory 256m --memory-swap 512m myapp

CPU :

Fenêtre de terminal
# Limiter à 0.5 CPU
docker run --cpus 0.5 myapp
# Affinité CPU
docker run --cpuset-cpus "0,1" myapp

Processus (fork bomb protection) :

Fenêtre de terminal
docker run --pids-limit 100 myapp

Exemple complet :

Fenêtre de terminal
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:latest

Pour les tâches de calcul sans besoin réseau :

Fenêtre de terminal
# Aucune interface réseau (sauf loopback)
docker run --network none myapp process-files.sh

Script officiel vérifiant la conformité au CIS Docker Benchmark :

Fenêtre de terminal
# Exécution du benchmark
docker 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-security

Catégories vérifiées :

SectionContenu
1Configuration hôte
2Configuration daemon
3Fichiers de configuration
4Images et Dockerfiles
5Runtime conteneurs
6Opérations de sécurité
7Docker Swarm

Parmi les dizaines de règles CIS, voici celles qui apportent le plus grand gain de sécurité pour l’effort investi.

IDRecommandationImpact
2.1Activer --live-restoreContinuité de service
2.2Désactiver userland-proxyPerformance + sécurité
4.1Créer un user non-root dans les imagesIsolation
5.1Ne pas utiliser --privilegedCritique
5.2Limiter les capabilitiesMoindre privilège
5.4Restreindre les montagesIntégrité hôte
5.10Limiter la mémoireDoS prevention

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ômeCause probableSolution
permission denied dans le conteneurFilesystem read-onlyAjouter --tmpfs /path pour les fichiers temporaires
operation not permittedCapability manquante--cap-add la capability requise
Conteneur tué par OOMLimite mémoire atteinteAugmenter --memory ou optimiser l’app
Port binding failedRootless + port < 1024Utiliser port > 1024 ou ajouter capability
Volume permission deniedUser namespace actifAjuster UID/GID ou --userns=host
Fenêtre de terminal
# Vérifier les capabilities effectives
docker exec mycontainer cat /proc/1/status | grep -E '^Cap'
# Vérifier le profil seccomp
docker 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 ressources
docker stats --no-stream mycontainer
  1. Le daemon Docker tourne en root : protégez le socket, utilisez rootless si possible
  2. Conteneurs ≠ VMs : le kernel partagé est un vecteur d’attaque
  3. Jamais --privileged : utilisez --cap-add pour les capabilities spécifiques
  4. Capabilities minimales : --cap-drop ALL puis --cap-add uniquement le nécessaire
  5. Seccomp actif : le profil par défaut bloque 44 syscalls dangereux
  6. Images minimales : Distroless ou Alpine réduisent la surface d’attaque
  7. Limites de ressources : --memory, --cpus, --pids-limit protègent l’hôte
  8. Audit régulier : Docker Bench for Security vérifie la conformité CIS

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

10 questions
8 min.
70%

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