TP
Le but de ce TP est de deployer docker puis de durcir au maximum les conteneurs déployés.
Prérequis : https://doc.oxyhack.com/books/ynov/page/lab-docker-default
Docker Bench - Premier test
Docker Bench est un outil que j’utilise pour évaluer la sécurité de ma configuration Docker. Il exécute une série de tests automatisés basés sur les bonnes pratiques de sécurité, m'aidant à identifier et corriger les failles potentielles dans mon environnement Docker.
Mise en Place :
Clonez Docker Bench :
git clone https://github.com/docker/docker-bench-security.git
Exécutez l’outil :
cd docker-bench-security sudo ./docker-bench-security.sh
Résultat du premier test :

Mise en place des actions de hardenninng (DockerFile)
1. Exécution de Conteneurs avec des Privilèges Réduits
Risques de Sécurité :
Exécuter des conteneurs avec des privilèges root expose l'hôte à des risques élevés en cas de compromission du conteneur.
Solution :
Pour limiter les risques, je vais exécuter mes conteneurs avec un utilisateur non-root. Docker permet de configurer un mappage des utilisateurs entre l'hôte et le conteneur grâce aux user namespaces. Cela permet de mapper l'utilisateur root du conteneur à un utilisateur non-privilégié sur l'hôte.
Comment ça fonctionne ?
Le mappage des utilisateurs permet de faire en sorte que le root dans le conteneur ne soit pas réellement un utilisateur root sur l'hôte. Par exemple, l'utilisateur root du conteneur peut être mappé à un utilisateur non privilégié sur l'hôte. Cela permet de restreindre les permissions d’un conteneur compromis.
Mise en Place :
Activer le mappage des utilisateurs (userns-remap) :
Pour cela, je vais modifier la configuration de Docker en ajoutant l'option
userns-remap
dans le fichier/etc/docker/daemon.json
. Cela va forcer Docker à utiliser des espaces de noms utilisateurs pour mapper les utilisateurs du conteneur à ceux de l'hôte.
{
"userns-remap": "default"
}
sudo systemctl restart docker
Modifier le Dockerfile : Dans mon Dockerfile, je vais créer un utilisateur non-root qui sera utilisé pour exécuter l'application à l'intérieur du conteneur.
# Je commence avec l'image de base python:3.9-slim
FROM python:3.9-slim
# Je crée un utilisateur non-root nommé "appuser"
RUN useradd -m appuser
# Je définis l'utilisateur non-root pour exécuter les commandes suivantes dans le conteneur
USER appuser
# Je définis le répertoire de travail pour l'application dans le conteneur
WORKDIR /home/appuser
# Je copie le fichier requirements.txt en s'assurant que l'utilisateur "appuser" a la propriété du fichier
COPY --chown=appuser:appuser requirements.txt /home/appuser/
# J'installe les dépendances Python sans cache pour alléger l'image
RUN pip install --no-cache-dir -r requirements.txt
# Je copie l'application Python dans le conteneur
COPY --chown=appuser:appuser app.py /home/appuser/
# Je définis la commande à exécuter lors du démarrage du conteneur
CMD ["python", "app.py"]
Vérification : Une fois le conteneur lancé, je peux vérifier que l'application tourne sous un utilisateur non-root avec la commande suivante :
docker exec -it <nom_du_conteneur> whoami

2. Hardenning du compose .yaml
Voici le docker-compose.yml
services:
caddy:
image: caddy:latest # Image Docker pour le serveur Caddy
container_name: caddy # Nom du conteneur
ports:
- "80:80" # Redirection du port 80
volumes:
- "./Caddyfile:/etc/caddy/Caddyfile:ro" # Volume pour la configuration
networks:
- app_network # Réseau du conteneur
user: "1000:1000" # Utilisateur du conteneur
read_only: true # Conteneur en lecture seule
security_opt:
- seccomp:default.json # Sécurisation avec seccomp
- apparmor:docker-default # Profil AppArmor
cap_drop:
- ALL # Suppression de toutes les capacités du conteneur
deploy:
resources:
limits:
memory: 512M # Limite mémoire
cpus: "0.5" # Limite CPU
restart_policy:
condition: on-failure # Redémarrage en cas d'échec
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost/"] # Vérification de l'état du conteneur
backend:
build:
context: ./backend # Dossier du backend
dockerfile: Dockerfile # Fichier Dockerfile pour construire l'image
container_name: flask_app # Nom du conteneur
restart: unless-stopped # Redémarrage sauf si stoppé manuellement
networks:
- app_network # Réseau du conteneur
user: "1000:1000" # Utilisateur du conteneur
read_only: true # Conteneur en lecture seule
security_opt:
- seccomp:default.json # Sécurisation avec seccomp
- apparmor:docker-default # Profil AppArmor
cap_drop:
- ALL # Suppression de toutes les capacités du conteneur
deploy:
resources:
limits:
memory: 512M # Limite mémoire
cpus: "0.5" # Limite CPU
pids: 100 # Limite des processus
restart_policy:
condition: on-failure # Redémarrage en cas d'échec
environment:
- FLASK_ENV=production # Variable d'environnement Flask
healthcheck:
test: ["CMD", "curl", "--fail", "http://localhost/"] # Vérification de l'état du conteneur
redis:
image: redis:alpine # Image Docker pour Redis
container_name: redis # Nom du conteneur
restart: unless-stopped # Redémarrage sauf si stoppé manuellement
networks:
- app_network # Réseau du conteneur
user: "1000:1000" # Utilisateur du conteneur
read_only: true # Conteneur en lecture seule
security_opt:
- seccomp:default.json # Sécurisation avec seccomp
- apparmor:docker-default # Profil AppArmor
cap_drop:
- ALL # Suppression de toutes les capacités du conteneur
deploy:
resources:
limits:
memory: 256M # Limite mémoire
cpus: "0.2" # Limite CPU
pids: 50 # Limite des processus
restart_policy:
condition: on-failure # Redémarrage en cas d'échec
healthcheck:
test: ["CMD", "redis-cli", "ping"] # Vérification de l'état de Redis
networks:
app_network:
driver: bridge # Utilisation du driver bridge pour le réseau
Explication des mesures de sécurité appliquées :
Exécution sous un utilisateur non-root (
user: "1000:1000"
) :Risque : Si un conteneur s'exécute en tant que root, un attaquant ayant compromis le conteneur pourrait potentiellement obtenir un accès complet au système hôte.
Solution : En exécutant les conteneurs sous un utilisateur non-root (ici l'UID 1000 et le GID 1000), on limite les privilèges d'un conteneur, réduisant ainsi le risque de prise de contrôle complète.
Système de fichiers en lecture seule (
read_only: true
) :Risque : Un système de fichiers en écriture peut être manipulé par un attaquant pour injecter du code malveillant ou modifier des fichiers sensibles.
Solution : En rendant le système de fichiers en lecture seule, on empêche toute modification par des processus malveillants, ce qui est particulièrement utile pour les services qui n'ont pas besoin d'écrire sur le disque.
Options de sécurité (
security_opt
) :seccomp:unconfined
: Restreint les appels système que les conteneurs peuvent effectuer. Cela permet de réduire les risques d'exploitation des vulnérabilités dans le noyau.no-new-privileges
: Empêche l'élévation de privilèges. Même si un processus est compromis, il ne pourra pas obtenir des privilèges plus élevés que ceux accordés initialement au conteneur.
Retrait des capacités inutiles (
cap_drop: - ALL
) :Risque : Certains conteneurs peuvent avoir des capacités qui leur permettent d'effectuer des actions non sécurisées, comme manipuler les ressources du noyau ou interagir avec d'autres conteneurs de manière dangereuse.
Solution : En retirant toutes les capacités inutiles, on minimise la surface d'attaque. Cela empêche des actions malveillantes comme l'accès non autorisé au système hôte ou la manipulation de la configuration du conteneur.
Limitation des ressources (CPU et mémoire) (
deploy.resources.limits
) :Risque : Si un conteneur consomme trop de ressources (CPU, mémoire), il peut entraîner un déni de service (DoS) sur le système hôte ou affecter les autres conteneurs en partageant les ressources.
Solution : En limitant la mémoire et l'utilisation du CPU de chaque conteneur, on garantit que chaque service reste dans des limites contrôlées, prévenant ainsi l'épuisement des ressources du système.
Contrôle de santé du conteneur (
healthcheck
) :Risque : Si un service est défaillant et n'est pas détecté, il peut entraîner une perte de service sans alerte.
Solution : Un contrôle de santé vérifie régulièrement si le service fonctionne correctement. Si le service est en panne, Docker peut redémarrer automatiquement le conteneur pour maintenir la disponibilité du service.
3. Isolation Réseau et Restriction du Trafic
Risques de Sécurité : La communication réseau libre entre conteneurs expose le système à des attaques réseau internes.
Solution : Configurez des réseaux Docker et des règles
nftables
pour restreindre le trafic.Mise en Place sur Fedora :
Créez un réseau Docker isolé :
docker network create --driver bridge isolated_network
Associez les conteneurs au réseau isolé :
docker run --network=isolated_network my-container-image
Utilisez
nftables
pour restreindre le trafic :sudo nft add table inet docker sudo nft add chain inet docker input { type filter hook input priority 0 \; } sudo nft add rule inet docker input iifname "docker0" drop
Vérification :
docker network inspect isolated_network
4. Configurer l'Audit des Fichiers Docker
Risque : L'absence d'audit des fichiers Docker peut permettre des modifications malveillantes non détectées, affectant la sécurité du système.
Solution : J'ai configuré auditd pour suivre les accès et modifications aux fichiers critiques de Docker (comme
/var/lib/docker
,/etc/docker
, etc.).
Commandes pour configurer les règles d'audit :
# Les mettres dans ce fichiers pour les rendres permanantes
sudo nano /etc/audit/rules.d/audit.rules
# Vérifier les règles appliquées
sudo auditctl -l
# Recharger les règles
sudo auditctl -R /etc/audit/rules.d/docker.rules
# Génerer un rapport
sudo aureport -f
Voici ma config :
# Surveiller l'exécution de Docker et ses composants principaux :
-w /usr/bin/dockerd -k docker
-w /usr/bin/docker -k docker
-w /usr/bin/docker-containerd -k docker
-w /usr/bin/docker-runc -k docker
-w /usr/bin/containerd -k docker
-w /usr/bin/containerd-shim -k docker
-w /usr/bin/runc -k docker
# Surveiller les fichiers de configuration de Docker et containerd :
-w /etc/docker -k docker
-w /etc/docker/daemon.json -k docker
-w /etc/default/docker -k docker
-w /etc/containerd/config.toml -k docker
# Surveiller les répertoires et fichiers système importants pour Docker :
-w /var/lib/docker -k docker
-w /run/containerd -k docker
-w /run/containerd/containerd.sock -k docker
# Surveiller les services systemd pour Docker et containerd :
-w /usr/lib/systemd/system/docker.service -k docker
-w /usr/lib/systemd/system/docker.socket -k docker
-w /usr/lib/systemd/system/containerd.service -k docker
# Surveiller les répertoires de Docker avec les permissions d'accès :
-w /var/lib/docker -p wa
-w /etc/docker -p wa
Test d'une règle :

5. Autoriser Uniquement les Utilisateurs de Confiance à Contrôler Docker
Risque : Si des utilisateurs non autorisés ont accès à Docker, ils peuvent exécuter des actions malveillantes ou causer des erreurs humaines affectant la sécurité du système.
Solution : Je vais créer un groupe dédié aux administrateurs Docker et m'assurer que seuls les utilisateurs de confiance sont membres de ce groupe.
Commande :
# 1. Créer un groupe 'docker-admins' pour les administrateurs Docker sudo groupadd docker-admins # 2. Ajouter l'utilisateur 'alex' au groupe 'docker-admins' sudo usermod -aG docker-admins alex # 3. Vérifier que 'alex' fait bien partie du groupe groups alex # 4. Vérifier les permissions actuelles du socket Docker ls -l /var/run/docker.sock # 5. Modifier les permissions et le groupe propriétaire du socket Docker sudo chmod 660 /var/run/docker.sock sudo chown root:docker-admins /var/run/docker.sock # 6. Vérifier que les permissions sont appliquées correctement ls -l /var/run/docker.sock # 7. Appliquer immédiatement le changement de groupe sans se déconnecter newgrp docker-admins

6. Activer TLS + les logs
Risque : Si Docker écoute sans cryptage (HTTP non sécurisé), des attaquants peuvent intercepter et manipuler la communication entre le client Docker et le serveur Docker, exposant ainsi le système à des risques.
Solution : Je vais configurer Docker pour utiliser TLS (Transport Layer Security) afin d'assurer que les connexions avec:
{ "tls": true, "tlscert": "/etc/docker/certs/server-cert.pem", "tlskey": "/etc/docker/certs/server-key.pem", "tlsverify": true, "tlscacert": "/etc/docker/certs/ca.pem", "host": "tcp://0.0.0.0:2376" }
J’ai configuré Docker pour envoyer ses logs vers un serveur syslog distant afin de centraliser la gestion des logs. Voici les modifications apportées
{
"log-driver": "syslog", // Configure Docker pour envoyer les logs via le protocole syslog.
"log-opts": {
"syslog-address": "udp://192.168.5.129:514" // Définit l'adresse du serveur syslog distant (IP : 192.168.5.129, port : 514).
}
}
7. Sécurisation des fichiers TLS dans Docker
chown root:root /etc/docker/certs/*.pem
chmod 400 /etc/docker/certs/server-key.pem
chmod 444 /etc/docker/certs/{ca,server-cert}.pem
8. Activer Docker Content Trust (DCT)
Risque : Si Docker Content Trust (DCT) est désactivé, il est possible de télécharger et utiliser des images non vérifiées. Cela expose le système à des images malveillantes.
Solution : Je vais activer Docker Content Trust afin de garantir que seules des images signées et vérifiées sont téléchargées et utilisées.

Commande :
export DOCKER_CONTENT_TRUST=1
# Possibilité de le rendre permanant si on l'ajoute dans bashrc
9. Ne Pas Désactiver le Profil Seccomp par Défaut
Risque : Le profil seccomp est un mécanisme de sécurité qui limite les appels système que les conteneurs peuvent effectuer. Le désactiver augmente la surface d'attaque, permettant à un conteneur d'effectuer des actions potentiellement dangereuses.
Solution : Je vais m'assurer que le profil seccomp est activé et que les conteneurs n'utilisent pas des configurations trop permissives.
Commande pour appliquer le profil seccomp par défaut :
docker run --security-opt seccomp=default.json my-container
10. Créer une Partition Séparée pour Docker
Je n'ai pas pu le faire car la répartition de mes partitions ne laissait pas la place à l'utilisation d'une nouvelle partition.
Risque : Si Docker utilise la même partition que le système d'exploitation, une surcharge de l'espace disque utilisé par Docker peut impacter la performance et la stabilité du système.
Solution : Pour limiter ce risque, je vais créer une partition dédiée pour Docker, en particulier pour le dossier
/var/lib/docker
où Docker stocke ses images et conteneurs.
Résultat après hardenning
Nous pouvons observer que ces modifications ont permis d’améliorer drastiquement mon score afin d’atteindre un niveau de sécurité plus qu’acceptable. Le risque de continuer trop le hardening serait de provoquer des conflits de configuration, de restreindre excessivement certaines fonctionnalités nécessaires au bon fonctionnement des services, ou de compliquer la gestion et la maintenance des systèmes. Il est donc important de trouver un équilibre entre sécurité et fonctionnalité.

Last updated