Table des matières
Géoblocage d'un serveur
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.
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.