Home Linux NFTABLES, diseño de un firewall moderno

NFTABLES, diseño de un firewall moderno

by José Luis Sánchez Borque

IPTABLES HA MUERTO, LARGA VIDA A NFTABLES

(Primera parte)

Empiezo con este Post, el primero de una serie dedicada a Nftables. La evolución de Iptables. Ya nada justifica seguir empleando iptables en sistemas modernos, salvo el desconocimiento, que se arregla con formación, y la falta de tiempo para adaptar los scripts de iptables al nuevo entorno Nftables, que se arregla con planificación.

NFTABLES es la evolución hacia el filtrado de paquetes que hasta ahora representaba iptables.

  • Está disponible desde kernels Linux 3.13
  • Tiene una nueva interfaz de comando cuya sintaxis es diferente de iptables
  • Proporciona una nueva infraestructura que permite construir mapas y concatenaciones. Esta nueva característica le permite organizar un conjunto de reglas en un árbol multidimensional, lo que reduce la cantidad de reglas que deben consultarse drásticamente hasta encontrar la acción que se aplica en el paquete.

¿Por qué usar NFTABLES?

  • Evita la duplicación de código y las inconsistencias.
  • Ordene los paquetes más rápido con un conjunto de mapeo de datos mejorado
  • Simplifica la administración de ipv4 e ipv6 con una nueva familia, inet, que le permite registrar cadenas que pueden ver el tráfico ipv4 e ipv6
  • Proporciona una API de Netlink para aplicaciones de terceros.
  • Proporciona una sintaxis más amigable y compacta

Ganchos Netfilter (Netfilter Hooks)

La mayor parte de la infraestructura sigue siendo la misma, nftables reutiliza la infraestructura de enlace existente, el sistema de seguimiento de conexiones, el motor NAT, el registro de infraestructura, la gestión de colas, etc. Por lo tanto, solo se ha reemplazado la infraestructura de clasificación de paquetes.

Para aquellos que no están familiarizados con Netfilter, aquí hay una representación de estos ganchos (hooks)

200416-NFTABLES-HOOKS

Básicamente, el flujo de tráfico entrante a la máquina local verá el enlace entrante y el enrutamiento previo. Luego, el tráfico generado por los procesos locales sigue la ruta de salida y el enrutamiento posterior. Ruta superior en el gráfico anterior.

Y luego, los paquetes que no están destinados a nuestra máquina serán vistos por el gancho (hook) de entrada (previo a la decisión de enrutamiento). En resumen, los paquetes que no se dirigen a un proceso local (como un servidor Web) seguirán esta ruta: prerouting -> decisión de enrutamiento -> forward -> postrouting. Ruta inferior en el gráfico anterior.

Gancho (Hook) de entrada (Ingress)

Desde el kernel 4.2 de Linux, Netfilter también viene con un enlace de entrada que se puede usar desde nftables. Entonces el proceso ahora será el siguiente:

200416-NFTABLES-HOOKS-INGRESS

Podemos usar este nuevo enlace de ingreso para filtrar el tráfico de Capa 2 (MACs o VLANs), este nuevo enlace encontrado antes del enrutamiento previo.

nft -- add chain netdev filter input { type filter hook ingress device vlan100 priority -500 \; policy accept \; }

Trabajar con TABLAS

NFTABLES se compone de tablas, y tablas de cadenas. Similar a iptables.

Lo primero que tenemos que hacer para comenzar a trabajar con nftables es agregar al menos una tabla. Luego, podemos agregar cadenas y agregaremos las reglas a esas cadenas.

Tenemos seis tipos diferentes de tablas según la familia a la que pertenecen:

  • Ip (ipv4)
  • arp
  • ip6
  • Bridge
  • inet, que está disponible desde el kernel de Linux 3.14. Esta tabla es una tabla híbrida IPV4 + IPV6 que debería ayudar a simplificar la carga de la administración de firewall doble. Por lo tanto, las cadenas que registramos en esta tabla servirán para ver el tráfico IPv4 e IPv6.
  • netdev, disponible desde el kernel 4.2 de Linux. Esta familia viene con un enlace de entrada que se puede usar para registrar una cadena que filtra en una etapa muy temprana, es decir, antes del enrutamiento, como una alternativa a la infraestructura existente.

Veamos un ejemplo, que es como se aprende:

En primer lugar deciros la versión con la que estoy trabajando de Linux:

root@nft-fw:~# lsb_release -a

No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 18.04.4 LTS
Release: 18.04
Codename: bionic

Luego nos aseguramos si tenemos instalado ya nftables en nuestro sistema:

root@nft-fw:~# apt-cache search nftables

nftables - Program to control packet filtering rules by Netfilter project

Si no lo tuviéramos toca instalarlo:

root@nft-fw:~# apt install nftables

root@nft-fw:~# systemctl enable nftables.service

La orden systemctl enable nftables.service establece que el servicio nftables arranque con el sistema.

root@nft-fw:~# service nftables status

● nftables.service - nftables
  Loaded: loaded (/lib/systemd/system/nftables.service; enabled;
  Active: active (exited) since Sun 2020-04-19 09:06:06 CEST; 6min ago

El arranque toma por defecto la configuración establecida en el fichero /etc/nftables.conf.

root@nft-fw:~# cat /etc/nftables.conf

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
  chain input {
  type filter hook input priority 0;
}

chain forward {
  type filter hook forward priority 0;
}

chain output {
  type filter hook output priority 0;
}
}

Obtenemos el mismo resultado con nft list ruleset:

root@nft-fw:~# nft list ruleset

table inet filter {

chain input {
  type filter hook input priority 0; policy accept;
}

chain forward {
  type filter hook forward priority 0; policy accept;
}

chain output {
  type filter hook output priority 0; policy accept;
}
}

Para el primer ejemplo, la idea es hacer un script que ejecutaremos desde la línea de comandos con nft -f nombredelfichero. Dejamos para más adelante como meterlo en /etc/nftables.conf.

Definamos las especificaciones del diseño de nuestro primer ejemplo.

Vamos a configurar un firewall en un Servidor con una sola interface de red, Servidor Bastión (una sola tarjeta de red), servidos bastión, llamarlo como queráis. La IP de nuestra máquina es la 192.168.153.134/24.

Los criterios son los siguientes:

  • Borrar las reglas iniciales que puedan existir.
  • Solo nos preocuparemos de IPv4, dejando ipv6, nat, mangle, etc.. para ejemplos posteriores.
  • Política por defecto DROP en nuestra máquina
  • Permitir acceso ssh al servidor desde cualquier máquina de la red local
  • Permitir tráfico de salida dns (udp/53)
  • Permitir tráfico de salida http, https, ssh (tcp/22,tcp/80,tcp/443)
  • Queremos llevar cuenta de los paquetes que cumplen cada regla para estudio y optimización posterior del firewall.

Como ya hice con los POST de IPTABLES, me niego a marearos añadiendo las órdenes a pelo desde Shell, borrando, insertando, listando, etc… Trabajo como se haría en un entorno real, es decir un script de toda la vida…

Os adjunto el Script documentado:

root@nft-fw:~/fw# cat reglas.nft

#!/usr/sbin/nft -f
#
############################################
# Script nft v1.0                          #
# José Luis Sánchez                        #
# https://www.mytcpip.com                  #
# Fichero: /root/fw/reglas.nft             #
#                                          #
# Definición script                        #
# =================                        #
# - Solo IPv4                              #
# - Política por defecto DROP              #
# - Permitir acceso ssh al servidor desde  #
#   cualquier máquina de la red local      #
# - Permitir tráfico udp de salida 53      #
# - Permitir tráfico tcp de salida 22,     #
#   80, 443                                #
#                                          #
# En todos los casos añadimos counter al   #
# final para saber el tráfico asociado a   #
# cada regla.                              #
#                                          #
# Ejecutar el script con la orden:         #
#                                          #
# nft -f nombredelscript                   #
#                                          #
############################################

# Declaramos variables que utilizamos en el script

define RedLocal = 192.168.153.0/24
define udpSI = { 53 }
define tcpSI = { 22, 80, 443 }

# Borramos cualquier regla establecida en el sistema

flush ruleset

# Declaramos la tabla "filtrado" asociada a la familia "ip"
# Por lo tanto estamos solo aplicando estas reglas a IPv4

add table ip filtrado

# Establecemos política por defecto DROP en las cadenas input, output y forward
# asociadas a la tabla filtrado

add chain ip filtrado INPUT   { type filter hook input priority 0; policy drop; }
add chain ip filtrado FORWARD { type filter hook forward priority 0; policy drop; }
add chain ip filtrado OUTPUT  { type filter hook output priority 0; policy drop; }

# Permitimos cualquier tipo de tráfijo local loopback ( nombre interface lo )

add rule ip filtrado INPUT iifname lo counter accept
add rule ip filtrado OUTPUT oifname lo counter accept

# Permitimos acceso ssh al servidor desde cualquier máquina de la REd Local
# definida en la variable RedLocal

add rule ip filtrado INPUT  ip saddr $RedLocal tcp dport 22 counter accept
add rule ip filtrado OUTPUT ip daddr $RedLocal tcp sport 22 counter accept

# Permitimos todos los protocolos UDP definidos en la variable udpSI

add rule ip filtrado OUTPUT udp dport $udpSI counter accept
add rule ip filtrado INPUT  udp sport $udpSI counter accept

# Permitimos todos los protocolos TCP definidos en la variable tcpSI

add rule ip filtrado OUTPUT tcp dport $tcpSI counter accept
add rule ip filtrado INPUT  tcp sport $tcpSI counter accept

Nft se encarga de aplicar las reglas de OUTPUT e INPUT a todos los puertos definidos en las variables udpSI y tcpSI. NO es necesario un bucle como con iptables. Al menos pare esto.

Para ejecutarlo bastará con llamar al script desde la línea de comandos:

root@nft-fw:~# nft -f reglas.nft

No es necesario que el Script /root/fw/reglas.nft tenga permiso de ejecución..

Podremos comprobar con nft list ruleset las reglas activas. Nos os copio toda la salida por pantalla, solo una parte que aprovecho para comentaros un tema. En verde os he marcado los contadores asociados a cada regla. Nos permitirán establecer los protocolos más utilizados. Conviene por rendimiento que dichos protocolos sean las primeras reglas que se procesen..

root@nft-fw:~# nft list ruleset

....
....
....
table ip filter {

chain INPUT {
  type filter hook input priority 0; policy drop;
  iifname "lo" counter packets 0 bytes 0 accept
  ip saddr 192.168.153.1 tcp dport ssh counter packets 73 bytes 5224 accept
}

chain FORWARD {
  type filter hook forward priority 0; policy drop;
}

chain OUTPUT {
  type filter hook output priority 0; policy drop;
  oifname "lo" counter packets 0 bytes 0 accept
  ip daddr 192.168.153.1 tcp sport ssh counter packets 39 bytes 4200 accept
}
....
....
....

Recordar que siempre podéis borrar todo desde Shell con:

root@nft-fw:~# nft flush ruleset

En otro POST seguiremos con otros ejemplos más complejos, con enrutamiento y NAT aplicado.

Ejemplo 2 – Postrouting


En este segundo ejemplo vemos el típico ejemplo de un enrutador Linux que da acceso a Internet a través de la tarjeta de red que conecta con la red pública. En el esquema adjunto tenemos la red interna 192.168.153.0/24 que debe tener conectividad hacia el exterior a través de la tarjeta de red que cuelga de dicha red. Conceptualmente la hemos llamado Internet, pero está claro que puede ser una red cualquiera siempre que no disponga de enrutamiento hacía la red Interna, concepto que no siempre todos entienden.

Las especificaciones de nuestro diseño serán las siguientes:

  • Mantenemos las mismas políticas y reglas que anteriormente hemos definido en el ejemplo anterior.
  • Definiremos dos variables que hagan referencia a las tarjetas de red ens33 y ens39para ser más entendible todo y hacer el script más portable.
  • Permitir todos los protocolos TCP  desde la red Local hacia Internet definidos en la variable LtcpSI
  • Permitir todos los protocolos UDP desde la red Local hacia Internet definidos en la variable LudpSI
  • Recordar que estamos en política por defecto DROP
  • Quitar los counter del ejercicio anterior, ya que en entorno de producción mejor no ponerlos
  • Ningun puerto ni tráfico permitido desde fuera hacía la red Local

Bien pues aquí tenéis el script, que está comentado y es fácilmente interpretable si vienes del mundo IPTABLES:

#!/usr/sbin/nft -f
############################################
# Script nft v1.0                          #
# José Luis Sánchez                        #
# https://www.mytcpip.com                  #
#                                          #
# Definición script                        #
# =================                        #
# - Solo IPv4                              #
# - Política por defecto DROP              #
# - Permitir acceso ssh al servidor desde  #
# cualquier máquina de la red local        #
# Tráfico propio del router                #
# - Permitir tráfico udp de salida 53      #
# - Permitir tráfico tcp de salida 80,     #
#   443, 22                                #
# Tráfico desde Red Interna hacia          #
# el exterior                              #
# - Permitir tcp:80,443 y udp:53           #
#                                          #
# Ejecutar el script con la orden:         #
#                                          #
# nft -f nombredelscript                   #
#                                          #
############################################

# Declaramos variables que utilizamos en el script

define RedLocal = 192.168.153.0/24
define udpSI = { 53 }
define tcpSI = { 22, 80, 443 }
define LudpSI = { 53 }
define iwan = "ens33"
define ilan = "ens39"

# Borramos cualquier regla establecida en el sitema

flush ruleset

# Declaramos la tabla "filtrado" asociada a la familia "ip"
# Por lo tanto estamos solo aplicando estas reglas a IPv4

add table ip filtrado
add table ip nat

# Establecemos política por defecto DROP en las cadenas input, output y forward
# asociadas a la tabla filtrado

add chain ip filtrado INPUT { type filter hook input priority 0; policy drop; }
add chain ip filtrado FORWARD { type filter hook forward priority 0; policy drop; }
add chain ip filtrado OUTPUT { type filter hook output priority 0; policy drop; }

add chain ip nat POSTROUTING { type nat hook postrouting priority 50; }

add rule nat POSTROUTING ip saddr $RedLocal oif $iwan masquerade

# Permitimos cualquier tipo de tráfijo local loopback ( nombre interface lo )

add rule ip filtrado INPUT iif lo counter accept
add rule ip filtrado OUTPUT oif lo counter accept

# Permitimos acceso ssh al servidor desde cualquier máquina de la REd Local
# definida en la variable RedLocal

add rule ip filtrado INPUT ip saddr $RedLocal tcp dport 22 accept
add rule ip filtrado OUTPUT ip daddr $RedLocal tcp sport 22 accept

# Permitimos todos los protocolos UDP definidos en la variable udpSI

add rule ip filtrado OUTPUT udp dport $udpSI accept
add rule ip filtrado INPUT udp sport $udpSI accept

# Permitimos todos los protocolos TCP definidos en la variable tcpSI

add rule ip filtrado OUTPUT tcp dport $tcpSI accept
add rule ip filtrado INPUT tcp sport $tcpSI accept

# Trafico permitido desde red Local hacia Internet TCP

add rule ip filtrado FORWARD iif $ilan ip saddr $RedLocal oif $iwan tcp dport $LtcpSI accept
add rule ip filtrado FORWARD iif $iwan oif $ilan ip daddr $RedLocal tcp sport $LtcpSI accept

# Trafico permitido desde red Local hacia Internet TCP

add rule ip filtrado FORWARD iif $ilan ip saddr $RedLocal oif $iwan udp dport $LudpSI accept
add rule ip filtrado FORWARD iif $iwan oif $ilan ip daddr $RedLocal udp sport $LudpSI accept

Fijaros que iifname o oifname se puede resumir por iif o oif respectivamente.

Bien una vez ejecutado es escript con nft -f reglas1.nft, podemos ver las reglas como antes, con nft list ruleset  :

root@nft:~/fw# nft -f reglas1.nft
root@nft:~/fw# nft list ruleset
table ip filtrado {
 chain INPUT {
  type filter hook input priority 0; policy drop;
  iif "lo" counter packets 0 bytes 0 accept
  ip saddr 192.168.153.0/24 tcp dport ssh accept
  udp sport { domain } accept
  tcp sport { ssh, http, https } accept
 }

 chain FORWARD {
  type filter hook forward priority 0; policy drop;
  iif "ens39" ip saddr 192.168.153.0/24 oif "ens33" tcp dport { http, 433 } accept
  iif "ens33" oif "ens39" ip daddr 192.168.153.0/24 tcp sport { http, 433 } accept
  iif "ens39" ip saddr 192.168.153.0/24 oif "ens33" udp dport { domain } accept
  iif "ens33" oif "ens39" ip daddr 192.168.153.0/24 udp sport { domain } accept
 }

 chain OUTPUT {
  type filter hook output priority 0; policy drop;
  oif "lo" counter packets 0 bytes 0 accept
  ip daddr 192.168.153.0/24 tcp sport ssh accept
  udp dport { domain } accept
  tcp dport { ssh, http, https } accept
 }
}
table ip nat {
 chain POSTROUTING {
  type nat hook postrouting priority 50; policy accept;
  ip saddr 192.168.153.0/24 oif "ens33" masquerade
 }
}

Un detalle a tener en cuenta…Fijaros que he puesto 50 en la priority del postrouting. ¿Porqué?

En este caso no tiene ninguna importancia si ponemos 50, 10, 150 o 3…. ya que no hay más cadenas (chain) aplicadas a la tabla nat, y por lo tanto esa priority no se compara con ninguna otra de la misma tabla… En casos más complicados podremos jugar con dicha prioridad.

Recordar que estos script no son persistentes.. Tendremos que guardarlo en el fichero /etc/ntables.conf y asegurarnos que el servicio arranca con el sistema.. Todo esto y más en el siguiente Post.

Recordar también para que nuestra máquina ubuntu actúe actúe como enrutador debemos habilitarlo como tal:

Manera 1:  el echo de toda la vida….  que habría que añadirlo al inicio del script, no os olvidéis

echo 1 > /proc/sys/net/ipv4/ip_forward

Manera 2: Archivo /etc/sysctl.conf, de forma persistente.

Dentro del archivo hay una linea que activa o no el forwarding. Está comentada, la debemos descomentar:

net.ipv4.ip_forward = 1

Para aplicar los cambios sin reiniciar nuestra máquina basta con ejecutar:

sysctl -p

Cualquier duda, podéis dejar un comentario y os respondo así pueda.. que la vida no me da para más 😊

Os dejo el video con una demo de lo explicado en el Servidor Alone, el primer ejemplo:

You may also like

2 comments

Rolando junio 3, 2020 - 1:02 am

Hola desde Argentina! Creo que hay un error en la cadena de FORWARD
chain FORWARD {


iif «ens33» oif «ens39» ip daddr 192.168.153.0/24 tcp sport { http, 433 } accept (corregido)

iif «ens33» oif «ens39» ip daddr 192.168.153.0/24 udp sport { domain } accept (corregido)
}
Cuando el paquete vuelve desde Internet el destination address es la red local (daddr en vez de saddr)

Saludos. es un muy buen post!!!

Reply
José Luis Sánchez Borque junio 3, 2020 - 11:26 am

Hola Rolando,

muchas gracias por tu apreciación… Es cierto, se me escapó una «s» en vez de una «d» en el script diseñado con variables.

Ya lo he cambiado en ambos sitios.

Gracias por tu comentario. Espero te haya sido útil este Post, y espero en breve complementarlo con más cosas, como por ejemplo: PREROUTING.

Saludos.

Reply

Leave a Comment