<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>nftables - Matteo Cisilino</title>
	<atom:link href="https://www.cisilino.com/fr/tag/nftables-fr/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.cisilino.com/fr/</link>
	<description>Guide su WordPress, server e sicurezza</description>
	<lastBuildDate>Thu, 29 Oct 2026 19:01:24 +0000</lastBuildDate>
	<language>fr-FR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.4</generator>
	<item>
		<title>MySQL 5.7 dans Docker protégé par nftables</title>
		<link>https://www.cisilino.com/fr/2026/10/17/mysql-5-7-docker-nftables-pare-feu/</link>
					<comments>https://www.cisilino.com/fr/2026/10/17/mysql-5-7-docker-nftables-pare-feu/#respond</comments>
		
		<dc:creator><![CDATA[matteocisilino]]></dc:creator>
		<pubDate>Sat, 17 Oct 2026 08:10:45 +0000</pubDate>
				<category><![CDATA[Serveurs et bases de données]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[nftables]]></category>
		<category><![CDATA[pare-feu]]></category>
		<guid isPermaLink="false">https://www.cisilino.com/?p=273</guid>

					<description><![CDATA[<p>Comment faire tourner MySQL 5.7 dans Docker sur un serveur moderne sans l'exposer à internet : docker-compose, port fermé et pare-feu nftables.</p>
<p>The post <a href="https://www.cisilino.com/fr/2026/10/17/mysql-5-7-docker-nftables-pare-feu/">MySQL 5.7 dans Docker protégé par nftables</a> first appeared on <a href="https://www.cisilino.com/fr/">Matteo Cisilino</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">Préambule : MySQL 5.7 dans Docker</h2>
<p>Nous avions besoin d&rsquo;une base <strong>MySQL 5.7 dans Docker</strong>. Le serveur, lui, tourne pour le reste avec une distribution récente. Certaines applications héritées ne sont pas compatibles avec MySQL 8. Les raisons : changement du plugin d&rsquo;authentification par défaut, modes SQL plus stricts, nouveaux mots réservés. Les migrer rapidement n&rsquo;était pas envisageable.</p>
<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" src="https://www.cisilino.com/wp-content/uploads/2026/10/mysql-5-7-docker-nftables-pare-feu.webp" alt="MySQL 5.7 dans Docker : une base de données dans un cadre à contour orange en pointillés, avec un cercle et une barre" width="1200" height="630"/></figure>
<p>Installer une version aussi ancienne directement sur l&rsquo;hôte pose plusieurs problèmes. Les dépôts officiels des distributions actuelles ne la proposent plus. Les paquets tiers risquent d&rsquo;entrer en conflit avec le reste du système. Une mise à jour de l&rsquo;OS pourrait la casser.</p>
<p>Nous avons donc décidé de l&rsquo;<strong>isoler dans un conteneur Docker</strong>. L&rsquo;hôte reste propre et à jour. La version de la base est figée et reproductible. Données et configuration se trouvent dans un dossier facile à sauvegarder et à déplacer.</p>
<p>Cet article raconte comment nous l&rsquo;avons configurée et, surtout, les obstacles rencontrés en chemin. Il y a l&rsquo;exposition involontaire du port 3306 et une erreur iptables au redémarrage. Il y a aussi l&rsquo;écriture d&rsquo;un pare-feu avec <strong>nftables</strong> qui ne laisse passer que les IP autorisées.</p>
<blockquote class="wp-block-quote">
<p><strong>Note :</strong> MySQL 5.7 a atteint sa fin de vie (EOL) en octobre 2023. Il ne reçoit plus de correctifs de sécurité. C&rsquo;est une solution de transition : l&rsquo;objectif reste de migrer vers MySQL 8.x ou MariaDB. C&rsquo;est précisément pour cela qu&rsquo;il est encore plus important de ne pas la laisser exposée.</p>
</blockquote>
<hr class="wp-block-separator has-alpha-channel-opacity"/>
<h2 class="wp-block-heading">1. Structure du dossier</h2>
<pre class="wp-block-code"><code>/opt/mysql57/
├── docker-compose.yml
├── .env
├── conf.d/        # configurazioni personalizzate (my.cnf aggiuntivi)
├── data/          # dati del database (volume persistente)
└── logs/
</code></pre>
<p>Le fichier <code>.env</code> contient les identifiants, afin qu&rsquo;ils ne se retrouvent pas dans le compose. Voici son contenu :</p>
<pre class="wp-block-code"><code>TZ=Europe/Rome
MYSQL_ROOT_PASSWORD=una_password_molto_lunga_e_casuale
MYSQL_DATABASE=nome_db
MYSQL_USER=nome_utente
MYSQL_PASSWORD=altra_password_robusta
</code></pre>
<p>Protégez-le avec <code>chmod 600 .env</code>.</p>
<h2 class="wp-block-heading">2. Le docker-compose.yml</h2>
<pre class="wp-block-code"><code>services:
  mysql57:
    image: mysql:5.7
    container_name: mysql57
    restart: unless-stopped
    env_file:
      - .env
    ports:
      - "3306:3306"
    environment:
      TZ: "${TZ:-Europe/Rome}"
      MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}"
      MYSQL_DATABASE: "${MYSQL_DATABASE}"
      MYSQL_USER: "${MYSQL_USER}"
      MYSQL_PASSWORD: "${MYSQL_PASSWORD}"
    command:
      - --default-authentication-plugin=mysql_native_password
    volumes:
      - ./data:/var/lib/mysql
      - ./conf.d:/etc/mysql/conf.d:ro
      - ./logs:/var/log/mysql
    healthcheck:
      test: &#91;"CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$$MYSQL_ROOT_PASSWORD --silent"]
      interval: 20s
      timeout: 5s
      retries: 10
      start_period: 45s
    ulimits:
      nofile:
        soft: 65535
        hard: 65535
</code></pre>
<p>Quelques choix à noter :</p>
<ul class="wp-block-list">
<li><strong><code>restart: unless-stopped</code></strong> : la base redémarre toute seule après un redémarrage du serveur.</li>
<li><strong>Volumes en bind mount</strong> : données, configuration et logs sont sur l&rsquo;hôte. Sauvegardes et migrations se résument donc à des copies de dossiers.</li>
<li><strong>Healthcheck</strong> : permet aux autres services d&rsquo;attendre que MySQL soit vraiment prêt. Le <code>$$</code> sert à faire développer la variable dans le conteneur et non par Compose.</li>
<li><strong><code>ulimits nofile</code></strong> : évite l&rsquo;erreur « too many open files » sous charge.</li>
</ul>
<h2 class="wp-block-heading">3. Premier problème : MySQL 5.7 dans Docker exposé</h2>
<p>La ligne <code>- "3306:3306"</code> équivaut à <code>0.0.0.0:3306:3306</code>. Docker publie le port sur <strong>toutes les interfaces</strong> de l&rsquo;hôte, y compris la publique.</p>
<p>Le plus insidieux : Docker écrit ses propres règles directement dans le pare-feu du noyau. Elles sont évaluées avant celles d&rsquo;UFW ou de firewalld. Avoir un pare-feu actif <strong>ne suffit pas</strong> : le port reste joignable.</p>
<p>Voici les solutions possibles.</p>
<ol class="wp-block-list">
<li><strong>Localhost uniquement</strong> : <code>"127.0.0.1:3306:3306"</code>, si la base ne sert qu&rsquo;à l&rsquo;hôte ou via un tunnel SSH.</li>
<li><strong>Aucun port publié</strong> : si les clients sont d&rsquo;autres conteneurs, un réseau Docker partagé suffit.</li>
<li><strong>IP précise</strong> : publier uniquement sur une adresse d&rsquo;un réseau privé/VPN.</li>
<li><strong>Port public avec filtrage par IP</strong> : notre cas. Un client externe (une instance phpMyAdmin sur un autre serveur) doit pouvoir se connecter.</li>
</ol>
<p>Pour le cas 4, il faut un pare-feu qui filtre correctement le trafic des ports publiés par Docker. La <a href="https://docs.docker.com/engine/network/packet-filtering-firewalls/" target="_blank" rel="noopener">documentation de Docker sur le filtrage des paquets</a> explique comment Docker interagit avec les règles de l&rsquo;hôte.</p>
<h2 class="wp-block-heading">4. Deuxième problème : erreur iptables au démarrage</h2>
<p>Après avoir touché au pare-feu, <code>docker compose up</code> a renvoyé cette erreur.</p>
<pre class="wp-block-code"><code>failed to create network mysql57_default: Error response from daemon:
Failed to Setup IP tables: Unable to enable ACCEPT OUTGOING rule:
(iptables failed: iptables --wait -t filter -A DOCKER-FORWARD -i br-... -j ACCEPT:
iptables: No chain/target/match by that name.
</code></pre>
<p>L&rsquo;erreur ne dépend pas du compose. La chaîne <code>DOCKER-FORWARD</code> (introduite dans les versions récentes de Docker) n&rsquo;existait plus. Elle avait été supprimée par quelque chose qui avait nettoyé les règles <strong>après</strong> le démarrage du démon. Un redémarrage de Docker (<code>systemctl restart docker</code>) la recrée. Mais le problème revient tant que le vrai coupable n&rsquo;est pas éliminé.</p>
<h3 class="wp-block-heading">Le coupable : <code>flush ruleset</code></h3>
<p>Notre <code>/etc/nftables.conf</code> commençait par cette ligne.</p>
<pre class="wp-block-code"><code>flush ruleset
</code></pre>
<p>Cette commande supprime <strong>toutes</strong> les tables nftables, y compris celles créées par Docker. De plus, le fichier contenait une copie statique des règles de Docker. Le nom du bridge (<code>br-278550235c3f</code>) et l&rsquo;IP du conteneur (<code>172.18.0.2</code>) y étaient écrits à la main. Or le nom du bridge change chaque fois que le réseau Compose est recréé. La copie devenait aussitôt obsolète. La table <code>ip nat</code> entrait en conflit avec celle gérée par Docker.</p>
<p><strong>Règle d&rsquo;or :</strong> avec Docker, dans le fichier nftables on n&rsquo;utilise pas <code>flush ruleset</code>. On ne copie pas ses règles. On ne gère que son propre pare-feu, dans des tables dédiées.</p>
<h2 class="wp-block-heading">5. Le pare-feu avec nftables</h2>
<p>Le principe est le suivant pour MySQL 5.7 dans Docker. Les ports publiés par Docker sont réécrits par DNAT, puis transférés par l&rsquo;hôte vers le conteneur. Ils traversent donc la chaîne <strong><code>forward</code></strong>, pas <code>input</code>. Une règle <code>drop</code> dans une chaîne à nous, <code>forward</code>, rejette le paquet. Sa priorité est légèrement plus basse, donc elle est évaluée avant. Les règles <code>accept</code> de Docker n&rsquo;y changent rien.</p>
<p>Pour ne pas toucher aux tables des autres, nous utilisons le motif <code>table ...</code> + <code>delete table ...</code> : il rend le fichier ré-exécutable sans erreur et sans <code>flush</code>.</p>
<p><code>/etc/nftables.conf</code>:</p>
<pre class="wp-block-code"><code>#!/usr/sbin/nft -f

# NIENTE "flush ruleset": cancellerebbe anche le tabelle di Docker

# ---------------- firewall host ----------------
table inet filter
delete table inet filter

table inet filter {

    chain input {
        type filter hook input priority filter; policy drop;
        ct state established,related accept
        iifname "lo" accept
        ip6 nexthdr icmpv6 accept
        iifname "enp7s0" accept                       # rete privata
        ip saddr 203.0.113.20 tcp dport 22 accept     # SSH da host fidato
        ip saddr 203.0.113.30 tcp dport 22 accept     # SSH da VPN
        ip saddr 203.0.113.20 icmp type echo-request accept
        tcp dport { 80, 443 } accept
    }

    chain output {
        type filter hook output priority filter; policy accept;
    }
}

# ---------------- guardia MySQL (porta pubblicata da Docker) ----------------
table ip mysql_guard
delete table ip mysql_guard

table ip mysql_guard {
    set allowed {
        type ipv4_addr
        flags interval
        elements = {
            203.0.113.10        # phpMyAdmin
        }
    }

    chain forward {
        type filter hook forward priority filter - 5; policy accept;
        iifname "enp7s0" accept
        ct status dnat tcp dport 3306 ip saddr @allowed accept
        ct status dnat tcp dport 3306 counter drop
    }
}
</code></pre>
<p>Voici comment fonctionne la garde.</p>
<ul class="wp-block-list">
<li><code>ct status dnat</code> ne sélectionne que les paquets passés par le DNAT, c&rsquo;est-à-dire ceux destinés aux ports publiés par Docker.</li>
<li>La première règle accepte ceux qui sont dans l&rsquo;ensemble <code>allowed</code>. La seconde rejette tous les autres et les compte (<code>counter</code>).</li>
<li>Le trafic local (<code>127.0.0.1</code>) et celui entre conteneurs du même réseau ne passent pas par le forward. Ils ne sont donc pas touchés.</li>
</ul>
<blockquote class="wp-block-quote">
<p><strong>Compatibilité :</strong> dans une première version, nous utilisions <code>ct original proto-dst 3306</code>. Mais certaines versions de <code>nft</code> répondent par <em>« Can&rsquo;t parse symbolic invalid expressions »</em>. Dans le hook <code>forward</code>, le DNAT a déjà eu lieu. Le port du conteneur coïncide avec le port publié (<code>3306:3306</code>). L&rsquo;expression <code>tcp dport 3306</code> combinée à <code>ct status dnat</code> est donc équivalente et fonctionne partout. Si vous changez le mapping (par exemple <code>3307:3306</code>), la règle doit être adaptée au port interne.</p>
</blockquote>
<h3 class="wp-block-heading">Appliquer les modifications sans se verrouiller dehors</h3>
<p>Gardez une session SSH de secours ouverte. Puis lancez les commandes suivantes.</p>
<pre class="wp-block-code"><code>nft -c -f /etc/nftables.conf     # solo verifica della sintassi
nft delete table ip nat          # una tantum: elimina la vecchia copia statica
nft -f /etc/nftables.conf
systemctl restart docker         # Docker ricrea le sue tabelle pulite
cd /opt/mysql57 &amp;&amp; docker compose up -d
</code></pre>
<p>Pour le chargement au démarrage, assurez-vous que le service est activé (<code>systemctl enable --now nftables</code>). Vérifiez aussi que <code>/etc/nftables.conf</code> ne contient pas <code>flush ruleset</code>.</p>
<h3 class="wp-block-heading">Gérer les IP à l&rsquo;exécution</h3>
<pre class="wp-block-code"><code>nft add element ip mysql_guard allowed { 203.0.113.50 }
nft delete element ip mysql_guard allowed { 203.0.113.50 }
</code></pre>
<p>Pour le rendre permanent, mettez aussi à jour l&rsquo;ensemble dans le fichier.</p>
<h2 class="wp-block-heading">6. Vérification</h2>
<p>Depuis le serveur autorisé, testez.</p>
<pre class="wp-block-code"><code>nc -zv IP_PUBBLICO_DB 3306        # deve rispondere "succeeded"
</code></pre>
<p>Depuis une autre IP, testez à nouveau.</p>
<pre class="wp-block-code"><code>nmap -Pn -p 3306 IP_PUBBLICO_DB   # deve risultare "filtered" / timeout
</code></pre>
<p>Sur le serveur, le compteur de la règle de rejet doit augmenter à chaque tentative non autorisée.</p>
<pre class="wp-block-code"><code>nft list chain ip mysql_guard forward
ss -tlnp | grep 3306
</code></pre>
<h2 class="wp-block-heading">7. Seconde barrière : les utilisateurs MySQL</h2>
<p>Le pare-feu ne remplace pas les droits de la base de données. Créez l&rsquo;utilisateur avec un hôte limité à l&rsquo;IP du client.</p>
<pre class="wp-block-code"><code>CREATE USER 'pma_user'@'203.0.113.10' IDENTIFIED BY 'password_robusta';
GRANT ALL ON nome_db.* TO 'pma_user'@'203.0.113.10';
FLUSH PRIVILEGES;
</code></pre>
<p>Si la connexion TCP aboutit mais que MySQL répond <code>Access denied</code>, le problème est ici. Il n&rsquo;est pas dans le pare-feu.</p>
<h2 class="wp-block-heading">8. À retenir</h2>
<ul class="wp-block-list">
<li><strong>Docker et le pare-feu de l&rsquo;hôte ne s&rsquo;ignorent pas</strong>. Comprendre quelle chaîne le trafic traverse (<code>input</code> ou <code>forward</code>) est la clé de tout.</li>
<li><strong>Jamais <code>flush ruleset</code></strong> sur un hôte avec Docker ; utilisez des tables dédiées.</li>
<li><strong>Ne copiez pas les règles générées par Docker</strong> dans vos fichiers : noms de bridge et IP changent.</li>
<li>Si vous avez déjà exposé le port, vérifiez les logs dans <code>./logs</code>. Envisagez aussi de <strong>changer les mots de passe</strong>, surtout celui de root.</li>
<li>Quand c&rsquo;est possible, préférez un <strong>VPN (WireGuard) ou un tunnel SSH</strong> à l&rsquo;exposition publique d&rsquo;une base de données.</li>
<li>Planifiez la <strong>migration</strong> de MySQL 5.7 vers une version prise en charge.</li>
</ul>
<p>Si MySQL refuse de redémarrer après une mise à jour, voyez aussi <a href="https://www.cisilino.com/fr/2026/10/13/mysql-ne-demarre-plus-debian-12/">MySQL qui ne démarre plus sous Debian 12</a>.</p>
<p>Vous avez des questions ou une solution différente pour MySQL 5.7 dans Docker ? Écrivez-le dans les commentaires.</p><p>The post <a href="https://www.cisilino.com/fr/2026/10/17/mysql-5-7-docker-nftables-pare-feu/">MySQL 5.7 dans Docker protégé par nftables</a> first appeared on <a href="https://www.cisilino.com/fr/">Matteo Cisilino</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://www.cisilino.com/fr/2026/10/17/mysql-5-7-docker-nftables-pare-feu/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
