Asegurando SSH con fail2ban y firewalld

Cuando uno expone cualquier servicio a Internet, es esperado que bots y usuarios maliciosos eventualmente te encuentren e intenten explotar alguna vulnerabilidad sobre algún servicio o puerto expuesto. Anteriormente utilizaba fail2ban con ufw para asegurar SSH en mi servidor bastión, ahora usando RHEL 9 decidí hacer lo mismo pero usando firewalld.

Como fail2ban no esta incluido en los repositorios oficiales de RHEL 9, debemos añadir el repositorio Extra Packages for Enterprise Linux (EPEL) que es mantenido por la comunidad de Fedora para poder instalarlo. Esta es la instrucción oficial que encontramos en la web de EPEL para RHEL 9:

1
2
sudo subscription-manager repos --enable codeready-builder-for-rhel-9-$(arch)-rpms
sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm

Ahora podemos instalar fail2ban:

1
sudo dnf install fail2ban-firewalld fail2ban-systemd

Esto instalará algunas dependencias adicionales del cliente y servidor de fail2ban.

Creamos un archivo de configuración para fail2ban en /etc/fail2ban/jail.local:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[DEFAULT]
ignoreip = 127.0.0.1/8 192.168.1.0/24
bantime = 672h
findtime = 10m
maxretry = 5
action = %(action_)s
banaction = firewallcmd-rich-rules[actiontype=<multiport>]
banaction_allports = firewallcmd-rich-rules[actiontype=<allports>]

[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s

En este archivo jail.local lo que estamos haciendo es previniendo que deje algún cliente de la red local por fuera, que el período de baneo sea 28 días, que rastree 5 intentos de login en menos de 10 minutos y que lo activamos para el servicio sshd.

Ahora activamos e iniciamos fail2ban:

1
2
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Hasta aquí, todo funciona correctamente, intente loguearme remotamente con mi teléfono usando Terminus y luego del 5to intento fallido, no tengo manera de ingresar. Verificando con fail2ban veremos lo siguiente:

1
2
3
4
5
6
7
8
9
10
sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 0
|  |- Total failed: 0
|  `- Journal matches:  _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned: 1
   |- Total banned: 1
   `- Banned IP list:   190.57.40.119

Fui exitosamente baneado de mi servidor…

Notificaciones por Telegram

Anteriormente tenía configurado Slack para recibir mensajes de notificación del homelab, pero como configure un solo canal con todos los servidores que administraba en aquel entonces, era una locura la cantidad de mensajes de baneo por ataques automatizados así que al final lo silencié y deje de revisarlo.

Como las webs que manejo en el homelab son de menor escala, la cantidad de ataques automatizados por el momento es menor. Solo es cuestión de tiempo para que sea objeto de interés. Así que para estar al tanto de lo que está ocurriendo con mis servidores, he optado por usar Telegram para notificaciones. Me he basado en este script de kexplo con algunos ajustes.

Editamos el archivo de configuración de fail2ban: /etc/fail2ban/jail.local

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[DEFAULT]
ignoreip = 127.0.0.1/8 192.168.1.0/24
bantime = 672h
findtime = 10m
maxretry = 5
action = %(action_)s
banaction = firewallcmd-rich-rules[actiontype=<multiport>]
banaction_allports = firewallcmd-rich-rules[actiontype=<allports>]

[sshd]
telegram_chat_id =
telegram_bot_token =
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
action = telegram[telegram_chat_id=%(telegram_chat_id)s, telegram_bot_token=%(telegram_bot_token)s]

Solo añadimos los datos de nuestro bot de Telegram en las variables telegram_chat_id y telegram_bot_token y la línea de acción de fail2ban al final.

Por último para que nos envíe mensajes por Telegram, añadimos el archivo /etc/fail2ban/action.d/telegram.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[Definition]

# Option:  actionstart
# Notes.:  command executed once at the start of Fail2Ban.
# Values:  CMD
#
actionstart = curl -s -o /dev/null -X GET https://api.telegram.org/bot<telegram_bot_token>/sendMessage?chat_id=<telegram_chat_id> --data-urlencode text="[<fq-hostname>] La jaula <name> ha iniciado exitosamente."

# Option:  actionstop
# Notes.:  command executed once at the end of Fail2Ban
# Values:  CMD
#
actionstop = curl -s -o /dev/null -X GET https://api.telegram.org/bot<telegram_bot_token>/sendMessage?chat_id=<telegram_chat_id> --data-urlencode chat_id=<telegram_chat_id> --data-urlencode text="[<fq-hostname>] La jaula <name> ha sido detenida."

# Option:  actioncheck
# Notes.:  command executed once before each actionban command
# Values:  CMD
#
actioncheck =

# Option:  actionban
# Notes.:  command executed when banning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    <ip>  IP address
#          <failures>  number of failures
#          <time>  unix timestamp of the ban time
# Values:  CMD
#
actionban = curl -s -o /dev/null -X GET https://api.telegram.org/bot<telegram_bot_token>/sendMessage?chat_id=<telegram_chat_id> --data-urlencode text="[<fq-hostname>] 🚫 <ip> ha sido baneado por fail2ban luego de <failures> intentos contra <name>."

# Option:  actionunban
# Notes.:  command executed when unbanning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    <ip>  IP address
#          <failures>  number of failures
#          <time>  unix timestamp of the ban time
# Values:  CMD
#
actionunban = curl -s -o /dev/null -X GET https://api.telegram.org/bot<telegram_bot_token>/sendMessage?chat_id=<telegram_chat_id> --data-urlencode text="[<fq-hostname>] Se ha eliminado el baneo contra <ip>."

[Init]

init = 'Notificaciones de Fail2Ban por Telegram activadas'

Tuve algunos issues con esto, ya que el comando curl trabajaba perfecto por si solo, pero no cuando lo ejecutaba fail2ban. Indagando errores inespecíficos pensé en SELinux que suele dar algunos problemas extras cuando todo parece estar bien y buscando en los logs logré dar con el error que muy amistosamente indicaba que hacer para solucionarlo.


Moisés Serrano Samudio Médico de atención primaria, fotógrafo aficionado, apasionado de las tecnologías relacionadas con el EdTech y el eHealth y diseñador/desarrollador de sitios web de salud. Médico, apasionado del EdTech/eHealth y diseñador/desarrollador de sitios web de salud.
Moisés Serrano Samudio

@linkmoises

Médico de atención primaria, fotógrafo aficionado, apasionado de las tecnologías relacionadas con el EdTech y el eHealth.

Entradas relacionadas

  1. Aún no hay comentarios...

Deja una respuesta

Su email no será publicado. Required fields are marked *