🎯 Objectif de la semaine
Maîtriser la sécurité des conteneurs Docker : comprendre l'isolation via namespaces et cgroups, implémenter le principe du moindre privilège, et identifier les faiblesses spécifiques aux environnements conteneurisés. Apprendre à déployer des applications dans des "prisons légères" qui limitent radicalement l'impact d'une compromission.
Pourquoi c'est fondamental : 92% des organisations utilisent des conteneurs en production. Les comprendre, c'est comprendre l'infrastructure moderne. Les sécuriser, c'est protéger le cœur de l'entreprise numérique.
Libraries, Binaries] C[Système de fichiers virtuel] end K -->|Namespaces| NS1[PID Namespace
Isolation processus] K -->|Namespaces| NS2[Network Namespace
Isolation réseau] K -->|Namespaces| NS3[Mount Namespace
Isolation fichiers] K -->|Namespaces| NS4[User Namespace
Isolation utilisateurs] K -->|cgroups| CG1[CPU
Limitation ressources] K -->|cgroups| CG2[Memory
Limitation mémoire] K -->|cgroups| CG3[Block I/O
Limitation disque] K -->|Capabilities| CAP[Capabilités Linux
Privilèges granulaires] K -->|SecComp| SC[Filtres Syscall
Blocage appels système] NS1 --> A NS2 --> A NS3 --> A NS4 --> A CG1 --> A CG2 --> A CG3 --> A CAP --> A SC --> A subgraph "⚠️ Vecteurs d'attaque" V1[Privileged Container] V2[Host Mount] V3[Dirty Cow] V4[Image Vulnerabilities] end style A fill:#0a0e17,stroke:#00d4ff,stroke-width:2px style K fill:#11151f,stroke:#9d4edd,stroke-width:2px style V1 fill:#11151f,stroke:#ef4444,stroke-width:2px
📚 Sujet – Théorie (1 heure)
Conteneurs vs Machines Virtuelles : La Révolution de l'Isolation
Les conteneurs ne sont pas des machines virtuelles légères – ce sont des processus isolés partageant le même noyau hôte. Cette différence fondamentale change tout en matière de sécurité.
Machine Virtuelle (VM)
- Hyperviseur complet (Type 1/2)
- Noyau dédié par VM
- Isolation forte mais lourde
- Démarrage : 30-60 secondes
- Mémoire : 1-4 Go par VM
- Attack surface : Hyperviseur + OS complet
Conteneur Docker
- Namespaces Linux (no hyperviseur)
- Noyau partagé avec hôte
- Isolation légère mais efficace
- Démarrage : < 1 seconde
- Mémoire : 10-100 Mo par conteneur
- Attack surface : Kernel Linux + runtime
Les 4 Piliers de la Sécurité Docker
🔒 Namespaces
Isolation de vue
- PID : Processus isolés
- Network : Réseau privé
- Mount : FS virtuel
- UTS : Hostname isolé
- IPC : Mémoire partagée
- User : UID/GID mapping
⚖️ cgroups
Limitation ressources
- CPU : Quotas et limites
- Memory : RAM max
- Block I/O : Accès disque
- Devices : Accès périphériques
- Network : Bandwidth
🎭 Capabilities
Privilèges granulaires
- 38 privilèges Linux
- Root dans container ≠ root hôte
- DROP inutiles, KEEP nécessaires
- Ex: CAP_NET_RAW pour ping
🚫 Seccomp
Filtrage syscalls
- 300+ syscalls Linux
- Profil par défaut Docker bloque 44
- Personnalisation fine possible
- Prévention kernel exploits
Vulnérabilités des Conteneurs : Le Guide du Red Team
| Vecteur | Impact | Détection | Mitigation |
|---|---|---|---|
| Privileged Container | Échappement complet vers hôte | docker ps --format "{{.Names}}: {{.Status}}" |
Jamais --privileged en prod |
| Host Mount | Accès fichiers système hôte | docker inspect --format='{{.Mounts}}' |
Volumes nommés uniquement |
| Kernel Exploit | Dirty Cow, etc. | Mise à jour kernel régulière | User namespaces, seccomp strict |
| Image Vulnerabilities | RCE via packages vulnérables | Trivy, Grype, Clair | Images minimales, scans CI/CD |
| API Docker exposée | Contrôle Docker daemon | Port 2375/2376 exposés | TLS obligatoire, firewall |
Principes de Sécurité Container-Native
🔐 Les 7 Commandements du Conteneur Sécurisé
- Non-root par défaut : USER dans Dockerfile
- Images minimales : Alpine, Distroless, Scratch
- Capabilities dropped : --cap-drop ALL, --cap-add si nécessaire
- Read-only filesystem : --read-only, volumes en écriture
- Ressources limitées : --memory, --cpus, --pids-limit
- Scans réguliers : Intégration CI/CD avec Trivy
- Signatures : Docker Content Trust, Cosign
⚠️ Les 5 Péchés Capitaux du Docker
--privileged: Donne tous les privilèges kernel--net=host: Partage l'espace réseau de l'hôte--pid=host: Voir tous les processus de l'hôte-v /:/host: Monter la racine de l'hôtedocker.sock: Monter le socket Docker dans le conteneur
Une seule de ces erreurs annule toute la sécurité du conteneur.
🔬 Mission pratique – Laboratoire (2 heures)
Architecture du Lab Docker
Docker Engine] --> C1[📦 Conteneur Sécurisé
nginx-secure:8080] H --> C2[📦 Conteneur Vulnérable
nginx-vuln:8081] H --> C3[🔧 Outils Sécurité
Trivy, dive, docker-bench] subgraph "🔒 Isolation Namespaces" C1 C2 end subgraph "🎯 Tests d'Échappement" T1[Test breakout conteneur] T2[Exploit kernel simulé] T3[Scan vulnérabilités] end C1 -.->|Isolation| T1 C2 -.->|Vulnérabilité| T2 C3 --> T3 style C1 fill:#0a0e17,stroke:#00f5a0,stroke-width:2px style C2 fill:#0a0e17,stroke:#ef4444,stroke-width:2px style H fill:#11151f,stroke:#00d4ff,stroke-width:3px
⚠️ Préparation de l'Environnement
🔒 Isolation obligatoire : Exécutez ce lab sur une VM dédiée. Jamais sur votre machine principale avec des données sensibles.
Étapes Détaillées d'Implémentation
Étape 1 : Installation et Configuration Docker
# Installation Docker Engine
sudo apt update
sudo apt install docker.io docker-compose -y
# Configuration sécurité de base
sudo systemctl start docker
sudo systemctl enable docker
# Ajout utilisateur au groupe docker (éviter sudo)
sudo usermod -aG docker $USER
# Déconnexion/reconnexion nécessaire
# Vérification installation
docker --version
docker-compose --version
# Test hello-world
docker run --rm hello-world
🔧 Docker sans sudo : L'ajout au groupe docker donne des privilèges équivalents à root sur le démon Docker. En production, utilisez des mécanismes plus fins (RBAC).
Étape 2 : Création d'une Image Sécurisée
📁 Structure du projet :
docker-lab/
├── secure-app/
│ ├── Dockerfile
│ ├── nginx.conf
│ └── html/
│ └── index.html
├── vulnerable-app/
│ └── Dockerfile
└── docker-compose.yml
# Création structure
mkdir -p ~/mon_lab/docker-lab/{secure-app/html,vulnerable-app}
cd ~/mon_lab/docker-lab
# secure-app/Dockerfile
# Image minimale Alpine
FROM nginx:1.24-alpine
# Création utilisateur non-root
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
# Configuration sécurité Nginx
RUN echo "daemon off;" >> /etc/nginx/nginx.conf && \
echo "user appuser appgroup;" >> /etc/nginx/nginx.conf
# Suppression fichiers inutiles
RUN rm -rf /usr/share/nginx/html/* && \
rm -rf /etc/nginx/conf.d/default.conf
# Copie configuration custom
COPY nginx.conf /etc/nginx/nginx.conf
COPY html/ /usr/share/nginx/html/
# Changer propriétaire fichiers
RUN chown -R appuser:appgroup /usr/share/nginx/html && \
chown -R appuser:appgroup /var/cache/nginx && \
chown -R appuser:appgroup /var/log/nginx
# User non-root
USER appuser
# Exposition port
EXPOSE 8080
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/ || exit 1
# Entrypoint sécurisé
ENTRYPOINT ["nginx"]
CMD ["-c", "/etc/nginx/nginx.conf"]
# secure-app/nginx.conf
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
# Headers sécurité
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
server {
listen 8080;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Sécurité supplémentaire
server_tokens off;
location / {
try_files $uri $uri/ =404;
}
# Blocage accès fichiers sensibles
location ~ /\.(?!well-known) {
deny all;
}
location ~* \.(log|sql|bak|old)$ {
deny all;
}
}
}
Application Sécurisée - PCTAMALOU
🚀 Application Conteneurisée Sécurisée
Déployée avec les meilleures pratiques Docker
- User non-root (appuser:1001)
- Capabilities limitées
- Filesystem read-only
- Scans réguliers
# Build de l'image sécurisée
cd secure-app
docker build -t pctamalou/secure-app:1.0 .
# Inspection de l'image
docker image inspect pctamalou/secure-app:1.0 | jq '.[0].Config.User'
Étape 3 : Déploiement avec Options de Sécurité
# Lancement conteneur sécurisé
docker run -d \
--name secure-app \
--restart unless-stopped \
--read-only \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--security-opt no-new-privileges \
--security-opt seccomp=/etc/docker/seccomp/default.json \
--pids-limit 100 \
--memory 256m \
--cpus 0.5 \
-p 8080:8080 \
-v secure-app-data:/var/cache/nginx \
-v secure-app-logs:/var/log/nginx \
pctamalou/secure-app:1.0
# Vérification
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
curl http://localhost:8080
Étape 4 : Création d'un Conteneur Vulnérable (À NE PAS REPRODUIRE EN PROD)
# vulnerable-app/Dockerfile
FROM ubuntu:latest
RUN apt update && apt install -y nginx
# Mauvaise pratique : root explicite
USER root
# Exposition tous les ports
EXPOSE 80 443 22
CMD ["nginx", "-g", "daemon off;"]
# Build et lancement vulnérable
cd ../vulnerable-app
docker build -t vulnerable-app .
# Lancement avec TOUTES les mauvaises pratiques
docker run -d \
--name vuln-app \
--privileged \
--net=host \
--pid=host \
-v /:/host \
-p 8081:80 \
vulnerable-app
# Vérification de la vulnérabilité
docker exec vuln-app whoami # root
docker exec vuln-app ls /host # Accès à tout l'hôte !
Étape 5 : Scanning de Sécurité
# Installation Trivy
sudo apt install trivy -y
# Scan image vulnérable
trivy image vulnerable-app
# Scan image sécurisée
trivy image pctamalou/secure-app:1.0
# Scan avec sortie JSON pour analyse
trivy image --format json --output trivy-report.json nginx:alpine
# Benchmark sécurité Docker
docker run -it --net host --pid host --userns host --cap-add audit_control \
-e DOCKER_CONTENT_TRUST=1 \
-v /etc:/etc:ro \
-v /usr/bin/docker-containerd:/usr/bin/docker-containerd:ro \
-v /usr/bin/docker-runc:/usr/bin/docker-runc:ro \
-v /lib/systemd/system:/lib/systemd/system:ro \
-v /sbin/iptables:/sbin/iptables:ro \
-v /var/lib:/var/lib:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--label docker_bench_security \
docker/docker-bench-security
Étape 6 : Tests d'Échappement (Simulation)
🎯 Défi de Sécurité
Essayez de "sortir" des conteneurs :
# Test 1 : Conteneur sécurisé (devrait échouer)
docker exec secure-app sh -c "whoami; cat /etc/shadow"
# Test 2 : Conteneur vulnérable (devrait réussir)
docker exec vuln-app sh -c "whoami; ls /host/etc/shadow"
# Test 3 : Exploitation kernel (simulée)
# Dirty Cow exploit (CVE-2016-5195) - À tester seulement en lab isolé
# docker run --privileged -it ubuntu bash
# apt update && apt install -y gcc
# wget https://raw.githubusercontent.com/dirtycow/dirtycow.github.io/master/dirtyc0w.c
# gcc -pthread dirtyc0w.c -o dirtyc0w
Documentez les différences de comportement.
Étape 7 : Docker Compose pour Orchestration Sécurisée
# docker-compose.yml
version: '3.8'
services:
secure-app:
build: ./secure-app
image: pctamalou/secure-app:1.0
container_name: secure-app
restart: unless-stopped
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
mem_limit: 256m
cpus: 0.5
ports:
- "8080:8080"
volumes:
- secure-app-data:/var/cache/nginx
- secure-app-logs:/var/log/nginx
networks:
- secure-net
vulnerable-app:
build: ./vulnerable-app
container_name: vulnerable-app
privileged: true
network_mode: host
pid: host
volumes:
- /:/host
ports:
- "8081:80"
# À NE PAS UTILISER EN PRODUCTION
volumes:
secure-app-data:
secure-app-logs:
networks:
secure-net:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/24
Exercice Avancé : Policy Engine avec OPA/Gatekeeper
🎯 Défi Expert (Kubernetes)
Pour ceux qui veulent aller plus loin :
# Exemple de ConstraintTemplate OPA Gatekeeper
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {"app", "version"}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}
Créez une politique qui empêche les déploiements avec privileged: true.
📋 Objectif de la semaine – Checklist de validation
✅ Compétences techniques acquises
- Dockerfile avec utilisateur non-root
- Conteneur sécurisé avec --read-only, --cap-drop
- Scan d'image avec Trivy et analyse résultats
- Docker Compose avec paramètres sécurité
- Différenciation conteneur sécurisé/vulnérable
🧠 Changement mental accompli
- Compréhension profonde namespaces/cgroups vs hyperviseur
- Habitude de toujours vérifier --privileged, --net=host
- Vision des conteneurs comme "prisons légères" pas "VM légères"
- Conscience qu'un kernel vulnérable = tous conteneurs vulnérables
💪 Pour aller plus loin :
Explorez gVisor (runtime utilisateur), Kata Containers (conteneurs VM), ou Firecracker (microVM). Testez un breakout réel en environnement contrôlé avec docker-bench-security.
📚 Ressources pour Approfondir
Standards et Guides :
- CIS Docker Benchmark : Standards de sécurité
- NIST SP 800-190 : Container Security Guide
- OWASP Docker Top 10 : Vulnérabilités courantes
- Kubernetes Hardening Guide : NSA/CISA
⚠️ Production Ready Checklist
- Runtime : containerd > Docker Engine
- Orchestration : Kubernetes avec Pod Security Policies
- Registry : Harbor avec scanning automatique
- Monitoring : Falco pour détection runtime
- Secrets : HashiCorp Vault, Kubernetes Secrets
🔮 Frère d'armes,
Tu viens d'apprendre à construire des prisons numériques.
Les conteneurs Docker ne sont pas une magie moderne – ce sont des mécanismes d'isolation Linux anciens habillés d'une interface simple. Namespaces, cgroups, capabilities : ces technologies existent depuis des années, mais Docker les a rendues accessibles.
Cette semaine, tu as vu la différence fondamentale entre une VM et un conteneur. L'une isole par virtualisation complète, l'autre par séparation de vue. L'une est lourde mais forte, l'autre légère mais dépendante du kernel.
Mais voici la leçon la plus importante : la sécurité des conteneurs est une question de configuration, pas de technologie. Le même moteur Docker peut créer une prison impénétrable ou une porte grande ouverte vers l'hôte. --privileged annule tout. --net=host brise l'isolation. -v /:/host donne les clés du royaume.
Tu n'es plus un simple utilisateur de Docker. Tu es devenu un architecte de conteneurs sécurisés. Tu sais pourquoi Alpine est plus sûr qu'Ubuntu, pourquoi non-root est obligatoire, pourquoi les capabilities doivent être retirées.
Dans la semaine 38, nous irons plus loin dans le cloud. Nous apprendrons à sécuriser les infrastructures Kubernetes, à configurer les Network Policies, à implémenter les Pod Security Standards. Nous passerons des conteneurs isolés aux orchestrations sécurisées.
Mais pour l'instant, pratique. Recrée cet exercice avec une application réelle (WordPress, Node.js, Python). Applique les mêmes principes. Scan, teste, améliore.
Un Gardien qui maîtrise les conteneurs ne laisse jamais une application vulnérable menacer son hôte.
— Platon-Y