Installation d'une blocklist d'IPs malveillantes avec ipset et iptables sur un serveur Debian/Ubuntu
Grâce à un projet trouvé sur GitLab, j’ai intégré une blocklist d’IPs malveillantes dans mon firewall Linux en utilisant ipset et iptables. Le projet Data-Shield_IPv4_Blocklist
fournit des listes d’IPs malveillantes (~100k entrées).
J’ai déjà iptables sur mon serveur mais la meilleure approche est d’utiliser ipset, ajouter 100k règles iptables individuelles serait catastrophique en performance, tandis qu’ipset gère cela en O(1) (temps constant quelque soit la taille)
Les listes sont mises à jour toutes les 6h via un script bash qui télécharge les listes, les nettoie, les charge dans un ipset temporaire, puis swap atomiquement pour éviter toute interruption de service. Une règle iptables bloque ensuite tout trafic entrant depuis ces IPs. J’avoue, j’ai demandé l’aide de Claude pour le script.
Installation pas a pas
1. Installer les dependances
sudo apt install ipset curl iptables-persistent2. Copier et rendre le script executable
Voir le script complet à la fin de l’article.
sudo cp datashield-blocklist.sh /usr/local/bin/datashield-blocklist.sh
sudo chmod +x /usr/local/bin/datashield-blocklist.sh3. Premier lancement (en mode observation recommande)
Avant d’activer le DROP, testez d’abord ce qui serait bloque. Lancez une fois et verifiez les logs :
sudo /usr/local/bin/datashield-blocklist.sh
# Verifier ce qui est dans le ipset
sudo ipset list datashield | head -20
sudo ipset list datashield | wc -l
# Verifier la regle iptables
sudo iptables -L INPUT -n --line-numbers | head -54. Automatiser (mise a jour toutes les 6h)
sudo crontab -eAjouter la ligne :
0 */6 * * * /usr/local/bin/datashield-blocklist.sh >> /var/log/datashield.log 2>&15. Persister les regles apres reboot
sudo netfilter-persistent saveOu avec ipset-persistent :
sudo apt install ipset-persistent
sudo ipset save > /etc/ipsets.confCe que fait le script
| Etape | Detail |
|---|---|
| Telechargement | Liste standard (~100k IPs) + liste critique (pour APIs/DMZ) via jsDelivr CDN, avec fallback GitHub |
| Filtrage | Ne garde que les IPv4 valides, deduplique |
| Swap atomique | Remplace l’ancienne liste sans interruption de service |
| iptables | Insere une seule regle en haut de la chaine INPUT (-I INPUT 1) |
| Direction | Inbound uniquement (WAN -> LAN), jamais outbound, conforme aux specs du projet |
Pourquoi ipset et pas des regles iptables individuelles ? Avec 100k IPs, des regles individuelles ralentiraient drastiquement le firewall.
ipsetutilise une hash table – la recherche est O(1) quelle que soit la taille de la liste.
datashield-blocklist.sh
Voici le script complet, à copier dans /usr/local/bin/datashield-blocklist.sh et rendre executable :
#!/bin/bash
# =============================================================================
# Data-Shield IPv4 Blocklist - Intégration iptables/ipset
# Source: https://github.com/duggytuxy/Data-Shield_IPv4_Blocklist
#
# Prérequis : ipset, iptables, curl (apt install ipset curl)
# Usage : sudo bash datashield-blocklist.sh
# Cron : 0 */6 * * * /usr/local/bin/datashield-blocklist.sh >> /var/log/datashield.log 2>&1
# =============================================================================
set -euo pipefail
# --- Configuration -----------------------------------------------------------
IPSET_NAME="datashield"
IPSET_TMP="${IPSET_NAME}_tmp"
LOG_FILE="/var/log/datashield-blocklist.log"
# Listes à télécharger (CDN jsDelivr = haute dispo, choisir selon vos besoins)
# Liste standard : Web Apps, Nginx/Apache, VPS (scanners génériques)
# Liste critique : APIs, DMZ, infrastructure exposée
#
# Pour un serveur API, les deux listes sont complémentaires (IPs différentes).
# Commentez la ligne "standard" si vous voulez alléger, mais garder les deux
# reste recommandé : surcoût mémoire négligeable, couverture maximale.
URLS=(
"https://cdn.jsdelivr.net/gh/duggytuxy/Data-Shield_IPv4_Blocklist@main/prod_data-shield_ipv4_blocklist.txt" # standard : scanners génériques
"https://cdn.jsdelivr.net/gh/duggytuxy/Data-Shield_IPv4_Blocklist@main/prod_critical_data-shield_ipv4_blocklist.txt" # critical : attaques API/DMZ
)
# Fallback GitHub Raw si jsDelivr est indisponible
URLS_FALLBACK=(
"https://raw.githubusercontent.com/duggytuxy/Data-Shield_IPv4_Blocklist/main/prod_data-shield_ipv4_blocklist.txt"
"https://raw.githubusercontent.com/duggytuxy/Data-Shield_IPv4_Blocklist/main/prod_critical_data-shield_ipv4_blocklist.txt"
)
# -----------------------------------------------------------------------------
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
# --- Vérifications préalables ------------------------------------------------
if [ "$(id -u)" -ne 0 ]; then
echo "ERREUR: Ce script doit être exécuté en tant que root (sudo)." >&2
exit 1
fi
for cmd in ipset iptables curl; do
if ! command -v "$cmd" &>/dev/null; then
log "ERREUR: '$cmd' n'est pas installé. Installez-le avec : apt install $cmd"
exit 1
fi
done
log "=== Début de la mise à jour Data-Shield Blocklist ==="
# --- Téléchargement des listes -----------------------------------------------
TMP_RAW=$(mktemp)
SUCCESS=0
for i in "${!URLS[@]}"; do
URL="${URLS[$i]}"
FALLBACK="${URLS_FALLBACK[$i]}"
log "Téléchargement: $URL"
if curl -sSf --max-time 30 "$URL" >> "$TMP_RAW"; then
SUCCESS=$((SUCCESS + 1))
else
log "AVERT: Echec jsDelivr, tentative fallback GitHub..."
if curl -sSf --max-time 30 "$FALLBACK" >> "$TMP_RAW"; then
SUCCESS=$((SUCCESS + 1))
else
log "AVERT: Echec également sur le fallback."
fi
fi
done
if [ "$SUCCESS" -eq 0 ]; then
log "ERREUR: Impossible de télécharger aucune liste. Abandon."
rm -f "$TMP_RAW"
exit 1
fi
# --- Nettoyage : ne garder que des IPv4 valides (avec ou sans CIDR) ----------
TMP_CLEAN=$(mktemp)
grep -Eo '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(/[0-9]{1,2})?' "$TMP_RAW" \
| sort -u \
> "$TMP_CLEAN"
COUNT=$(wc -l < "$TMP_CLEAN")
log "Entrées uniques valides: $COUNT"
if [ "$COUNT" -lt 1000 ]; then
log "ERREUR: Moins de 1000 IPs trouvées, la liste semble corrompue. Abandon."
rm -f "$TMP_RAW" "$TMP_CLEAN"
exit 1
fi
# --- Création du ipset temporaire (remplacement atomique) --------------------
ipset destroy "$IPSET_TMP" 2>/dev/null || true
ipset create "$IPSET_TMP" hash:net hashsize 131072 maxelem 300000
log "Chargement dans ipset..."
while IFS= read -r ip; do
ipset add "$IPSET_TMP" "$ip" 2>/dev/null || true
done < "$TMP_CLEAN"
LOADED=$(ipset list "$IPSET_TMP" | grep -c "^[0-9]" || echo 0)
log "Entrées chargées dans ipset: $LOADED"
# Swap atomique : remplace la liste active sans interruption de service
if ipset list "$IPSET_NAME" &>/dev/null; then
ipset swap "$IPSET_TMP" "$IPSET_NAME"
ipset destroy "$IPSET_TMP"
log "ipset '$IPSET_NAME' mis à jour par swap atomique."
else
ipset rename "$IPSET_TMP" "$IPSET_NAME"
log "ipset '$IPSET_NAME' créé."
fi
# --- Ajout de la règle iptables (idempotent) ---------------------------------
# Bloque le trafic ENTRANT (WAN → LAN) depuis les IPs malveillantes
# Ne touche PAS au trafic sortant, conformément aux recommandations du projet
if ! iptables -C INPUT -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; then
# Insère en position 1 pour agir avant toute autre règle
iptables -I INPUT 1 -m set --match-set "$IPSET_NAME" src -j DROP
log "Règle iptables INPUT créée."
else
log "Règle iptables INPUT déjà en place."
fi
# Décommentez si ce serveur fait du routage (passerelle/bridge) :
# if ! iptables -C FORWARD -m set --match-set "$IPSET_NAME" src -j DROP 2>/dev/null; then
# iptables -I FORWARD 1 -m set --match-set "$IPSET_NAME" src -j DROP
# log "Règle iptables FORWARD créée."
# fi
# --- Nettoyage ---------------------------------------------------------------
rm -f "$TMP_RAW" "$TMP_CLEAN"
log "=== Mise à jour terminée. $LOADED entrées actives. ==="