Introducción a Nftables

Esta frase creo que puede resumir de qué vamos a hablar hoy, hoy vamos a hablar de la evolución del firewall en Linux, la cual en los últimos años se había desmadrado un poco.

Por un lado teníamos iptables, por otro ebtables y luego la configuración y el funcionamiento de ambos no estaban homogeneizados, además de los problemas de configuración de repetir trozos, etc…

A día de hoy tenemos un software para controlarlo todo con un mejor rendimiento y una sintaxis más adaptada a los tiempos que corren, se llama nftables y ya lo podéis encontrar en nuestro queridísimo Debian 10.

Y aún sin haber escrito ni una sola línea nunca de nftables podréis empezar a utilizar nftables porque en iptables tenemos un fantástico conversor, el iptables-translate.

Pero antes de meternos con comandos y demás voy a contaros un poco como se organiza todo esto suponiendo que no sabéis nada ni de iptables ni de nftables ni de nada parecido, voy a intentarlo.

Por un lado tenemos las reglas (rules) que son las acciones y se configuran dentro de una cadena o chain. Una regla sería por ejemplo tira todo el tráfico de entrada al puerto 25.

Por otro lado tenemos las cadenas (chains) que son un grupo de reglas (rules) que están dentro de tables (tables). Una cadena sería por ejemplo el conjunto de reglas que aplican a las redes autorizadas a acceder por el cliente.

Y por último tenemos las tables (tables) que son simplemente el contenedor de las cadenas. Lo normal en una máquina estándar es que hayan muy poquitas tablas, incluso que sólo haya una, pero claro, depende de quien lo configure.

Con esto ya podéis ver cual es la organización. La idea es que podamos ver la tabla y sólo con leer las cadenas podamos saber qué hace ese firewall. Creedme si os digo que es muy más sencillo verlo así, es algo parecido a programar, cuando programáis no ponéis todo el código línea tras línea, usáis funciones, que serían las cadenas, y si es necesario podéis reutilizar código.

Un ejemplo podría ser el autorizar el acceso SSH desde las IPs de gestión, pero también permitiremos desde esas mismas IPs el acceso al puerto de DNS. En este caso podemos definir las reglas correspondientes a esas máquinas autorizadas y definirlas en la cadena «Autorizadas», luego podemos decir que pueden acceder al puerto de SSH son las «Autorizadas» y también podemos decir que tienen acceso al DNS las máquinas «Autorizadas», sin tener que definir otra vez las reglas porque podemos reutilizarlas.

Si os dais cuenta la herramienta ip_set ya no será necesaria en nftables porque ya lo hace por defecto.

Si empezamos por los comandos relacionados con las tables tendremos lo más sencillo, aunque lo primero que tenemos que saber es cual es el comando de nftables que no es otro que nft, con ese comando se hace todo.

Obviamente para escribir reglas antes tenemos que tener una cadena y para definir cadenas tenemos que tener una tabla.

Así que lo primero será definir una tabla, aunque por defecto tenemos una llamada filter de tipo IP.

Las tables pueden ser de tipo ip, arp, ip6, bridge, inet, netdev.

Por supuesto las tablas podemos añadirlas, borrarlas, vaciarlas o listarlas:

nft (list | add | delete | flush) table [<family>] <name>
nft list tables [<family>]

Y en el caso de list podemos añadir -n para mostar la información en formato numérico y -a para mostrar el handle.

Las tablas realmente no tienen mucho más misterio

Ahora vamos a meternos con las cadenas, los chains, aquí ya tenemos bastante más chica.

Necesitamos definir las cadenas para poder incluir dentro las reglas y la cadena tiene que estar dentro de una tabla.

Tenemos que saber antes de nada que las cadenas pueden ser de tres tipos (type):

  • filter: arp, bridge, ip, ip6 and inet
  • route: soportado por ip e ip6 y es lo que era el mangle, es decir, modificar paquetes antes de reenviar.
  • nat: ip e ip6, ojo que no podemos hacer nat en una tabla definida como inet, tened cuidado con esto para no volveros locos.

Además en una cadena tenemos que definir un anclaje (hook), que no es más que el estado en el que se encuentre el paquete:

  • Para ip, ip6 e inet: prerouting, input, forward, output, postrouting.
  • Para arp: input, output.
  • Para netdev is: ingress.

El tercero de los parámetros es la prioridad (priority) que define el orden en el que se van a ejecutar esa cadena respecto a otras en la misma tabla.

La prioridad más baja se procesa antes y los números pueden ser negativos, en el ejemplo procesaremos primero la segunda porque tiene un número más pequeño.

chain input {
type filter hook input priority -20; policy accept;
}
chain input {
type filter hook input priority -30; policy accept;
}

Y el último parámetro es la política (policy) que nos dice qué hacer con el paquete, puede ser accept, drop, queue, continue o return.

Y ahora vamos a por las reglas que son la parte más compleja de nftables en cuanto a sintaxis, pero el funcionamiento ya veis como es.

Definimos una tabla de un tipo, luego definimos la cadena de un tipo, con un hook para definir el momento donde hay que aplicar la cadena y definimos una prioridad para las reglas.

Las reglas en si tienen bastante variedad y sería imposible que en un podcast os comente todas las posibilidades, de momento el ejemplo más fácil posible:

nft add table inet firewall
nft add chain inet firewall trafico_no_aceptado {type filter hook input priority 0\;} 
nft add rule inet firewall trafico_no_aceptado ip saddr 192.168.14.5 drop

Ahora vamos a crear un ip set para poder añadir más ips y no tener que definir constantemente las IPs en el firewall

nft add set inet firewall prohibidas { type ipv4_addr\;}
nft add element inet firewall prohibidas { 192.168.14.4 }
nft add element inet firewall prohibidas { 192.168.15.4, 192.168.15.6 }

Y ahora agregamos el ip set al chain

nft add rule inet firewall trafico_no_aceptado ip saddr @prohibidas counter drop

Y creamos otro chain

nft add chain inet firewall trafico_aceptado
nft add rule inet firewall trafico_aceptado ip saddr 192.168.18.4 accept
nft add rule inet firewall trafico_aceptado ip saddr 192.168.18.0/24 drop

A ver cómo ha quedado:

root@andromeda:~# nft list ruleset
table ip filter {
}
table inet firewall {
    set prohibidas {
        type ipv4_addr
        elements = { 192.168.14.4, 192.168.15.4,
                 192.168.15.6 }
    }
chain trafico_no_aceptado {
    ip saddr 192.168.14.5 drop
    ip saddr @prohibidas counter packets 0 bytes 0 drop
}

chain trafico_aceptado {
    ip saddr 192.168.18.4 accept
    ip saddr 192.168.18.0/24 drop
}
}

Ahora vamos a añadir que una IP pueda acceder al puerto 53 de UDP.

nft add rule inet firewall trafico_no_aceptado udp dport {53}  ip saddr 192.168.18.15 
root@andromeda:~# nft list ruleset
table ip filter {
}
table inet firewall {
    set prohibidas {
        type ipv4_addr
        elements = { 192.168.14.4, 192.168.15.4,
                 192.168.15.6 }
    }
chain trafico_aceptado {
    ip saddr 192.168.18.4 accept
    ip saddr 192.168.18.0/24 drop
}

chain trafico_no_aceptado {
    type filter hook input priority 0; policy accept;
    ip saddr 192.168.14.5 drop
    ip saddr @prohibidas counter packets 0 bytes 0 drop
    udp dport { domain } ip saddr 192.168.18.15
}
}

Bueno, esto es ir jugando con la consola un poco, otro día nos dedicamos a ver cómo va el NAT y más cosas, pero antes vamos a ver esto como se guarda en el sistema, pero de momento hemos configurado por consola, pero si rearrancamos el servicio la configuración se perderá. La cosa es muy sencillita, tenéis que guardarlo en /etc/nftables.conf.

nft list ruleset > /etc/nftables.conf

Cuando rearranquéis el servicio después de guardar la configuración ya no perderéis la configuración del firewall.

Por cierto, para rearrancar:

systemctl restart nftables.service 

Hasta aquí el programa de hoy, pero me da que podríamos ir haciendo más capítulos sobre nftables porque es un tema lo bastante amplio como para dedicarle algún capítulo más.