Docker et NFTtables

Il y a un moment déjà, j’avais critiqué la manière dont Docker interagissait par défaut au niveau réseau. Rien n’a changé ces dernières années sur ce point.

Ce qui a changé par contre (autre le fait qu’on ne devrait plus parler de Docker, mais de runC, de rkt, de k8s, … ou de Docker) c’est que la plupart des systèmes ont (enfin) migré du vénérable IPTables vers NFTables. C’est là que le bât blesse, les API de gestion des conteneurs travaillent encore majoritairement avec IPTables uniquement.

Il est néanmoins possible de faire cohabiter les 2, au prix d’une configuration NFTables un peu moins simple et performante, pour permettre à iptables-nft de correctement transformer les règles IPTables demandées par vos conteneurs en règles NFTables.

Ci-dessous une configuration minimaliste compatible, ce qui importe ici, c’est de nommer ses chaines comme avec IPTables et de séparer IPv4 d’IPv6 (donc on ne peut pas utiliser les tables inet) :

#!/usr/sbin/nft -f

define SRVaddrV4 = 198.51.100.1
define SRVaddrV6 = 2001:db8::/32

flush ruleset

table ip filter {
chain INPUT {
type filter hook input priority 0; policy drop;
ct state invalid counter drop comment « drop invalid packets »
ct state {established, related} counter accept comment « accept all established or related »
iifname lo accept comment « accept loopback if »
iifname != lo ip daddr 127.0.0.0/8 counter drop comment « drop connections to loopback not coming from loopback v4 »
ip protocol icmp counter accept comment « Accept all icmp types v4 »
ip saddr $SRVaddrV4 counter accept comment « Accept all from server »

tcp dport {80, 443} counter accept comment « Accept HTTP/HTTPS »

counter comment « count accepted packets »
drop comment « Final DROP »
}
chain FORWARD {
type filter hook forward priority 0; policy drop;
counter comment « count dropped packets »
}
chain OUTPUT {
type filter hook output priority 0; policy accept;
counter comment « count accepted packets »
}
}

table ip6 filter {
chain INPUT {
type filter hook input priority 0; policy drop;
ct state invalid counter drop comment « drop invalid packets »
ct state {established, related} counter accept comment « accept all established or related »
iifname lo accept comment « accept loopback if »
iifname != lo ip6 daddr ::1/128 counter drop comment « drop connections to loopback not coming from loopback v6 »
ip6 nexthdr icmpv6 counter accept comment « Accept all icmp types v6 »
ip6 saddr $SRVaddrV6 counter accept comment « Accept all from server »

tcp dport {80, 443} counter accept comment « Accept HTTP/HTTPS »

counter comment « count accepted packets »
drop comment « Final DROP »
}
chain FORWARD {
type filter hook forward priority 0; policy drop;
counter comment « count dropped packets »
}
chain OUTPUT {
type filter hook output priority 0; policy accept;
counter comment « count accepted packets »
}
}

table ip nat {
chain PREROUTING {
type nat hook prerouting priority -100; policy accept;
}
chain POSTROUTING {
type nat hook postrouting priority 100; policy accept;
}
chain INPUT {
type nat hook input priority -100; policy accept;
}
chain OUTPUT {
type nat hook output priority -100; policy accept;
}
}

Avec cette base, les règles nécessaires pour vos conteneurs devraient fonctionner sans interférer avec vos propres règles.

nb : si vous devez recharger ce fichier, pensez à relancer les services de conteneurs (docker.service par exemple) afin qu’ils réinjectent leurs règles de base.

On pourrait aller plus loin en ajoutant directement les tables nécessaires, ce qui permettrait de n’avoir qu’à relancer les conteneurs, mais je ne trouve pas ça portable ni plus efficace. Si le downtime n’est pas une option, injectez vos règles (nft add/delete …) directement sans toucher à la configuration.

sources :

  • https://blog.ghostinashell.com/linux/nftables/2020/03/07/nftables.html
  • https://ehlers.berlin/blog/nftables-and-docker/

Docker et les réseaux

Docker est probablement l’une des « technologies » les plus importantes de ces dernières années. Bien que s’appuyant sur des briques et des principes qui existaient depuis longtemps (en particulier LXC), l’équipe de Docker a réussi à s’imposer là ou tous les autres ont échoué, en grande partie grâce à sa simplicité (de façade).

Mais en essayant de tout simplifier pour les débutants ou les développeurs, ils ont parfois fait des choix techniques plus que discutables.

Pour un développeur qui travail dans son coin ce n’est pas gênant et ça rend les choses plus simples, mais quand vient l’heure de la mise en production, de gros problèmes peuvent survenir.

Heureusement on peut maintenant (ça n’a pas toujours été le cas) changer tout ou partie de ce comportement, c’est ce qu’on va voir ici. Continuer la lecture