Skip to main contentSkip to footer

Sustituir fail2ban por el agente para Linux

Cómo pasa un host de fail2ban al agente sin interrupción: primero comprobar qué posee ya los objetos del cortafuegos, ejecutar los dos un tiempo, después desactivar fail2ban de forma definitiva, y cómo volver atrás en segundos si hace falta.

Sustituir fail2ban

El agente no se pone al lado de fail2ban, lo releva. fail2ban sigue disponible como una fuente entre muchas, así que un host que todavía lo usa sigue notificando sus bloqueos, y un host sin fail2ban no pierde nada: cada tipo de ataque que antes llegaba por un jail tiene ahora un detector que lee el registro original.

Medido durante once días en un host de producción: el agente encontró 187 de las 189 direcciones que fail2ban había bloqueado en la misma máquina, las dos que faltaban eran direcciones de prueba metidas a mano, notificó 78 direcciones más que se habían quedado por debajo de los umbrales de fail2ban, y acertó los bloqueos de FTP tres de tres. La escalada para reincidentes sale del historial de bloqueos propio del agente, y reportedip-agent test <file> ocupa el lugar de fail2ban-regex.

Comprobar primero si algo ya posee estos nombres

Esto costó una tarde en una flota de producción, así que va antes que todo lo demás. Un script de cortafuegos escrito a mano, del tipo que este sitio documentaba antes, puede usar exactamente los nombres que usa el agente. Encontrados en el campo: los conjuntos rip-whitelist, rip-ssh, rip-mail, rip-web, rip-ftp y rip-edge, más una cadena rip-blacklist colgada con -A INPUT -j rip-blacklist, reconstruida cada hora por una tarea de cron, y casi carácter por carácter la cadena que el agente construye él mismo.

En un host así el orden habitual es erróneo en las dos direcciones. La primera sincronización del agente reconstruye la cadena y sustituye las reglas DROP existentes. En un host instalado con --mode log las sustituye por reglas LOG, lo que deja al host sin protección hasta que la tarea de cron antigua vuelva a ejecutarse, es decir hasta una hora más tarde. Y cuando esa tarea se ejecuta, reconstruye la cadena a su manera y retira de paso la regla rip-local del agente. Dos programas, una cadena, y cada uno deshaciendo el trabajo del otro cada hora.

Comprueba por tanto antes de la primera sincronización, y si los nombres chocan, no instales este host con --mode log. Instala el agente, que no toca ninguna regla, importa la lista blanca antigua, desactiva la tarea de cron antigua, y solo después lanza la primera sincronización con mode: drop, el valor por defecto. Una fase de observación con --mode log tiene su sitio en los hosts donde no chocan nombres.

bash
# 1. Does something already own these names?
ipset list -n | grep "^rip-"
iptables -S INPUT | grep rip-
iptables -S rip-blacklist 2>/dev/null | head
crontab -l | grep -iE "ipset|blacklist|reportedip"
ls -l /etc/cron.d/ /etc/cron.hourly/ 2>/dev/null

# 2. If yes, take the old whitelist over FIRST. The feed can be
#    downloaded again; that list cannot.
ipset list rip-whitelist    | sed -n "/^Members/,\$p" | tail -n +2 >  /tmp/rip-wl
ipset list rip-whitelist-v6 | sed -n "/^Members/,\$p" | tail -n +2 >> /tmp/rip-wl
wc -l /tmp/rip-wl
while read -r a; do
  [ -n "$a" ] && reportedip-agent whitelist add "$a" "imported from the old script"
done < /tmp/rip-wl
reportedip-agent whitelist list | wc -l

# 3. Disable the old job, then hand the chain over in one step.
crontab -l | grep -v blacklist | crontab -      # or: rm /etc/cron.d/<job>
# Only if the agent was installed with --mode log:
sed -i "s/^mode: log/mode: drop/" /etc/reportedip-agent/config.yaml
reportedip-agent sync
reportedip-agent status
El paso 2 no es opcional. En los hosts examinados, la lista blanca del script antiguo contenía 208 prefijos IPv4 y 61 prefijos IPv6: rastreadores de buscadores, Cloudflare y las propias máquinas del operador. Sin esa importación, el agente habría notificado el primer día las direcciones de Googlebot, de Cloudflare y de la propia flota. Una lista blanca es el único estado que una migración no puede reconstruir a partir de ninguna otra parte, así que impórtala antes de cambiar nada y vuelve a leerla antes de la primera sincronización con reportedip-agent whitelist list.

Ejecutar los dos un tiempo

Ejecutar los dos es seguro y es la forma sensata de migrar. El agente nunca escribe en /etc/fail2ban, y fail2ban no sabe nada de los conjuntos rip-, así que los dos usan cadenas separadas y estados separados. Dos reglas que descartan la misma dirección cuestan una comparación de paquete.

Lo único que no conviene hacer es notificar el mismo evento dos veces. Si conservas la acción de fail2ban que envía los bloqueos por HTTP y configuras fail2ban como fuente del agente, cada bloqueo sale del host dos veces y se paga dos veces de tu cuota diaria. Elige el agente o la acción.

Instala con REPORTEDIP_FAIL2BAN_SOURCE=0 cuando estés sustituyendo fail2ban. El instalador detecta un fail2ban en marcha y lo escribe como fuente, lo que es correcto para un host que lo conserva y erróneo para una migración: cuando fail2ban desaparece, desaparece también el registro al que apunta esa fuente. La variable deja la fuente fuera, y sin ella la instalación imprime una línea que dice que la fuente está ahí y qué hacer con ella tras la retirada. En un host ya instalado, quita a mano la entrada fail2ban de sources y después para y arranca el servicio watch.

bash
# What did fail2ban ban, and what does the agent make of the
# same logs? Same file, same window, two verdicts.
fail2ban-client status sshd
reportedip-agent test /var/log/auth.log

# Per jail, then the agent over the file behind it
for j in $(fail2ban-client status | sed -n "s/.*Jail list:\t*//p" | tr -d " " | tr "," " "); do
  echo "== $j"; fail2ban-client status "$j" | grep -E "Total banned|Currently banned"
done
reportedip-agent status | sed -n "/^sources:/,/^queue:/p"

Desactivar fail2ban de forma definitiva

Hazlo cuando hayas comparado los dos durante unos días y la lista de fuentes del agente cubra cada jail que tenías.

bash
# 1. Stop it and keep it stopped.
systemctl stop fail2ban
systemctl disable fail2ban

# 2. Remove the fail2ban source from the agent config, then stop
#    and start the daemon. A restart is not enough for a removed
#    source: the daemon keeps tailing the old file.
$EDITOR /etc/reportedip-agent/config.yaml
systemctl stop reportedip-agent.service
systemctl start reportedip-agent.service

# 3. Check what fail2ban left in the kernel, in both address
#    families. Stopping it does not always clean up: a jail that
#    once used an iptables action leaves old-style f2b- chains behind.
nft list tables | grep f2b    || echo "no f2b table"
iptables -S  | grep "f2b-"    || echo "no f2b chain (v4)"
ip6tables -S | grep "f2b-"    || echo "no f2b chain (v6)"
ipset list -n | grep f2b      || echo "no f2b set"

# 4. Remove the leftovers from the RUNNING ruleset. The jump rule
#    carries -p and --dports, so it is deleted by its full text; a
#    plain -D INPUT -j f2b-sshd does not match it, and the chain then
#    refuses to go ("Device or resource busy"). Measured on a fleet:
#    the v6 leftover was the one that stayed.
nft delete table inet f2b-table 2>/dev/null
for T in iptables ip6tables; do
  $T -S INPUT | grep "f2b-" | sed "s/^-A INPUT //" | while read -r rule; do eval "$T -D INPUT $rule"; done
  for c in $($T -S | awk "/^-N f2b-/{print \$2}"); do $T -F "$c" && $T -X "$c"; done
done

# 5. And from the SAVED one, or they come back at the next boot.
#    Save without the rip- lines as well: the agent rebuilds its chain
#    at every sync, and a saved rule that names a rip- set which does
#    not exist yet at boot makes iptables-restore fail as a whole.
iptables-save  | grep -v -e "f2b-" -e "rip-" > /etc/iptables/rules.v4
ip6tables-save | grep -v -e "f2b-" -e "rip-" > /etc/iptables/rules.v6

# 6. Confirm the agent is happy without it.
reportedip-agent status | grep -i fail2ban
reportedip-agent status; echo "exit $?"
El paso 5 es el que se salta la gente. Si borras las cadenas f2b- solo del juego de reglas en marcha, netfilter-persistent las restaura en cada arranque desde /etc/iptables/rules.v4 y desde /etc/iptables/rules.v6. Vuelven vacías, así que no descartan nada y parecen inofensivas, y dentro de un año seguirán ahí desconcertando a quien lea el juego de reglas después. Revisa los dos archivos: en un host los restos estaban solo en el archivo v6, y quien busca únicamente en rules.v4 pasa de largo. Limpia primero el juego de reglas en marcha, luego guárdalo, y compruébalo después de un reinicio.

Una vuelta atrás es un comando y tarda segundos, porque el agente nunca tocó /etc/fail2ban: systemctl enable --now fail2ban devuelve cada jail y su propia tabla, y el agente no se ve afectado. Medido en un host en producción, los once jails habían vuelto en ocho segundos.

Última actualización: · Mantenido por el equipo de ReportedIP

Security Focused
Conforme al RGPD
Made in Germany
Volver a la documentación