Archives de catégorie : Réseau

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

IPv6 – pare feu

Objectif : profiter de l’ipv6 en conservant un bon niveau de sécurité réseau et sans avoir à gérer les pare-feu de tous ses équipements.

Petite précision avant de commencer. Bien qu’ils allouent un préfixe IPv6 à leur clients, la plupart des certains* FAI ne permettent pas de le segmenter en sous réseaux, ils s’attendent à ce que toutes les machines du réseau soient directement connectés à leur Box, il va donc falloir ruser un peu.

*edit : la situation a changé depuis que j’ai écrit cet article, par exemple avec la Freebox Revolution, Free semble (je ne peux pas tester) offrir un /61 avec possibilité de configurer la délégation, vous trouverez un exemple ici : http://www.goudal.net/?p=6

La suite reste valable dans le cas d’une Freebox V5

Prérequis :

  • une machine sous linux disposant d’au moins 2 interfaces réseaux
  • un préfixe IPv6 (FDN, Free, Nerim, OVH, SFR, … le proposent à leur abonnées)

Il y a 2 principales façon de faire :

  • la première consiste à faire du « brouting ». En gros on transporte les paquets de niveau 2 au travers d’un bridge. Ça revient à brancher tous vos équipements sur un switch capable de faire du filtrage. C’est simple mais assez rigide et pas très propre.
  • la seconde que je vais utiliser ici consiste à faire du routage traditionnel agrémenté d’un proxy NDP.

Ici la machine linux est un Raspberry-Pi sous Debian Wheezy, qui ne dispose que d’une carte réseau. Cette carte sera donc divisée en plusieurs interfaces de VLAN. La connectivité IPv6 sera assurée par une Freebox (v5) configurée en switch (donc pas en routeur).

La passerelle Linux assurera plusieurs fonctions :

  1. routeur-nat pour l’IPv4 : comme le font les box
  2. pare-feu IPv4 : comme ne le font pas les box
  3. routeur IPv6 : comme ne le font pas les box
  4. pare-feu IPv6 : comme ne le font pas les box
  5. proxy NDP : pour leurrer les Box
  6. on pourra compléter l’installation par quelques services de confort (serveur DNS, serveur DHCP, serveur NTP, proxy web, …)

En pratique, notre passerelle va se comporter comme un routeur-firewall IPv4 + IPv6 classique, mais va en plus transporter les annonces NDP (et uniquement ces annonces) de part et d’autre de ses interfaces.

Notes, à remplacer par vos informations :

  • l’interface physique est eth0
  • l’interface connectée à Internet est nommée vlan6
  • l’interface connectée au réseau local est nommée vlan46
  • le préfixe IPv6 de la connexion est 2001:db8:1234:1234::/64
  • le sous réseau 2001:db8:1234:1234::/64 est coté local, avec pour passerelle 2001:db8:1234:1234:1::2
  • le sous réseau 2001:db8:1234:1234::/126 (qui empiète sur le précédent) est notre interco entre notre passerelle et la box qui a pour adresse 2001:db8:1234:1234::1
  • le serveur DNS annoncé est 2001:db8::1
  • l’adresse public IPv4 est 1.1.1.2 avec 1.1.1.1 comme passerelle IPv4 par défaut
  • le réseau local a pour adresses 192.168.0.0/24 en IPv4 et fec0:0:0:ffff::/64 en IPv6
  • la passerelle a pour adresses locale 192.168.0.1 en IPv4 et fec0:0:0:ffff::1 en IPv6

Cette dernière adresse est utile si vous avez des postes sous Windows car ces derniers ne gèrent pas les annonces RDNSS, à la place Microsoft a configuré par défaut 3 adresses de DNS dans sa pile IPv6 : FEC0:0:0: FFFF::1, FEC0:0:0: FFFF::2 et FEC0:0:0:FFFF::3

Cela signifie qu’un poste portant l’une de ses adresses peut devenir automatiquement serveur DNS pour tous les postes d’un réseau local composé de Windows, niveau sécurité on a vu mieux.

En passant, Windows ne gère pas non plus les informations de routage fournie par DHCP en IPv6. Il faut donc annoncer le routeur via NDP et les DNS via DHCP. On ne peut donc pas faire du stateless ni du statefull, il faut combiner les 2 pour avoir un truc qui marche.

Configuration de base

Avant toute chose, assurez vous d’avoir un accès physique à la passerelle et qu’elle est correctement sécurisée (pas de SSH ouvert à tout le monde avec un mot de passe bidon, …).

Installation des paquets

  • Radvd : service d’annonce IPv6
  • NDPpd : proxy pour les paquets NDP (non disponible dans les dépôts Rapsberry, mais il se compile simplement)
  • VLAN : prise en charge du 802.1q (non nécessaire si vous avez plusieurs interfaces physique)

Configuration des services

/etc/radvd.conf

C’est ce service qui va faire de votre machine Linux une passerelle IPv6. Il va annoncer les routes (en particulier la route par défaut) et les serveurs DNS aux machines du réseau local.

interface vlan46 {
    AdvSendAdvert on;
    MinRtrAdvInterval 3;
    MaxRtrAdvInterval 10;
    AdvManagedFlag off;
    AdvOtherConfigFlag off;
    prefix 2001:db8:1234:1234::/64 {
        AdvOnLink on;
        AdvAutonomous on;
        AdvRouterAddr off;
    };
    route 2001:db8:1234:1234::/64 {
    };
    RDNSS 2001:db8::1 {
    };
};

/etc/ndppd.conf

Il va transmettre les annonces NDP de part de d’autre de la passerelle. Notez le /126.

route-ttl 30000
proxy vlan46 {
    router yes
    timeout 500
    ttl 30000
    rule 2001:db8:1234:1234::/126 {
        auto
    }
}
proxy vlan6 {
    router no
    timeout 500
    ttl 30000
    rule 2001:db8:1234:1234::/64 {
        auto
    }
}

Configuration des interfaces

/etc/network/interfaces

Fichier de configuration traditionnel, j’y ai inclus le lancement des services et la configuration noyau nécessaire au routage et au proxy NDP.

auto lo eth0 vlan6 vlan46

iface lo inet loopback

iface eth0 inet manual
    pre-up /sbin/modprobe -q ipv6 ; /bin/true
    pre-up echo 1 > /proc/sys/net/ipv4/ip_forward
    pre-up echo 1 > /proc/sys/net/ipv6/conf/eth0/disable_ipv6
    pre-up echo 1 > /proc/sys/net/ipv6/conf/all/forwarding
    pre-up echo 1 > /proc/sys/net/ipv6/conf/all/forwarding
    pre-up echo 1 > /proc/sys/net/ipv6/conf/default/forwarding
    pre-up echo 1 > /proc/sys/net/ipv6/conf/default/proxy_ndp
    pre-up echo 1 > /proc/sys/net/ipv6/conf/all/proxy_ndp
    up ifconfig eth0 promisc up
    post-up iptables-restore < /etc/iptables.up.rules
    post-up ip6tables-restore < /etc/ip6tables.up.rules
    
iface vlan6 inet manual
    vlan-raw-device eth0
    
iface vlan6 inet static
    vlan-raw-device eth0
    address 1.1.1.2
    netmask 255.255.255.0
    gateway 1.1.1.1
    
iface vlan6 inet6 static
    up ifconfig vlan6 promisc up
    vlan-raw-device eth0
    address 2001:db8:1234:1234::2
    netmask 126
    gateway 2001:db8:1234:1234::1
    
iface vlan46 inet static
    address 192.168.0.2
    netmask 255.255.255.0
    broadcast 192.168.0.255
    network 192.168.0.0
    vlan-raw-device eth0
    
iface vlan46 inet6 static
    up ifconfig vlan46 promisc up
    post-up /usr/local/sbin/ndppd -d
    post-up service radvd start
    address 2001:db8:1234:1234:1::2
    netmask 64
    post-up ip a a fec0:0:0:ffff::1/64 dev vlan46

Filtrage

IPv4

Ici un jeu de règle basique permettant à la passerelle de remplacer la box pour l’accès internet en IPv4 (ce qu’on appelle à tord du NAT). Les règles sont minimalistes, il n’y a pas par exemple de protection contre le flood.

/etc/iptables.up.rules

# Generated by iptables-save
*nat
:INPUT DROP [0:0]
:OUTPUT DROP [0:0]
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 192.168.0.0/24 ! -d 192.168.0.0/24 -j MASQUERADE
COMMIT
# Generated by iptables-save
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
# Generated by iptables-save
*filter
:localnet - [0:0]
:INPUT DROP [0:0]
:OUTPUT DROP [0:0]
:FORWARD DROP [0:0]
-A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
-A FORWARD -j localnet
-A localnet -s 192.168.0.0/24 -j ACCEPT
-A localnet -s 127.0.0.0/8 -j ACCEPT
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -j localnet
-A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A OUTPUT -j localnet
COMMIT

IPv6

La même chose en IPv6, sans le « NAT » bien entendu, rien de compliqué.

# Generated by ip6tables-save
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
#INPUT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -i vlan46 -j ACCEPT
-A INPUT -s fe80::/10 -j ACCEPT
-A INPUT -s ff00::/8 -j ACCEPT
-A INPUT -i vlan6 -s 2001:db8:1234:1234::/64 -j ACCEPT
#OUTPUT
-A OUTPUT -j ACCEPT
#FORWARD
-A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o vlan6 -j ACCEPT
-A FORWARD -s 2001:db8:1234:1234::/64 -j ACCEPT
#LOG
-A INPUT -j LOG --log-prefix "[IP6 INPUT] "
-A OUTPUT -j LOG --log-prefix "[IPv6 OUTPUT] "
-A FORWARD -j LOG --log-prefix "[IPv6 FORWARD] "
COMMIT

Il ne reste plus qu’à appliquer le tout, brancher et à vérifier que ça fonctionne.

Plusieurs sites permettent de vérifier si l’on utilise une connexion IPv4 ou IPv6, comme par exemple :

Sources :