Outils pour utilisateurs

Outils du site


geoblocage

Géoblocage d'un serveur

Que ce soit parfaitement clair : Je suis viscéralement contre le géoblocage d'un serveur public, car cela est totalement contraire à l'esprit d'internet et c'est préjudiciable aux internautes.

Ceci étant dit, pour un serveur privé (par exemple votre RaspberryPi privé ou un NAS perso) cela a tout son sens, car cela permet de réduire grandement la surface d'attaque (la vaste majorité des attaques venant de "gros" pays comme les USA, la Russie, la Chine, etc.)

Cette page va vous expliquer comment mettre en place un géoblocage en liste blance (whitelist) pour une machine Linux utilisant iptables (et plus particulièrement ufw). Notez que je vais utiliser une liste blanche (whitelist) des IP françaises, mais on peut assez facilement passer en blacklist (et j'expliquerai comment).

1️⃣ Établir la liste des plages d'IP

Première étape : Établir la liste des plages d'IP françaises et en faire un ipset qui pourra ensuite être réutilisé dans iptables.

Il existe différentes sources, et j'ai trouvé celle-là : https://github.com/herrbischoff/country-ip-blocks/tree/master/ipv4

Cette liste est alimentée à différentes sources (RIPE, etc.) et elle est mise à jour toutes les heures. En prime les plages CIDR sont déjà fusionnées, ce qui évite d'avoir à le faire.

Cela veut aussi dire que la liste des plages change, et que vous devez mettre à jour votre ipset régulièrement (idéalement : quotidiennement).

Même si personnellement, mon serveur ayant un faible trafic, je le fais une fois par semaine.

Le script shell suivant (make-create-ipset-fr.sh) va donc récupérer cette liste et créer un second script shell dont la tâche est de créer un ipset contenant ces plages au démarrage de la machine (/usr/local/bin/create-ipset-fr.sh).

make-create-ipset-fr.sh
#!/usr/bin/env bash
# Ce script construit le script create-ipset-fr.sh qui créé un ipset
# des plages d'IP françaises prêt à être utilisé dans iptables.
# Ce script ne touche pas à iptables. Il créé juste le fichier /usr/local/bin/create-ipset-fr.sh
# Ce script doit être lancé en root.
 
cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
set -o errexit
set -o nounset
set -o pipefail
 
echo "Création du script /usr/local/bin/create-ipset-fr.sh pour whitelister les IP françaises..."
 
if [[ $(id -u) -ne 0 ]] ; then echo "Ce script doit être lancé en root." ; exit 1 ; fi
 
echo """#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
# create-ipset-fr.sh : Création de l'ipset allowed_country pour whitelister les IP françaises.
 
logger \"Mise à jour de l'ipset allowed_country : début.\"
 
if [[ \$(id -u) -ne 0 ]] ; then echo \"Ce script doit être lancé en root.\" ; exit 1 ; fi
 
# Création de l'ipset : 
ipset -exist create allowed_country hash:net
 
# Whitelister les IP du LAN :
ipset add allowed_country 192.168.1.0/24;
 
# Whitelister d'autres IP ou plage si vous le souhaitez:
#ipset add allowed_country 195.25.12.0/24;
#ipset add allowed_country 197.12.13.14;
 
# Whitelister les plages CIDR françaises :
""" > create-ipset-fr.sh
 
n='([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'
m='([0-9]|[12][0-9]|3[012])'
 
for CIDR in $(wget -O - https://raw.githubusercontent.com/herrbischoff/country-ip-blocks/master/ipv4/fr.cidr)
do
    if [[ $CIDR =~ ^$n(\.$n){3}/$m$ ]]; then
        echo "ipset add allowed_country $CIDR;" >> create-ipset-fr.sh
    else
        echo "CIDR invalide exclu : $CIDR"
    fi
done
 
echo """
logger \"Mise à jour de l\'ipset allowed_country : terminé.\"
""" >> create-ipset-fr.sh
 
chmod +x create-ipset-fr.sh
cp create-ipset-fr.sh /usr/local/bin/

Le script doit être lancé en root:

sudo ./make-create-ipset-fr.sh

Voici à quoi ressemble le script généré :

create-ipset-fr.sh
#!/usr/bin/env bash
set -o errexit
set -o nounset
set -o pipefail
# create-ipset-fr.sh : Création de l'ipset allowed_country pour whitelister les IP françaises.
 
logger "Mise à jour de l'ipset allowed_country : début."
 
if [[ $(id -u) -ne 0 ]] ; then echo "Ce script doit être lancé en root." ; exit 1 ; fi
 
# Création de l'ipset : 
ipset -exist create allowed_country hash:net
 
# Whitelister les IP du LAN :
ipset add allowed_country 192.168.1.0/24;
 
# Whitelister d'autres IP ou plage si vous le souhaitez:
#ipset add allowed_country 195.25.12.0/24;
#ipset add allowed_country 197.12.13.14;
 
# Whitelister les plages CIDR françaises :
 
ipset add allowed_country 1.179.112.0/20;
ipset add allowed_country 2.3.0.0/16;
ipset add allowed_country 2.4.0.0/14;
ipset add allowed_country 2.8.0.0/13;
# (...etc.)

2️⃣ Créer l'ipset au démarrage de la machine

Maintenant que nous avons le script create-ipset-fr.sh qui peut nous créer l'ipset allowed_country dont nous avons besoin, ce script doit être lancé au démarrage de la machine, et en particulier avant ufw puisque c'est dans ufw qu'on va utiliser cet ipset.

On va donc créer le fichier /etc/systemd/system/ipset.service contenant:

[Unit] 
Description=Create ipset country whitelist
Before=ufw.service
After=network.target
 
[Service]
Type=oneshot
ExecStart=/usr/local/bin/create-ipset-fr.sh
 
[Install]
WantedBy=multi-user.target
  • On a un After=network.target afin que ce script ne démarre que quand le réseau est prêt.
  • Et Before=ufw.service afin que l'ipset soit créé avant que ufw démarre.

Faites ensuite:

sudo systemctl daemon-reload
sudo systemctl enable ipset.service
sudo systemctl start ipset.service

Notez que si vous voulez relancer le service alors que l'ipset existe déjà, vous pouvez le vider manuellement:

ipset -exist flush allowed_country

Après un redémarrage, vérifiez que l'ipset est bien créé en tapant sudo ipset --list allowed_country | less

Vous devez voir quelquechose de ce genre:

Name: allowed_country
Type: hash:net
Revision: 7
Header: family inet hashsize 2048 maxelem 65536 bucketsize 12 initval 0x2004d7df
Size in memory: 119376
References: 1
Number of entries: 4002
Members:
109.235.80.0/21
185.178.76.0/22
193.46.206.0/24
31.42.116.0/22
# et ainsi de suite...

3️⃣ Intégrer l'ipset à ufw

Il faut ensuite modifier la configuration d'ufw pour utiliser cet ipset. Malheureusement la ligne de commande d'ufw ne le permet pas, il va falloir passer par ses fichiers de configuration.

Modifiez le fichier /etc/ufw/before.rules (extrait du fichier ci-dessous):

# quickly process packets for which we already have a connection
-A ufw-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw-before-output -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw-before-forward -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

# drop INVALID packets (logs these in loglevel medium and higher)
-A ufw-before-input -m conntrack --ctstate INVALID -j ufw-logging-deny
-A ufw-before-input -m conntrack --ctstate INVALID -j DROP

# ⬇️ On insère ici notre ligne pour intégrer l'ipset:
# Whitelist des plages d'IP:
-A ufw-before-input -m set ! --match-set allowed_country src -j DROP

# ok icmp codes for INPUT
-A ufw-before-input -p icmp --icmp-type destination-unreachable -j ACCEPT
-A ufw-before-input -p icmp --icmp-type time-exceeded -j ACCEPT
-A ufw-before-input -p icmp --icmp-type parameter-problem -j ACCEPT
-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT
  • On ajoute notre ligne -A ufw-before-input -m set ! –match-set allowed_country src -j DROP:
    • On s'insère dans la chaîne ufw-before-input qui traite les paquets avant le reste (routage, etc.)
    • -m set ! –match-set allowed_country src -j DROP pour rejeter tous les paquets qui ne sont pas dans l'ipset allowed_country.
    • Vous noterez qu'on se place après RELATED,ESTABLISHED afin de ne pas bloquer les connexions qui auraient été préalablement établies vers des IP non whitelistées (afin de ne pas empêcher le serveur lui-même de contacter des IP ne faisant pas partie de la whitelist. Sinon vous aurez sans doute des soucis pour faire les mises à jour.)
    • On se met avant les réponses ICMP afin de ne pas répondre aux pings.

Si vous n'utilisez pas ufw, vous pouvez bien entendu utiliser directement cet ipset dans iptables:

sudo iptables -A INPUT -m set ! --match-set allowed_country src -j DROP

(Conseil : insérez la règle aussi après votre RELATED,ESTABLISHED).

4️⃣ Vérification

Après redémarrage si vous voulez tester que l'ipset est bien intégré par ufw, faites:

sudo iptables --list ufw-before-input

Dans la liste des règles, vous devez voir une mention à allowed_country :

Chain ufw-before-input (1 references)
target     prot opt source               destination         
ACCEPT     all  --  anywhere             anywhere            
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
ufw-logging-deny  all  --  anywhere             anywhere             ctstate INVALID
DROP       all  --  anywhere             anywhere             ctstate INVALID
DROP       all  --  anywhere             anywhere             ! match-set allowed_country src
ACCEPT     icmp --  anywhere             anywhere             icmp destination-unreachable
ACCEPT     icmp --  anywhere             anywhere             icmp time-exceeded
ACCEPT     icmp --  anywhere             anywhere             icmp parameter-problem
ACCEPT     icmp --  anywhere             anywhere             icmp echo-request
ACCEPT     udp  --  anywhere             anywhere             udp spt:bootps dpt:bootpc
ufw-not-local  all  --  anywhere             anywhere            
ACCEPT     udp  --  anywhere             mdns.mcast.net       udp dpt:mdns
ACCEPT     udp  --  anywhere             239.255.255.250      udp dpt:1900
ufw-user-input  all  --  anywhere             anywhere

Si vous avez un VPN, prenez une IP non-française et vérifiez que votre serveur n'est pas accessible (Vous pouvez aussi essayer de tester un port ouvert avec https://ping.eu/port-chk/)

Fonctionnement en blacklist

Ici j'ai fonctionné en whitelist : J'ai tout interdit, sauf les IP "françaises".

On peut aussi choisir de tout autoriser, sauf certains pays (blacklist), par exemple en modifiant le script pour aggrérer les plages de plusieurs pays :

# cn=Chine, tw=Taiwan, ru=Russie, in=Inde
COUNTRIES=('cn' 'tw' 'ru' 'in' )
for i in "${COUNTRIES[@]}"; do
    echo "Récupération des plages CIDR de ${i}"
    for CIDR in $(wget -O - https://raw.githubusercontent.com/herrbischoff/country-ip-blocks/master/ipv4/${i}.cidr)
    do
       if [[ $CIDR =~ ^$n(\.$n){3}/$m$ ]]; then
           echo "ipset add allowed_country $CIDR;" >> create-ipset-fr.sh
       else
           echo "CIDR invalide exclu : $CIDR"
       fi       
    done
done

Puis modifier la règle iptables:

sudo iptables -A INPUT -m set --match-set allowed_country src -j DROP

(on a juste retiré le point d'exclamation qui permet de faire une négation de la règle: donc tout paquet faisant partie de cette liste sera droppé.)

📝 Notes

  • Il semblerait que les IP de certains VPN, même en France, ne soient pas inclues dans la liste (par exemple les points de sortie "France" du VPN Mullvad ne sont pas dans le fichier fr.cidr). À vous de compléter les fichiers si besoin (surtout si vous êtes en whitelist et voulez accéder à votre serveur depuis votre VPN habituel).
  • Je n'ai pas non plus ici abordé les plages IPv6.
geoblocage.txt · Dernière modification : 2024/09/19 12:49 de sebsauvage