Internet étant truffé de machines plus ou moins vérolées qui scannent le
monde entier, j’ai eu envie de les bloquer à l’échelle complète de mon
réseau ; un genre de fail2ban2bgp.
Avec netfilter il n’existe pas de mécanisme pour avoir des règles
dynamiques, et donc pas de moyen d’avoir facilement des règles flowspec
dans son firewall.
Par contre, il est possible d’utiliser un ipset comme source dans une
règle. Et avec exabgp il est possible d’utiliser un script shell pour
transformer une annonce bgp en ipset. J’ai donc utilisé ceci pour
implémenter une sorte de flowspec sur des routeurs soft linux.
Voici le schéma global de la solution que j’ai mise en place :
- Envoyer les logs à une machine (avec rsyslog)
- Les faire traiter par fail2ban
- Lui faire créer des routes locales sur la machine (avec une banaction)
- Annoncer ces routes en BGP (avec bird)
- Envoyer ces routes à tous mes routeurs (avec des RRs)
- Transformer ces routes en règles netfilter (avec exabgp, ipset et un script shell)
À noter : pour me besoin je n’ai besoin que de bloquer des IPs sources, je n’ai donc pas du tout regardé pour aller plus finement dans le filtrage (au port, à la taille de paquet, etc.). Je n’ai pas non plus implémenté la suppression des routes en cas de withdrawn bgp, parce que j’ai envie que les règles soient persistantes. J’ai utilisé ipset pour pouvoir plus facilement le mettre en place si besoin, mais on pourrait simplifier en écrivant directement des règles iptables. Je me suis également contenté d’IPv4, parce que je n’ai vu personne pourrir mes logs en IPv6 pour le moment, mais le code est très facilement adaptable.
Je ne vais pas aborder les configurations rsyslog et fail2ban ici, ce n’est pas mon but.
La récupération des routes générées par fail2ban se passent très classiquement avec de la conf bird relativement simple. J’ai un filtre d’import sur le protocol kernel qui ajoute une communauté sur les routes unreachables, et un filtre d’export bgp qui utilise cette communauté. Cette machine là peere avec mes RRs pour que les routes soient redistribuées dans mon réseau.
protocol kernel kernel_grt_ipv4 {
learn;
persist;
ipv4 {
import filter {
if (dest = RTD_UNREACHABLE) then {
bgp_large_community.add((208627,666,666));
accept;
}
else {
reject;
}
};
export none;
};
}
protocol bgp ibgp_core02_ipv4 from t_ibgp_ipv4 {
neighbor 10.0.4.4 as 208627;
ipv4 {
import none;
export filter {
if (bgp_large_community ~ [(208627,666,666)]) then {
accept;
}
else {
reject;
}
};
};
}
Ensuite, sur les routeurs j’envoie les routes avec celle communauté sur un exabgp en localhost. La version que j’utilise (4.2.17) a un bug, toutes les commandes de cli finissent en timeout… Donc si vous comptez utiliser un truc du genre en prod, tentez les versions plus anciennes ou faites chier les devs pour qu’ils pissent du code utilisable. Quand j’ai remonté le souci, on m’a dit d’utiliser master, ce que je n’ai pas envie de faire, ça me semble plus sain de vivre avec ce bug pour le moment.
# /etc/bird.conf
protocol bgp bgp_drop_v4 from iBGP_IPv4 {
description "locahost exabgp";
neighbor 127.0.0.1 port 1179 as myasn;
local 127.0.0.1;
rr client;
ipv4 {
import none;
export filter {
if ((208627,666,666) ~ bgp_large_community) then {
accept;
}
else {
reject;
}
};
};
}
# /etc/exabgp/exabgp.env
[exabgp.tcp]
bind=127.0.0.1
port=1179
# /etc/exabgp/exabgp.conf
process acl {
run /usr/local/libexec/acl.sh;
encoder text;
}
template {
neighbor fw_v4 {
family {
ipv4 unicast;
}
api speaking {
processes [ acl ];
receive {
parsed;
update;
}
}
}
}
neighbor 127.0.0.1 {
inherit fw_v4;
router-id 127.0.0.1;
local-address 127.0.0.1;
local-as 208627;
peer-as 208627;
}
# /usr/local/libexec/acl.sh
#!/bin/sh
exec 2>&1> /tmp/exa
while read stdin; do
net="$(echo $stdin | awk '/announced/ { print $6 }')"
if [ -n "${net}" ]; then
neigh="$(echo $stdin | awk '/announced/ { print $2 }')"
if [ "${neigh}" = '127.0.0.1' ]; then
sudo ipset add firewall_v4 $net
elif [ "${neigh}" = '::1' ]; then
sudo ipset add firewall_v6 $net
fi
fi
done < /dev/stdin
J’ai hardcodé le nom de l’ipset utilisé parce que ce code n’est pas voué à
être générique, mais vous pouvez lancer le script avec des arguements
depuis exabgp. J’utilise sudo parce l’utilisateur exabgp n’a pas le droit
de lancer ipset par défaut (ce qui se comprend assez bien).
Le code part du principe que l’ipset existe déjà (ipset create
firewall_v4 hash:net family inet counters
).
Ensuite, dans iptables j’utilise l’ipset ainsi peuplé pour bloquer les IPs qui sont dedans
iptables -A INPUT -m set --match-set firewall_v4 src -j DROP
iptables -A FORWARD -m set --match-set firewall_v4 src -j DROP
ip6tables -A INPUT -m set --match-set firewall_v6 src -j DROP
ip6tables -A FORWARD -m set --match-set firewall_v6 src -j DROP
Ce qui apparaît sous la forme
DROP all -- * * 0.0.0.0/0 0.0.0.0/0 match-set firewall_v4 src
dans iptables -L -n -v
« Et voilà », je bloque les IPs sur tout mon réseau grâce à ça, et je ne perds pas toutes les règles en cas de flap bgp.