Exercice 04 : Appliquer les bonnes pratiques de sécurité Docker
🎯 Objectifs
À la fin de cet exercice, vous serez capable de :
- ✅ Créer un utilisateur non-root dans un Dockerfile avec
adduser/USER - ✅ Monter le système de fichiers en lecture seule avec
read_only: true - ✅ Empêcher l'escalade de privilèges avec
security_opt: no-new-privileges - ✅ Protéger les fichiers sensibles avec
.dockerignore - ✅ Injecter des secrets via des variables d'environnement sans les exposer dans l'image
Durée estimée : 20 min | Difficulté : ⭐⭐⭐⭐☆
📖 Contexte
Par défaut, les processus dans un conteneur tournent en root. Si l'application est compromise, l'attaquant dispose de tous les droits dans le conteneur et peut potentiellement s'échapper vers l'hôte. Appliquer le principe du moindre privilège réduit considérablement la surface d'attaque.
📋 Énoncé
Sécurisez une application web Python Flask en appliquant les quatre couches de protection : utilisateur non-root, filesystem en lecture seule, restriction des privilèges système et exclusion des fichiers sensibles du contexte de build.
🧭 Déroulement
Tâche 1 : Créer un utilisateur non-root dans le Dockerfile
Dans votre Dockerfile, après avoir installé les dépendances, créez un utilisateur appuser (uid 1001) et basculez vers cet utilisateur avant le CMD.
Indice :
`dockerfileRUN addgroup -S appgroup && adduser -S -G appgroup -u 1001 appuser
USER appuser
`Sur des images Debian/Ubuntu, remplacez par
addgroup --systemetadduser --system.
Vérification : docker run --rm <image> whoami affiche appuser, pas root.
Tâche 2 : Configurer .dockerignore
Créez un .dockerignore qui exclut .env, *.key, *.pem, __pycache__, .git et node_modules du contexte de build.
Indice : Chaque ligne du
.dockerignoreest un pattern glob, comme un.gitignore.
Vérification : Créez un fichier .env avec SECRET=topsecret dans le dossier. Buildez l'image et vérifiez que docker run --rm <image> cat /app/.env échoue avec "No such file".
Tâche 3 : Restreindre le conteneur dans docker-compose.yml
Dans docker-compose.yml, ajoutez sur le service applicatif :
read_only: truepour monter le système de fichiers en lecture seulesecurity_opt: ["no-new-privileges:true"]pour bloquer l'escalade de privilèges
Ajoutez un volume tmpfs sur /tmp pour permettre les écritures temporaires nécessaires à l'application.
Indice :
tmpfs: [/tmp]monte/tmpen mémoire, contournant leread_onlypour ce seul répertoire.
Vérification : docker compose run app touch /etc/test échoue avec "Read-only file system".
Tâche 4 : Injecter les secrets par variable d'environnement
Définissez la variable DATABASE_URL comme variable d'environnement dans docker-compose.yml en la lisant depuis un fichier .env local (non commité). Ne codez jamais la valeur directement dans le YAML.
Indice : Docker Compose charge automatiquement le fichier
.envdu répertoire courant. Référencez la variable avec${DATABASE_URL}dans le YAML.
Vérification : docker compose config affiche la valeur interpolée sans la stocker dans l'image.
🗂️ Mini-Projet
Complétez les points suivants :
docker run --rm <image> whoamiretourneappuser.envest absent de l'image (vérifiable avecdocker run --rm <image> ls -la /app)docker compose run app touch /etc/pwnedéchoue (read_only)docker inspect <container>montre"Privileged": falseet"ReadonlyRootfs": true- Aucun secret n'est codé en dur dans
Dockerfilenidocker-compose.yml