En tant qu'administrateur système ou développeur, la sécurité de vos serveurs est une priorité absolue. Vous avez probablement mis en place UFW (Uncomplicated Firewall) sur vos machines Ubuntu/Debian, un pare-feu simple et efficace. Vous avez méticuleusement défini vos règles : tout est bloqué par défaut, à l'exception des ports SSH (22), HTTP (80) et HTTPS (443). Votre forteresse numérique semble imprenable.
Puis, vient le moment de déployer une nouvelle application avec Docker. Vous lancez votre conteneur en exposant le port 3000, par exemple avec docker run -p 3000:3000 my-app
. Et là, c'est le drame. En testant depuis l'extérieur, vous vous apercevez avec horreur que le port 3000 est parfaitement accessible, comme si vos règles UFW n'existaient pas.
Rassurez-vous, vous n'êtes pas le premier à qui cela arrive. Ce comportement, bien que déroutant, est en réalité attendu. Docker, pour gérer son réseau, modifie directement les règles iptables
du noyau Linux, et ce, à un niveau qui court-circuite les règles classiques d'UFW.
Cet article a pour but de démystifier ce conflit apparent et de vous fournir une solution robuste et sécurisée pour faire cohabiter Docker et UFW. Nous allons reprendre le contrôle de notre pare-feu, sans sacrifier la flexibilité de Docker.
I. Comprendre le Conflit : Pourquoi Docker Ignore UFW ?
Avant de corriger le problème, il est crucial de comprendre pourquoi il se produit. Le diable, comme souvent en informatique, se cache dans les détails de l'implémentation réseau.
A. Le fonctionnement de base d'UFW
UFW, comme son nom l'indique, est une interface simplifiée pour iptables
. iptables
est l'outil en ligne de commande qui permet de configurer le pare-feu du noyau Linux (Netfilter).
UFW gère principalement la table filter
d'iptables, et plus spécifiquement les chaînes INPUT
(trafic entrant destiné au serveur lui-même), FORWARD
(trafic routé à travers le serveur) et OUTPUT
(trafic sortant du serveur).
Quand vous tapez ufw allow 80
, UFW ajoute une règle à la chaîne INPUT
pour accepter le trafic TCP sur le port 80. Simple et efficace. Le problème, c'est que Docker ne joue pas tout à fait dans la même cour.
B. La magie noire de Docker : Manipulation directe d'iptables
Pour que les conteneurs puissent communiquer avec le monde extérieur et inversement, Docker effectue une manipulation avancée du réseau. Lorsqu'on publie un port (-p 8080:80
), Docker met en place un mécanisme de NAT (Network Address Translation).
Il le fait en ajoutant des règles directement dans la table nat
d'iptables, et plus précisément dans la chaîne PREROUTING
. Cette chaîne est traitée par le noyau avant la chaîne INPUT
gérée par UFW.
Imaginez une boîte de nuit avec un videur à l'entrée principale (UFW). Il filtre tout le monde. Mais les VIP (le trafic Docker) ont une entrée dérobée qui les mène directement à l'intérieur, sans passer par le videur. C'est exactement ce qui se passe ici.
Techniquement, Docker crée une chaîne personnalisée nommée DOCKER
et ajoute une règle dans PREROUTING
qui dit : "Tout paquet destiné à un port que je gère, envoie-le directement vers ma chaîne DOCKER
pour traitement". Dans cette chaîne DOCKER
, une règle DNAT
(Destination NAT) réécrit l'adresse IP de destination pour la faire correspondre à l'IP interne du conteneur.
Le paquet est alors routé vers le conteneur via la chaîne FORWARD
(et non INPUT
), contournant ainsi les règles ufw deny
par défaut.
C. La conséquence : Une faille de sécurité involontaire
Le résultat est que n'importe quel port que vous publiez avec docker run -p ...
devient accessible publiquement, peu importe la configuration restrictives de vos règles UFW. C'est une porte dérobée que vous avez créée sans le savoir, ce qui annule une grande partie de l'intérêt d'avoir un pare-feu configuré avec soin.
II. La Solution Recommandée : Configurer UFW pour Gérer Docker
La solution la plus propre et la plus recommandée par la communauté consiste à modifier la configuration d'UFW pour qu'il soit "conscient" de Docker. Nous allons ajouter des règles qui s'intègrent proprement dans le flux de traitement d'iptables pour filtrer le trafic Docker.
A. Prérequis et mise en situation
Assurez-vous d'avoir les éléments suivants :
- Un serveur Linux (Ubuntu 22.04 LTS ou plus récent est idéal).
- Un accès root ou
sudo
. - Docker et Docker Compose installés (
docker --version
doit fonctionner). - UFW installé et activé (
sudo ufw status
doit retournerStatus: active
).
Pour illustrer le problème, mettons en place un scénario de test :
- Vérifiez l'accès : Depuis une machine externe (ou même votre propre machine si le serveur est local), essayez d'accéder à
http://<IP_DU_SERVEUR>:8080
. Vous verrez la page d'accueil de Nginx, prouvant que le port est ouvert malgré la règledeny incoming
d'UFW.
Nettoyez le conteneur de test :
docker stop ufw-test
Lancez un conteneur Nginx en exposant un port non autorisé (ex: 8080) :
docker run --rm -d --name ufw-test -p 8080:80 nginx
--rm
est pratique pour que le conteneur soit supprimé automatiquement à l'arrêt.
Configurez UFW de manière restrictive :
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh # Ou le port que vous utilisez pour SSH
sudo ufw allow http
sudo ufw allow https
sudo ufw enable # Si ce n'est pas déjà fait
B. Étape par étape : Modification du fichier de règles d'UFW
Docker, dans sa grande sagesse, a prévu un point d'entrée pour que les administrateurs puissent insérer leurs propres règles : la chaîne DOCKER-USER
. Les règles de cette chaîne sont exécutées avant toute règle ajoutée par Docker. C'est notre porte d'entrée pour reprendre le contrôle.
Nous allons modifier le fichier /etc/ufw/after.rules
. Comme son nom l'indique, les règles de ce fichier sont appliquées après les règles standards d'UFW. C'est l'endroit parfait pour ajouter notre logique.
Ajoutez le bloc de règles suivant à la toute fin du fichier. Ne modifiez rien d'autre.
# BEGIN UFW AND DOCKER
*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN -s 172.17.0.0/16
-A DOCKER-USER -j RETURN -s 172.18.0.0/16
-A DOCKER-USER -j RETURN -s 172.19.0.0/16
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j RETURN
COMMIT
# END UFW AND DOCKER
Ouvrez le fichier avec des privilèges sudo
:
sudo nano /etc/ufw/after.rules
Avertissement Important
Avant de sauvegarder, assurez-vous de comprendre ce que vous copiez-collez. Une mauvaise règleiptables
peut vous couper l'accès à votre serveur. Le bloc ci-dessus est une version sécurisée et éprouvée, mais la prudence est de mise.
- Sauvegardez le fichier et fermez l'éditeur (Ctrl+X, puis Y, puis Entrée dans
nano
).
C. Autoriser le trafic légitime vers les conteneurs
Maintenant que nous avons bloqué par défaut tout le trafic vers les conteneurs, il faut créer des exceptions. Pour cela, nous n'utiliserons pas ufw allow
mais ufw route allow
.
Pour autoriser l'accès au port 8080 de notre conteneur de test pour tout le monde :
sudo ufw route allow proto tcp from any to any port 8080
Si vous voulez être plus restrictif et n'autoriser qu'une seule adresse IP (par exemple, 88.88.88.88
) :
Bash
sudo ufw route allow proto tcp from 88.88.88.88 to any port 8080
Note : Pourquoiufw route allow
? La commandeufw allow
ajoute des règles à la chaîneINPUT
. Comme nous l'avons vu, le trafic Docker passe par la chaîneFORWARD
. La commandeufw route allow
est spécifiquement conçue pour ajouter des règles à la chaîneFORWARD
, ce qui est exactement ce dont nous avons besoin pour autoriser le trafic routé vers nos conteneurs.
D. Rechargement des services et vérification finale
Pour que les changements soient pris en compte, nous devons recharger UFW et redémarrer Docker.
- Vérifiez à nouveau l'accès :
- Essayez d'accéder à
http://<IP_DU_SERVEUR>:8080
depuis une machine externe non autorisée. La connexion devrait échouer (timeout). Victoire ! - Si vous aviez autorisé une IP spécifique, essayez d'accéder depuis cette IP. La connexion devrait fonctionner.
- Essayez d'accéder à
Relancez notre conteneur de test :Bash
docker run --rm -d --name ufw-test -p 8080:80 nginx
Redémarrez le service Docker :Bash
sudo systemctl restart docker
Rechargez UFW :Bash
sudo ufw reload
Vous avez maintenant un système où UFW contrôle l'accès à tous les ports, y compris ceux publiés par Docker.
III. Alternatives et Bonnes Pratiques
La méthode décrite ci-dessus est la plus recommandée. Cependant, il est bon de connaître les alternatives et de garder à l'esprit quelques bonnes pratiques générales.
A. L'alternative complexe : Désactiver la gestion d'iptables par Docker
Il est possible de dire à Docker d'arrêter complètement de toucher à iptables
. Cela vous donne le contrôle total, mais vous rend également responsable de toute la configuration réseau nécessaire au bon fonctionnement des conteneurs.
Pour ce faire, éditez ou créez le fichier /etc/docker/daemon.json
:
JSON
{
"iptables": false
}
Ensuite, redémarrez le service Docker.
Avertissement : Approche réservée aux experts Cette méthode est fortement déconseillée pour la majorité des cas d'usage. En désactivant la gestion d'iptables par Docker, vous perdez non seulement l'exposition des ports (-p
), mais aussi toute la connectivité réseau entre les conteneurs et internet. Vous devrez mettre en place manuellement des règles de NAT et de MASQUERADE complexes. Cette option n'est viable que si vous avez une expertise très pointue eniptables
et un besoin très spécifique.
B. Utiliser un script tiers : ufw-docker
Pour ceux qui trouvent la manipulation manuelle des fichiers de configuration intimidante, il existe des projets communautaires qui automatisent ce processus. Le plus connu est ufw-docker
.
Ce script, une fois installé, s'occupe de :
- Modifier
after.rules
pour vous. - Surveiller les événements Docker (démarrage/arrêt de conteneurs).
- Ajouter et supprimer dynamiquement les règles
ufw route
nécessaires lorsque vous exposez un port.
Vous pouvez le trouver sur GitHub :chaifeng/ufw-docker.
C'est une excellente alternative qui simplifie la gestion au quotidien, bien qu'elle ajoute une dépendance externe à votre système. L'installation est simple, généralement une commande wget
et un install
.
(Une capture d'écran montrant la sortie d'une commande ufw-docker allow my-container 80
pourrait être insérée ici pour illustrer la simplicité d'utilisation).
C. Bonnes pratiques de sécurité avec Docker
Corriger la cohabitation avec UFW est une étape cruciale, mais la sécurité de Docker ne s'arrête pas là :
- Principe du moindre privilège : N'exposez que les ports absolument nécessaires. Si une base de données n'a besoin de communiquer qu'avec votre backend applicatif, placez-les sur le même réseau Docker privé et n'exposez pas le port de la base de données sur l'hôte.
- Socket Docker : Ne montez jamais le socket Docker (
/var/run/docker.sock
) dans un conteneur, sauf si vous avez une confiance absolue en l'image. L'accès au socket équivaut à un accèsroot
sur l'hôte. - Réseaux personnalisés : Séparez vos applications dans des réseaux Docker dédiés (
docker network create mon_reseau
) pour isoler les conteneurs les uns des autres. - Scannez vos images : Utilisez des outils comme Trivy ou Docker Scout pour scanner vos images à la recherche de vulnérabilités connues (CVEs) avant de les déployer.
Récapitulatif de la section III Bien que la modification deafter.rules
soit la méthode de choix, des alternatives existent. La désactivation de la gestion d'iptables par Docker est puissante mais dangereuse et complexe. L'utilisation d'un script commeufw-docker
peut automatiser et simplifier le processus. Ces solutions techniques doivent s'accompagner de bonnes pratiques de sécurité générales pour assurer une protection complète de votre environnement conteneurisé.
Pour aller plus loin
Pour approfondir votre compréhension des mécanismes en jeu, voici quelques ressources précieuses :
- Documentation officielle de Docker sur le réseau :Docker container networking
- Documentation officielle de Docker sur l'intégration avec
iptables
:Docker and iptables - Documentation d'UFW (Ubuntu Community) :UFW - Uncomplicated Firewall
- Le projet
ufw-docker
sur GitHub :chaifeng/ufw-docker - Un tutoriel très détaillé sur
iptables
:The Beginner's Guide to iptables
Conclusion
La cohabitation entre Docker et UFW, bien que source de confusion initiale, est un problème parfaitement soluble. Nous avons vu que le comportement par défaut de Docker, qui manipule iptables
directement, est conçu pour la fonctionnalité mais peut créer une brèche de sécurité en contournant les règles d'UFW.
En résumé, voici les points clés à retenir :
- Le problème : Docker modifie la table
nat
d'iptables via la chaînePREROUTING
, ce qui est traité avant la chaîneINPUT
gérée par UFW. - La solution : La méthode la plus fiable est d'éditer le fichier
/etc/ufw/after.rules
pour insérer des règles de filtrage dans la chaîneDOCKER-USER
. - La mise en pratique : Une fois la configuration en place, le trafic vers les conteneurs est bloqué par défaut. On utilise ensuite
ufw route allow proto tcp from ... to any port ...
pour ouvrir spécifiquement les ports nécessaires. - Les alternatives : Des scripts comme
ufw-docker
peuvent automatiser ce processus, tandis que la désactivation complète de la gestion d'iptables par Docker est une option réservée aux experts.
En appliquant la méthode décrite dans cet article, vous pouvez profiter du meilleur des deux mondes : la puissance et la flexibilité de la conteneurisation avec Docker, et la tranquillité d'esprit offerte par un pare-feu robuste et correctement configuré avec UFW. Vous avez désormais repris le contrôle total de la sécurité réseau de votre hôte.