Hardening Docker & Conteneurs
Sécuriser les conteneurs : rootless, capabilities, seccomp, AppArmor, images, secrets
Images sécurisées
# Scanner les vulnérabilités d'une imagedocker scout cves {{IMAGE}}{{TAG}}trivy image {{IMAGE}}{{TAG}}grype {{IMAGE}}{{TAG}}# Scanner avec un seuil de sévérité (CI/CD — fail si CRITICAL)trivy image --exit-code 1 --severity CRITICAL {{IMAGE}}{{TAG}}# Inspecter le contenu d'une imagedocker sbom {{IMAGE}}{{TAG}} # Software Bill of Materialssyft {{IMAGE}}{{TAG}} # SBOM alternatifdive {{IMAGE}}{{TAG}} # Analyser les layers# Mettre à jour les imagesdocker pull {{IMAGE}}{{TAG}} # Récupérer la dernière version# Puis rebuilder les conteneurs qui en dépendent
# Dockerfile sécurisé — bonnes pratiquesFROM alpine:3.20 AS builder# ... build steps ...# Stage final minimalFROM gcr.io/distroless/static-debian12# Ou : FROM scratch (pour des binaires statiques Go/Rust)# Copier uniquement les artefacts nécessairesCOPY --from=builder /app/binary /app/binary# Utilisateur non-root dédiéRUN addgroup -g 10001 appgroup && adduser -u 10001 -G appgroup -s /bin/sh -D appuser# Pour distroless : USER avec UID directementUSER 10001:10001# Pas de secrets dans les ARG/ENV# ARG API_KEY ← dangereux, visible dans docker history# Utiliser --secret ou des volumes au runtimeEXPOSE 8080ENTRYPOINT ["/app/binary"]
Ne jamais mettre de credentials, tokens ou clés dans un Dockerfile — même supprimés dans un layer ultérieur, ils restent visibles via docker history --no-trunc IMAGE. Utiliser BuildKit secrets : RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret.
Exécution rootless
# Installer Docker rootless (daemon sans root)dockerd-rootless-setuptool.sh install# Vérifier que le daemon tourne en rootlesssystemctl --user status dockerdocker info | grep -i rootless# Podman — rootless nativement (drop-in replacement Docker)podman run --rm {{IMAGE}} id# uid=1000(user) gid=1000(user) groups=1000(user) ← pas de root# Forcer un utilisateur non-root à l'exécutiondocker run --user 10001000 {{IMAGE}} id# Vérifier l'utilisateur d'un conteneur en coursdocker inspect {{CONTAINER}} --format='{{.Config.User}}'
Capabilities Linux
# Principe : drop ALL, ajouter uniquement ce qui est nécessairedocker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE {{IMAGE}}# Capabilities couramment nécessaires (et seulement elles)# NET_BIND_SERVICE : écouter sur les ports < 1024# CHOWN : changer le propriétaire de fichiers# SETUID, SETGID : changer l'UID/GID du processus# DAC_OVERRIDE : bypass des permissions sur les fichiers (à éviter)# Capabilities dangereuses — ne JAMAIS accorder en production# CAP_SYS_ADMIN : quasi-root, permet mount, namespace, etc.# CAP_NET_ADMIN : modifier la config réseau de l'hôte# CAP_SYS_PTRACE : attacher à des processus (débogage)# CAP_DAC_OVERRIDE: bypass complet des ACLs fichiers# CAP_SYS_MODULE : charger des modules kernel# Lister les capabilities d'un conteneur en coursdocker inspect {{CONTAINER}} --format='{{.HostConfig.CapAdd}} HostConfigCapDrop# Vérifier les capabilities depuis l'intérieur du conteneurcapsh --printcat /proc/1/status | grep Cap
docker run --privileged donne au conteneur un accès complet à l'hôte : tous les devices, toutes les capabilities, bypass des namespaces. Un conteneur privileged compromis = hôte compromis. Ne jamais utiliser en production, même temporairement.
Seccomp
# Le profil seccomp par défaut de Docker bloque ~44 syscalls dangereux# Vérifier le profil actif d'un conteneurdocker inspect --format='{{.HostConfig.SecurityOpt}}' {{CONTAINER}}# Appliquer un profil seccomp restrictif customdocker run --security-opt seccomp/path/to/profile.json {{IMAGE}}# Désactiver seccomp (à éviter — uniquement pour debug)docker run --security-opt seccompunconfined {{IMAGE}}
{"defaultAction": "SCMP_ACT_ERRNO","architectures": ["SCMP_ARCH_X86_64"],"syscalls": [{"names": ["read", "write", "close", "fstat", "mmap", "mprotect","munmap", "brk", "rt_sigaction", "rt_sigprocmask","exit", "exit_group", "futex", "nanosleep","clock_gettime", "gettimeofday", "getpid", "getuid","getgid", "geteuid", "getegid", "socket", "connect","sendto", "recvfrom", "sendmsg", "recvmsg", "bind","listen", "accept", "accept4", "epoll_create","epoll_create1", "epoll_ctl", "epoll_wait","epoll_pwait", "openat", "open", "stat", "lstat","access", "ioctl", "fcntl", "dup", "dup2", "pipe"],"action": "SCMP_ACT_ALLOW"}]}
AppArmor / SELinux
# AppArmor — profil par défaut Dockerdocker run --security-opt apparmordocker-default {{IMAGE}}# Appliquer un profil AppArmor custom# 1. Créer le profilcat > /etc/apparmor.d/docker-custom << 'EOF'#include <tunables/global>profile docker-custom flagsattach_disconnectedmediate_deleted#include <abstractions/base>networkcapabilityfileumountdeny PROC wdeny /sys/f wklxdeny /sys/fs wklxdeny /sys/fs/c wklxdeny /sys/fs/cg wklxdeny /sys/fs/cgr wklxdeny /sys/firmware/ rwklxdeny /sys/kernel/security/ rwklxEOF# 2. Charger le profilapparmor_parser -r -W /etc/apparmor.d/docker-custom# 3. Appliquerdocker run --security-opt apparmordocker-custom {{IMAGE}}# SELinux — label de type conteneurdocker run --security-opt labeltypecontainer_t {{IMAGE}}# Vérifier les labels SELinuxdocker inspect {{CONTAINER}} --format='{{.HostConfig.SecurityOpt}}'
Réseau
# Pas de réseau si inutiledocker run --network=none {{IMAGE}}# Network Docker custom (isolation entre conteneurs)docker network create --driver bridge --opt comdockernetworkbridgeenable_iccfalse app-networkdocker run --network=app-network {{IMAGE}}# Exposer uniquement sur localhost (jamais 0.0.0.0 en prod)docker run -p 12700180808080 {{IMAGE}}# Inspecter le réseau d'un conteneurdocker inspect {{CONTAINER}} --format='{{json NetworkSettingsNetworks | jq# Désactiver ICC dans daemon.json (Inter-Container Communication)# Voir section daemon.json ci-dessous
--network=host supprime l'isolation réseau du conteneur — il partage la stack réseau de l'hôte. Toute écoute sur un port dans le conteneur est directement exposée sur l'hôte. À éviter absolument en production.
Secrets management
# Docker Secrets (Swarm mode)echo "{{SECRET_VALUE}}" | docker secret create my-secretdocker service create --secret my-secret {{IMAGE}}# Disponible dans le conteneur : /run/secrets/my-secret# BuildKit secrets (build time — ne pas persister dans les layers)docker build --secret idmysecretsrc/secret.txt -t {{IMAGE}}# Dans le Dockerfile :# RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret# Volume read-only pour les secretsdocker run -v /host/secrets/myapp/run/secretsro {{IMAGE}}# HashiCorp Vault Agent sidecar# Vault Agent s'authentifie auprès de Vault et écrit les secrets dans un volume partagé
Ne jamais passer de secrets via variables d'environnement (-e SECRET=value) — docker inspect les expose en clair à quiconque peut exécuter cette commande sur l'hôte. Les variables d'environnement sont aussi visibles dans /proc/PID/environ depuis l'intérieur du conteneur.
Filesystem et volumes
# Root filesystem en lecture seule + tmpfs pour les répertoires qui nécessitent des écrituresdocker run--read-only--tmpfs /tmprwnoexecnosuidsize--tmpfs /var/runrwnoexecnosuidsize{{IMAGE}}# Empêcher l'escalade de privilèges (sudo, setuid)docker run --no-new-privileges {{IMAGE}}# Monter les volumes en lecture seule si possibledocker run -v /host/data/app/dataro {{IMAGE}}# Vérifier les options de sécurité appliquéesdocker inspect {{CONTAINER}} --format='ReadOnlyRootfs HostConfigReadonlyRootfsNoNewPrivileges HostConfigSecurityOptPrivileged HostConfigPrivileged
daemon.json hardening
{"icc": false,"no-new-privileges": true,"userns-remap": "default","live-restore": true,"userland-proxy": false,"log-driver": "json-file","log-opts": {"max-size": "10m","max-file": "3"},"storage-driver": "overlay2","seccomp-profile": "/etc/docker/seccomp-profile.json"}
# Appliquer la configuration# Éditer /etc/docker/daemon.json puis :systemctl restart docker# Vérifier la configuration activedocker info | grep -A5 "Security Options"docker info | grep "Logging Driver"
Checklist sécurité Docker
docker-bench-security (CIS Docker Benchmark) est un script qui audite automatiquement la configuration Docker de l'hôte : daemon, images, conteneurs en cours, sécurité du runtime. Exécuter avec docker run --rm -v /var/run/docker.sock:/var/run/docker.sock docker/docker-bench-security et corriger tous les [WARN].