En la lección anterior pusiste cerradura a la puerta principal. Pero srv-tramontana sigue teniendo todas las ventanas abiertas: cualquiera que alcance la máquina puede hablar con el 8080 de la aplicación, y si mañana PostgreSQL escuchara en 0.0.0.0 en lugar de en localhost, también con el 5432. El bot de 203.0.113.44 sigue llamando 47 veces por madrugada y nadie se lo impide.
Un cortafuegos responde a una pregunta anterior a la de la autenticación: ¿quién tiene siquiera derecho a hablar con este servidor, y por qué puerto? Al terminar esta lección tendrás una política escrita y aplicada —solo SSH limitado y HTTPS desde fuera, PostgreSQL solo desde la red interna, el 8080 cerrado al exterior—, entenderás qué hay debajo de ufw, sabrás escribir un ruleset de nftables desde cero y tendrás a fail2ban bloqueando automáticamente a quien insista. Y todo ello sin perder la sesión SSH, porque la regla de oro no cambia: nunca cierres la puerta por la que estás entrando.
Contenido
- Qué es y qué no es un cortafuegos
- El modelo de Linux: netfilter y sus frontales
- Tablas, cadenas, hooks y estado de conexión
ufwen la práctica- Perfiles de aplicación
- Cuando
ufwno llega:nftablesdirecto - Equivalencias
iptables→nftables - Reglas de salida y por qué casi nadie las pone
sysctlde red con impacto en seguridadfail2ban: del registro al bloqueo automático- Verificar desde fuera con
nmap - Caso Tramontana: la política y el informe a Marta
- Qué es y qué no es un cortafuegos
Un cortafuegos decide qué paquetes entran, salen o atraviesan una máquina, según reglas que tú escribes. Eso es todo lo que hace, y conviene tenerlo muy claro:
| Un firewall sí | Un firewall no |
|---|---|
| Reduce la superficie expuesta a la red | Arregla un servicio mal configurado |
| Limita quién puede intentar autenticarse | Impide un ataque por el puerto que sí dejas abierto |
| Frena escaneos y ruido automatizado | Detecta que ya te han entrado (eso es 06-04) |
| Contiene el daño lateral tras un compromiso | Sustituye a las actualizaciones de seguridad |
La defensa en profundidad consiste precisamente en no depender de una sola capa: si un atacante supera el firewall, se encuentra con SSH sin contraseñas; si supera SSH, se encuentra con svc-tramontana sin shell y con ProtectSystem=strict; si llega al disco, se encuentra con los secretos cifrados del 06-05. Ninguna capa es suficiente sola, y esa es la idea.
- El modelo de Linux: netfilter y sus frontales
En el kernel hay un único motor de filtrado, netfilter. Todo lo demás son formas de escribirle reglas:
| Capa | Qué es | Estado en Ubuntu 24.04 |
|---|---|---|
| netfilter | El motor, dentro del kernel | Lo que realmente filtra |
nftables |
El lenguaje y la herramienta nft actuales |
La opción nativa: un solo comando para IPv4, IPv6, ARP y bridge |
iptables |
El interfaz histórico | Presente como iptables-nft: traduce a nftables por debajo |
ufw |
Uncomplicated Firewall, frontal de Ubuntu | Instalado por defecto, escribe reglas de nftables |
firewalld |
Frontal con zonas, típico de RHEL | Disponible, poco habitual aquí |
La consecuencia práctica: en Ubuntu 24.04, cuando escribes ufw allow 22/tcp, ufw genera reglas que acaban en el mismo motor nftables que verías con nft list ruleset. No son sistemas rivales: son dos niveles de abstracción sobre lo mismo.
Y la advertencia que ahorra tardes enteras: usa uno u otro, no los dos a la vez. Si activas ufw y además habilitas el servicio nftables con tu propio /etc/nftables.conf, tendrás dos conjuntos de reglas compitiendo, un flush ruleset que borra las de ufw en cada arranque y un comportamiento imposible de razonar.
- Tablas, cadenas, hooks y estado de conexión
Cuatro conceptos y ya puedes leer cualquier ruleset:
- Tabla: el contenedor. En
nftablesse declara con una familia:ip(IPv4),ip6,inet(las dos a la vez, lo que querrás casi siempre),arp,bridge,netdev. - Cadena: un conjunto ordenado de reglas colgado de un hook del kernel. Tres importan:
input(paquetes destinados a esta máquina, donde escribes casi todo),output(paquetes generados por ella, el egress del apartado 8) yforward(paquetes que la atraviesan, solo si es router o VPN → 08-04). - Política por defecto: qué pasa con un paquete que no encaja en ninguna regla. Solo hay dos filosofías, y una es la correcta:
policy drop(lista blanca: se prohíbe todo y se permite lo necesario) frente apolicy accept(lista negra: se permite todo y se prohíbe lo malo conocido). La lista negra es imposible de mantener, porque exige conocer de antemano todo lo que puede ir mal. - Estado de conexión: netfilter recuerda las conexiones en curso (connection tracking). Eso permite escribir
ct state established,related accepty despreocuparte del tráfico de vuelta.
Ese último punto es el que lo cambia todo. Sin filtrado con estado tendrías que abrir a mano los puertos altos para las respuestas, lo que equivale a no filtrar nada. Con estado la lógica es limpísima: new es el primer paquete de una conexión y es ahí donde se decide; established pertenece a una conexión ya aceptada y se acepta sin mirar; related es una conexión secundaria de otra (errores ICMP, datos de FTP) y también se acepta; e invalid no encaja en ninguna conexión conocida, así que se descarta.
ufw en la práctica
ufw en la prácticaufw es lo que usarás el 95 % de las veces. Empieza siempre midiendo:
El orden correcto de operaciones, y no hay ninguna otra forma de hacerlo bien en una máquina remota:
# 1. Políticas por defecto: denegar entrante, permitir saliente
$ sudo ufw default deny incoming
$ sudo ufw default allow outgoing
$ sudo ufw default deny routed
# 2. PRIMERO permitir SSH. Sin esto, el paso 4 te deja fuera.
$ sudo ufw limit 22/tcp comment 'SSH con limitacion de intentos'
# 3. El resto de servicios
$ sudo ufw allow 443/tcp comment 'HTTPS publico'
$ sudo ufw allow from 10.0.2.0/24 to any port 5432 proto tcp comment 'PostgreSQL red interna'
# 4. AHORA sí, habilitar
$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startupEse aviso del paso 4 no es decorativo: si ejecutas ufw enable con la política deny incoming y sin haber permitido SSH, la sesión se corta en el acto y solo recuperas la máquina por la consola de la VM. Ten esa consola abierta antes de empezar.
$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip
To Action From
-- ------ ----
22/tcp LIMIT IN Anywhere # SSH con limitacion
443/tcp ALLOW IN Anywhere # HTTPS publico
5432/tcp ALLOW IN 10.0.2.0/24 # PostgreSQL red interna
22/tcp (v6) LIMIT IN Anywhere (v6)Cuatro detalles que importan de esta salida:
ufwcrea automáticamente la regla IPv6 equivalente. Es exactamente el motivo por el que no se desactiva IPv6 «por si acaso»: aquí está cubierto.LIMITno esALLOW:ufw limitbloquea una IP que abre 6 o más conexiones en 30 segundos. Es una defensa barata contra fuerza bruta, aunquefail2ban(apartado 10) es mucho más fino.- El
commentes documentación viva, ydeny (routed)deja claro que esta máquina no reenvía nada, coherente con elip_forward = 0del 06-01.
deny frente a reject, que es una diferencia real y no un matiz:
| Acción | Qué hace el kernel | Lo que ve quien llama | Cuándo usarla |
|---|---|---|---|
deny (DROP) |
Descarta en silencio | Timeout: no sabe si la máquina existe | Cara a Internet: cuesta tiempo al escáner |
reject |
Responde ICMP port unreachable o TCP RST | «Cerrado» inmediato | Red interna: evita esperas de 30 s a tus aplicaciones |
Gestionar reglas existentes exige verlas numeradas, porque el orden importa: gana la primera que encaja.
$ sudo ufw status numbered
To Action From
-- ------ ----
[ 1] 22/tcp LIMIT IN Anywhere
[ 2] 443/tcp ALLOW IN Anywhere
[ 3] 5432/tcp ALLOW IN 10.0.2.0/24
$ sudo ufw delete 3
$ sudo ufw insert 1 deny from 203.0.113.44 comment 'bloqueo manual bot'Ojo: al borrar la regla 3, las siguientes se renumeran. Borra siempre de mayor a menor, o mejor, borra por especificación (sudo ufw delete allow 443/tcp), que es idempotente y no depende de números.
- Perfiles de aplicación
ufw trae perfiles con nombre en /etc/ufw/applications.d/, que traducen «OpenSSH» a «puerto 22/tcp»:
$ sudo ufw app list
Available applications:
Nginx Full
OpenSSH
$ sudo ufw app info OpenSSH
Profile: OpenSSH — Ports: 22/tcp
$ sudo ufw allow OpenSSHY puedes escribir el tuyo, que es la forma limpia de documentar los puertos de tu aplicación:
[Tramontana]
title=Tramontana Reservas
description=Aplicacion web de reservas (proxy inverso en 08-01)
ports=443/tcpGuárdalo en /etc/ufw/applications.d/tramontana y recárgalo con sudo ufw app update Tramontana: si mañana cambia el puerto, se cambia en un solo sitio.
- Cuando
ufw no llega: nftables directo
ufw no llega: nftables directoufw se queda corto cuando necesitas limitación de tasa fina, conjuntos de direcciones (set), marcado de paquetes, NAT elaborado o simplemente leer exactamente lo que hay. Entonces se escribe nftables a mano, en /etc/nftables.conf:
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
set redes_internas {
type ipv4_addr ; flags interval ; elements = { 10.0.2.0/24 }
}
chain input {
type filter hook input priority 0; policy drop;
# Tráfico de vuelta y basura: lo primero, por eficiencia
ct state established,related accept
ct state invalid drop
# Loopback: sin esto se rompen medio sistema y los túneles SSH
iif lo accept
# ICMP imprescindible (no bloquees ICMP entero: rompe la MTU)
ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } accept
ip6 nexthdr ipv6-icmp accept
# SSH con limitación de intentos nuevos, y HTTPS público
tcp dport 22 ct state new limit rate 6/minute burst 6 packets accept
tcp dport 443 accept
# PostgreSQL solo desde la red interna
ip saddr @redes_internas tcp dport 5432 accept
# Lo demás cae por la política, dejando rastro acotado
limit rate 5/minute log prefix "nft-drop-in: " level info
counter
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}Comentarios sobre el diseño, que es lo que se aprende:
table inetcubre IPv4 e IPv6 con las mismas reglas. Coniptablesnecesitarías duplicarlo todo enip6tables, y ahí es donde la gente se deja agujeros.policy dropeninputyforward,acceptenoutput: lista blanca donde importa.- El orden es deliberado: lo más frecuente arriba (
established), lo excepcional abajo. Cada paquete recorre la cadena hasta encontrar su regla. - No bloquees todo ICMP.
destination-unreachabletransporta el descubrimiento de MTU: sin él tendrás conexiones que se cuelgan al transferir ficheros grandes y te volverás loco buscando la causa. - El
logfinal lleva su propiolimit rate, porque un escaneo puede generar miles de líneas por segundo y llenarte el disco. Registrar lo que se descarta es lo que hace diagnosticable el firewall.
$ sudo nft -c -f /etc/nftables.conf && echo "sintaxis correcta"
sintaxis correcta
$ sudo systemctl enable --now nftables
$ sudo nft list ruleset | head -2
table inet filter {
set redes_internas {nft -c comprueba sin aplicar: es el netplan generate de los cortafuegos, y se usa siempre. La persistencia la da el servicio nftables, que ejecuta /etc/nftables.conf en cada arranque; sin él, nft es tan volátil como ip addr add.
- Equivalencias
iptables → nftables
iptables → nftablesSigue habiendo montañas de documentación escrita en iptables. Esta tabla te permite leerla:
iptables |
nftables |
|---|---|
-A INPUT -p tcp --dport 22 -j ACCEPT |
tcp dport 22 accept |
-P INPUT DROP |
policy drop en la definición de la cadena |
-m state --state ESTABLISHED,RELATED -j ACCEPT |
ct state established,related accept |
-s 10.0.2.0/24 / -i lo -j ACCEPT |
ip saddr 10.0.2.0/24 / iif lo accept |
-j REJECT --reject-with icmp-port-unreachable |
reject with icmpx type port-unreachable |
iptables -L -n -v / iptables-save |
nft list ruleset |
Reglas separadas en iptables/ip6tables |
Una sola en table inet |
- Reglas de salida y por qué casi nadie las pone
Casi todo el mundo escribe output policy accept y se olvida. Es cómodo y es una oportunidad perdida: las reglas de egress no impiden que te entren, pero limitan muchísimo lo que un atacante puede hacer después. Sin salida libre, no puede descargar su segunda fase desde un servidor externo, no puede exfiltrar reservas.csv a un host cualquiera y no puede unirse a una botnet.
chain output {
type filter hook output priority 0; policy drop;
ct state established,related accept
oif lo accept
udp dport { 53, 123 } accept # DNS y NTP
tcp dport { 53, 80, 443 } accept # DNS/TCP y repositorios apt
ip daddr 10.0.2.0/24 accept # red interna
limit rate 5/minute log prefix "nft-drop-out: "
}Por qué casi nadie las pone, con honestidad: rompen cosas de forma sutil y difícil de diagnosticar. Un apt update contra un espejo nuevo, un webhook, una actualización de certificados... todo falla con timeouts que nadie relaciona con el firewall. Si las adoptas, hazlo midiendo primero qué salidas usa de verdad la máquina (con el log en output y política accept durante una semana) y solo después cambiando la política a drop.
sysctl de red con impacto en seguridad
sysctl de red con impacto en seguridadEstos parámetros no son de rendimiento —eso es 07-03—, sino de comportamiento del stack de red frente a abusos:
$ sudo tee /etc/sysctl.d/60-seguridad-red.conf > /dev/null <<'CONF'
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.tcp_syncookies = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
CONF
$ sudo sysctl --system | grep -A8 60-seguridad| Parámetro | Qué evita |
|---|---|
rp_filter = 1 |
Paquetes con origen falsificado que no volverían por la misma interfaz (antispoofing) |
accept_redirects = 0 / send_redirects = 0 |
Que un ICMP redirect ajeno reescriba tu tabla de rutas, y que tú informes de topología a terceros |
accept_source_route = 0 |
Paquetes que dictan su propio camino, técnica clásica de evasión |
tcp_syncookies = 1 |
Que una inundación SYN agote la cola de conexiones a medias |
icmp_echo_ignore_broadcasts = 1 |
Ser amplificador de un ataque smurf |
log_martians = 1 |
Registra paquetes imposibles: señal temprana de algo raro |
Ojo con rp_filter = 1 (estricto): en una máquina con varias interfaces y rutas asimétricas descarta tráfico legítimo. En srv-tramontana, con una sola interfaz, es seguro; en un router o una máquina con VPN, usa 2 (modo laxo).
fail2ban: del registro al bloqueo automático
fail2ban: del registro al bloqueo automáticofail2ban cierra el bucle: lee los logs, detecta patrones de abuso y le pide al firewall que bloquee la IP durante un tiempo. No sustituye al cortafuegos, lo pilota.
$ sudo apt install -y fail2ban
$ sudo tee /etc/fail2ban/jail.local > /dev/null <<'CONF'
[DEFAULT]
# NUNCA te bloquees a ti mismo: red interna y portátiles del equipo
ignoreip = 127.0.0.1/8 ::1 10.0.2.0/24
bantime = 1h
findtime = 10m
maxretry = 5
backend = systemd
banaction = ufw
[sshd]
enabled = true
port = ssh
maxretry = 3
bantime = 24h
CONF
$ sudo systemctl enable --now fail2ban
$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Total failed: 47
| `- Journal matches: _SYSTEMD_UNIT=ssh.service + _COMM=sshd
`- Actions
|- Currently banned: 1
`- Banned IP list: 203.0.113.44Se edita jail.local, nunca jail.conf: el segundo pertenece al paquete y la próxima actualización se lleva tus cambios por delante. Las tres cifras que gobiernan todo:
| Parámetro | Significado | Valor elegido |
|---|---|---|
maxretry / findtime |
Fallos tolerados y ventana en que se cuentan | 3 en 10 minutos |
bantime |
Duración del bloqueo | 24 h (-1 sería permanente) |
banaction |
Cómo bloquea | ufw, para no crear un tercer conjunto de reglas |
ignoreip |
Quién es inmune | Tu red y tus portátiles |
ignoreip es la protección contra el error más humillante de todos: teclear mal la contraseña de sudo tres veces y que tu propio servidor te bloquee. Si aun así ocurre:
$ sudo fail2ban-client set sshd unbanip 10.0.2.77
$ sudo fail2ban-client set sshd banip 203.0.113.44Y la honestidad que toca: con PasswordAuthentication no del 06-02, fail2ban ya no te protege de que adivinen nada, porque no hay nada que adivinar. Lo que hace es reducir ruido, consumo y superficie: menos conexiones, menos logs, menos oportunidades ante un fallo futuro de sshd. Es una medida de higiene, no la que te salva.
- Verificar desde fuera con
nmap
nmapUn firewall no está verificado hasta que lo miras desde fuera. Desde portatil-alumno, contra tu propia VM:
$ nmap -Pn -p 22,443,5432,8080 10.0.2.15
Nmap scan report for srv-tramontana (10.0.2.15)
PORT STATE SERVICE
22/tcp open ssh
443/tcp open https
5432/tcp open postgresql
8080/tcp filtered http-proxyCómo se lee: open responde, closed responde que no hay nadie, y filtered significa que algo descarta el paquete en silencio — justo el efecto de tu política deny. El 8080 aparece filtered: objetivo cumplido. El 5432 sale open porque el escaneo viene de 10.0.2.77, que está dentro de la red interna permitida; repetido desde fuera de ese rango saldría filtered.
Advertencia legal, sin matices: escanear puertos de sistemas ajenos sin autorización escrita es ilegal en España y en la práctica totalidad de las jurisdicciones, y puede constituir delito aunque no causes daño. En este curso todo escaneo se hace desde portatil-alumno contra srv-tramontana, ambas tuyas y dentro de tu laboratorio. Que una herramienta sea legal no hace legal cualquier uso: la diferencia entre auditoría y ataque es el permiso, y el permiso se documenta por escrito antes de teclear nada.
- Caso Tramontana: la política y el informe a Marta
La política queda así, y se decide antes de tocar ningún comando:
| Servicio | Puerto | Desde dónde | Motivo |
|---|---|---|---|
| SSH | 22/tcp | Cualquiera, con limit |
Administración; endurecido en 06-02 |
| HTTPS | 443/tcp | Cualquiera | Cara pública; el proxy llega en 08-01 |
| PostgreSQL | 5432/tcp | Solo 10.0.2.0/24 |
Nunca desde Internet |
| Aplicación | 8080/tcp | Nadie | Quedará detrás del proxy inverso (08-01) |
| Todo lo demás | — | Denegado | Es una lista blanca |
Diagnóstico cuando «el servicio no responde» y la causa eres tú: primero mira si el servicio escucha (ss -tulpn | grep 8080), después si el firewall lo descarta.
$ sudo journalctl -k --since "-15m" | grep 'UFW BLOCK' | tail -2
ago 18 19:22:41 srv-tramontana kernel: [UFW BLOCK] IN=enp0s3 SRC=203.0.113.44 DST=10.0.2.15 PROTO=TCP SPT=44210 DPT=8080 SYN
ago 18 19:23:02 srv-tramontana kernel: [UFW BLOCK] IN=enp0s3 SRC=198.51.100.9 DST=10.0.2.15 PROTO=TCP SPT=51882 DPT=23 SYNSe lee de izquierda a derecha: entró por enp0s3, desde SRC, hacia DPT (puerto destino). La primera línea es el bot buscando la aplicación en el 8080; la segunda, alguien probando Telnet en 2026. Si quien aparece bloqueado es tu propio servicio legítimo, ya sabes qué regla falta.
El informe a Marta, con la estructura del curso —qué protege y qué no protege—:
Firewall aplicado en srv-tramontana el 18/08. Qué protege: solo son accesibles desde fuera SSH (con limitación de intentos) y HTTPS; la base de datos solo acepta conexiones de la red interna; el puerto 8080 de la aplicación ya no es alcanzable desde Internet; la IP 203.0.113.44, con 47 intentos de acceso, está bloqueada automáticamente 24 horas por fail2ban, y cualquier otra que lo intente lo estará también. Qué NO protege: no defiende de un fallo en la propia aplicación web, que sigue siendo accesible por el puerto que debe estar abierto; no impide nada a quien tenga credenciales válidas; no detecta que ya nos hayan entrado (eso se aborda en la revisión de detección); y no cifra el tráfico, que sigue viajando en claro hasta que instalemos el certificado. Pendiente: cifrado TLS y sacar la contraseña de la base de datos del fichero de configuración.
Advertencia de seguridad y compliance: en un entorno real, cualquier cambio en las reglas de cortafuegos de un sistema que trata datos personales debe planificarse con ventana de mantenimiento, quedar documentado y revisarlo el responsable de seguridad; la exposición de servicios a Internet forma parte del análisis de riesgos exigido por el RGPD. Las técnicas de escaneo y prueba se aplican exclusivamente sobre sistemas propios o con autorización escrita.
Errores Comunes y Consejos
ufw enableantes de permitir SSH. El error clásico y el más caro. Permitir siempre primero, habilitar después, y con la consola de la VM abierta.- Mezclar
ufwy/etc/nftables.conf. Dos conjuntos de reglas compitiendo y unflush rulesetque borra las del otro en cada arranque. Elige uno. - Bloquear todo ICMP. Rompe el descubrimiento de MTU y provoca conexiones que se cuelgan en transferencias grandes. Permite al menos
destination-unreachableytime-exceeded. - Olvidar
iif lo accept. Media máquina deja de funcionar: túneles SSH, bases de datos por socket TCP local, procesos que se hablan por127.0.0.1. - Reglas sin
comment. Dentro de seis meses nadie se atreverá a borrar una regla que no sabe para qué está, y el ruleset solo crecerá. logsinlimit rate. Un escaneo te llena/var/logen minutos, y quedarte sin disco es una caída autoinfligida. Y no confíes solo en el firewall para lo interno: la aplicación debería escuchar únicamente en127.0.0.1de todas formas.- Consejo: guarda
/etc/nftables.confo la salida deufw status numbereden git, y añade la comprobación de que el firewall está activo arevision_salud.sh.
Ejercicios
- La política completa desde cero. Escribe la secuencia exacta de comandos
ufwpara aplicar la política de la tabla del apartado 12 en una máquina remota, en el orden correcto, incluyendo las comprobaciones antes y después. - Regla que
ufwno expresa bien. Marta pide que soloportatil-luis(10.0.2.30) pueda acceder al 8080 para pruebas, con un máximo de 10 conexiones nuevas por minuto. Escribe la regla denftablesy explica por quéufwse queda corto. - Autobloqueo. Luis llama: no puede entrar por SSH desde 10.0.2.77 y dice que «el servidor le ha echado». Diagnostica el problema, resuélvelo y propón la corrección permanente.
Soluciones
1. Con la consola de la VM abierta y una segunda sesión SSH activa:
# Medir antes
$ sudo ufw status verbose > /root/ufw-antes.txt ; ss -tulpn | grep LISTEN
# Políticas por defecto
$ sudo ufw default deny incoming && sudo ufw default allow outgoing
$ sudo ufw default deny routed
# SSH PRIMERO, siempre; después el resto
$ sudo ufw limit 22/tcp comment 'SSH administracion'
$ sudo ufw allow 443/tcp comment 'HTTPS publico'
$ sudo ufw allow from 10.0.2.0/24 to any port 5432 proto tcp comment 'PostgreSQL interno'
# Habilitar y verificar
$ sudo ufw enable && sudo ufw status numbered
$ sudo diff -u /root/ufw-antes.txt <(sudo ufw status verbose)Y la verificación que de verdad cierra el trabajo, desde portatil-alumno: abrir una sesión SSH nueva sin cerrar la anterior, y nmap -Pn -p 22,443,5432,8080 10.0.2.15 para comprobar que el 8080 aparece filtered. Fíjate en que el 8080 no necesita ninguna regla: la política deny incoming ya lo cubre. En una lista blanca, lo que no se nombra queda prohibido.
2. La regla en nftables, dentro de la cadena input y antes del log final:
ufw se queda corto por dos motivos: solo ofrece limit, con un umbral fijo (6 conexiones en 30 segundos) que no se puede ajustar sin editar sus plantillas internas, y no permite combinar en una sola regla origen concreto, puerto, estado de conexión y tasa personalizada. Aquí necesitamos exactamente eso. La alternativa dentro de ufw sería sudo ufw allow from 10.0.2.30 to any port 8080 proto tcp, que da el control de origen pero no la limitación de tasa.
Detalle importante: ct state new hace que la tasa se aplique solo a conexiones nuevas. Sin él, limitarías también los paquetes de una transferencia en curso y provocarías cortes aleatorios que parecerían un problema de red.
3. Diagnóstico en tres comandos:
$ sudo fail2ban-client status sshd | grep 'Banned IP list'
`- Banned IP list: 203.0.113.44 10.0.2.77
$ sudo journalctl -u fail2ban --since "-1h" | grep 10.0.2.77
fail2ban.actions [1204]: NOTICE [sshd] Ban 10.0.2.77Luis ha fallado tres veces en diez minutos —probablemente con una clave equivocada o sin cargar en el agente— y la jaula sshd lo ha bloqueado 24 horas. Desbloqueo inmediato y corrección permanente:
$ sudo fail2ban-client set sshd unbanip 10.0.2.77
$ grep ignoreip /etc/fail2ban/jail.local
ignoreip = 127.0.0.1/8 ::1 10.0.2.0/24Y aquí está la lección de verdad: ignoreip ya incluía 10.0.2.0/24, así que si el bloqueo se produjo es porque el fichero se editó sin recargar el servicio, o porque la línea se escribió en jail.conf en lugar de en jail.local y una actualización del paquete se la llevó. La corrección permanente es sudo fail2ban-client reload tras cada cambio, verificar con sudo fail2ban-client get sshd ignoreip, y añadir esa comprobación a revision_salud.sh. La causa raíz de fondo es que Luis usa contraseña donde debería usar su clave ed25519: revisa su authorized_keys.
Conclusión
srv-tramontana ya no habla con cualquiera. Tiene una política de lista blanca escrita, aplicada en el orden correcto y verificada desde fuera con nmap: SSH limitado, HTTPS abierto, PostgreSQL restringido a la red interna y el 8080 invisible desde Internet a la espera del proxy inverso del Módulo 8. Sabes que debajo de ufw hay nftables y que debajo de nftables está netfilter; sabes escribir un ruleset inet completo con estado de conexión, conjuntos y limitación de tasa; has ajustado los sysctl que evitan suplantación, redirecciones e inundaciones SYN; y fail2ban bloquea automáticamente a 203.0.113.44 y a quien venga detrás, sin bloquearte a ti. El primero de los tres incidentes abiertos queda cerrado.
Y ahora la parte incómoda. Todo lo que has montado en estas tres lecciones es prevención, y la prevención falla: falla por un CVE que aún no tiene parche, por una credencial filtrada —como la de aquella copia con permisos 644—, por un despiste al abrir una regla «solo un momento» o porque alguien con acceso legítimo hace algo que no debería. El día que falle, tu firewall no te dirá nada: seguirá aplicando obedientemente las reglas mientras la conexión del atacante figura como established. La pregunta deja de ser «¿cómo impido que entren?» y pasa a ser «¿cómo me entero de que ya han entrado?». Eso es la lección 06-04: Sistemas de Detección de Intrusos, donde montarás AIDE para vigilar la integridad de los ficheros, pondrás auditd a seguir cada escritura sobre app.conf, buscarás las señales de compromiso en los logs, pasarás Lynis a tu servidor y aprenderás el procedimiento de respuesta a incidentes — incluida la regla que a nadie le gusta oír: un servidor comprometido se reinstala, no se limpia.
Curso de Linux: De Principiante a Administrador de Sistemas
Módulo 1: Introducción a Linux
- ¿Qué es Linux?
- Historia de Linux
- Distribuciones de Linux
- Instalando Linux
- Primer Contacto con el Sistema
- Estructura del Sistema de Archivos de Linux
Módulo 2: Comandos Básicos de Linux
- Introducción a la Línea de Comandos
- Obtener Ayuda y Documentación del Sistema
- Navegando el Sistema de Archivos
- Operaciones con Archivos y Directorios
- Visualización y Edición de Archivos
- Enlaces Duros y Simbólicos
- Permisos y Propiedad de Archivos
Módulo 3: Habilidades Avanzadas en la Línea de Comandos
- El Entorno del Shell: Variables, Alias e Historial
- Uso de Comodines y Expresiones Regulares
- Búsqueda de Archivos y Contenido: find, locate y grep
- Tuberías y Redirección
- Procesamiento de Texto: cut, sort, uniq, sed y awk
- Gestión de Procesos
- Programación de Tareas con Cron
- Comandos de Redes
Módulo 4: Scripting en Shell
- Introducción al Scripting en Shell
- Variables y Tipos de Datos
- Entrada, Salida y Argumentos de un Script
- Estructuras de Control
- Funciones y Librerías
- Depuración y Manejo de Errores
- Scripts de Producción: Buenas Prácticas
Módulo 5: Administración del Sistema
- Gestión de Usuarios y Grupos
- sudo y Permisos Especiales
- Gestión de Paquetes
- Gestión de Discos
- systemd y la Gestión de Servicios
- Registros del Sistema: journald y syslog
- Monitoreo del Sistema y Optimización del Rendimiento
- Respaldo y Restauración
Módulo 6: Redes y Seguridad
- Configuración de Redes
- SSH y Acceso Remoto
- Firewall y Seguridad Perimetral
- Sistemas de Detección de Intrusos
- Gestión de Secretos y Certificados TLS
- Asegurando Sistemas Linux
Módulo 7: Temas Avanzados
- El Proceso de Arranque y la Recuperación del Sistema
- Diagnóstico Avanzado: strace, perf y eBPF
- Optimización del Kernel de Linux
- Virtualización con Linux
- Contenedores de Linux y Docker
- Automatización con Ansible
- Alta Disponibilidad y Balanceo de Carga
