nftables: Estrutura de Firewall Linux

Saudações. Nesse artigo e tutorial vou apresentar a vocês o nftables (nft), a nova estrutura de firewall do Kernel Linux.

Pré-requisitos (constam em outros artigos aqui do blog):

  • Instalação do Linux (Debian);
  • Internet no servidor;

Cuidados:

  • Não execute este tutorial em servidores que ja tenham:
    • Firewall UFW
    • Docker ou Podman

1 – Sobre o nftables

O Kernel Linux passa por 3 sistemas de firewall:

  • ipchains: o primeiro framework para firewall e NAT, descontinuado;
  • iptables netfilter: framework legado e mais utilizado;
  • nftables: versão nova do framework, com mais performance e compatibilidade extrema (eBPF e afins);

O Firewall do Linux é feito a nivel de kernel por módulos da camada netfilter. O iptables era o comando usado para gerir os módulos e regras na camada netfilter.

Recentemente as distribuições migraram para o nftables. A compatibilidade é mantida por meio do iptables-nft, que traduz os argumentos de iptables para nft e aplica as regras.

Vamos configurar nosso Debian para que ele seja 100% guiado por regras de nftables.

Instalando

Bash
# Instalar NFTABLES:
apt -y install iptables;
apt -y install nftables;

# Instalar ferramentas relacionadas:
apt -y install conntrack;

# Ativar execução do nftables durante o boot:
systemctl enable nftables;

Preparativos

Bash
# Desativar firewall para reestruturacao:
systemctl stop nftables;

# Backup da configuração original:
[ -f /etc/nftables.conf.orig ] || cp /etc/nftables.conf /etc/nftables.conf.orig;

# Retirar defaults (debian: .conf, alpine: .nfg)
rm -f /etc/nftables.conf 2>/dev/null;
rm -f /etc/nftables.nft  2>/dev/null;

# Criar configuracao baseada nos arquivos ordenados
# da pasta /etc/nftables.d/
mkdir -p /etc/nftables.d;
(
    echo '#!/usr/sbin/nft -f'
    echo
    echo 'flush ruleset'
    echo 'include "/etc/nftables.d/*.conf"'
    echo 'include "/etc/nftables.d/*.nft"'
    echo
) > /etc/nftables.conf;

# Unificar diferentes distribuicoes no mesmo arquivo, oficial: .conf
ln -sf /etc/nftables.conf /etc/nftables.nft;

Com a configuração acima, nosso firewall se baseará na existencia de arquivos no diretório /etc/nftables.d

Vamos criar arquivos com 2 extensões diferentes:

  • *.conf: arquivos que definirão tabelas principais padronizadas;
  • *.nft: arquivos que criarão estruturas personalizadas de firewall, tabelas, listas, etc…;

2 – Estrutura de firewall

Com essas configurações, vou recriar no nftables os mesmos nomes da estrutura legada do iptables, com isso poderemos usar regras de nftables (nft) e tornar nosso firewall compatível com iptables-nft.

Estruturas:

  • RAW: pre-processamento bruto de pacotes;
  • NAT: permite redirecionar pacotes e fazer NAT/CGNAT;
  • MANGLE: permite marcações especiais de pacotes e conexões;
  • FILTER: permite a filtragem (DROP e REJECT) de pacotes;

Criando arquivos das estruturas

Bash
# Arquivos de boot das tabelas
echo "create table ip raw"     > /etc/nftables.d/0001-table-ipv4-raw.conf;
echo "create table ip mangle"  > /etc/nftables.d/0002-table-ipv4-mangle.conf;
echo "create table ip filter"  > /etc/nftables.d/0003-table-ipv4-filter.conf;
echo "create table ip nat"     > /etc/nftables.d/0004-table-ipv4-nat.conf;

echo "create table ip6 raw"    > /etc/nftables.d/0011-table-ipv6-raw.conf;
echo "create table ip6 mangle" > /etc/nftables.d/0012-table-ipv6-mangle.conf;
echo "create table ip6 filter" > /etc/nftables.d/0013-table-ipv6-filter.conf;
echo "create table ip6 nat"    > /etc/nftables.d/0014-table-ipv6-nat.conf;


#======================================================== IPv4
#------------ RAW
(
  echo "create chain ip raw PREROUTING {";
  echo "    type filter hook prerouting priority raw;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0021-chain-ipv4-raw-prerouting.conf;

(
  echo "create chain ip raw OUTPUT {";
  echo "    type filter hook output priority raw;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0022-chain-ipv4-raw-output.conf;


#------------ MANGLE
(
  echo "create chain ip mangle PREROUTING {";
  echo "    type filter hook prerouting priority mangle;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0023-chain-ipv4-mangle-prerouting.conf;

(
  echo "create chain ip mangle POSTROUTING {";
  echo "    type filter hook postrouting priority mangle;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0024-chain-ipv4-mangle-postrouting.conf;

(
  echo "create chain ip mangle FORWARD {";
  echo "    type filter hook forward priority mangle;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0025-chain-ipv4-mangle-forward.conf;

(
  echo "create chain ip mangle INPUT {";
  echo "    type filter hook input priority mangle;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0026-chain-ipv4-mangle-input.conf;

(
  echo "create chain ip mangle OUTPUT {";
  echo "    type route hook output priority mangle;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0027-chain-ipv4-mangle-output.conf;

#------------ FILTER
(
  echo "create chain ip filter FORWARD {";
  echo "    type filter hook forward priority filter;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0028-chain-ipv4-filter-forward.conf;

(
  echo "create chain ip filter INPUT {";
  echo "    type filter hook input priority filter;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0029-chain-ipv4-filter-input.conf;

(
  echo "create chain ip filter OUTPUT {";
  echo "    type filter hook output priority filter;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0030-chain-ipv4-filter-output.conf;


#------------ NAT
(
  echo "create chain ip nat PREROUTING {";
  echo "    type nat hook prerouting priority dstnat;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0031-chain-ipv4-nat-prerouting.conf;

(
  echo "create chain ip nat INPUT {";
  echo "    type nat hook input priority 100;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0032-chain-ipv4-nat-input.conf;
	
(
  echo "create chain ip nat OUTPUT {";
  echo "    type nat hook output priority -100;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0033-chain-ipv4-nat-output.conf;

(
  echo "create chain ip nat POSTROUTING {";
  echo "    type nat hook postrouting priority srcnat;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0034-chain-ipv4-nat-postrouting.conf;



#======================================================== IPv6
#------------ RAW
(
  echo "create chain ip6 raw PREROUTING {";
  echo "    type filter hook prerouting priority raw;";
  echo "    policy accept";
  echo "}";
) > /etc/nftables.d/0041-chain-ipv6-raw-prerouting.conf;

(
  echo "create chain ip6 raw OUTPUT {";
  echo "    type filter hook output priority raw;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0042-chain-ipv6-raw-postrouting.conf;

#------------ MANGLE
(
  echo "create chain ip6 mangle PREROUTING {";
  echo "    type filter hook prerouting priority mangle;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0043-chain-ipv6-mangle-prerouting.conf;

(
  echo "create chain ip6 mangle POSTROUTING {";
  echo "    type filter hook postrouting priority mangle;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0044-chain-ipv6-mangle-postrouting.conf;

(
  echo "create chain ip6 mangle FORWARD {";
  echo "    type filter hook forward priority mangle;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0045-chain-ipv6-mangle-forward.conf;

(
  echo "create chain ip6 mangle INPUT {";
  echo "    type filter hook input priority mangle;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0046-chain-ipv6-mangle-input.conf;

(
  echo "create chain ip6 mangle OUTPUT {";
  echo "    type route hook output priority mangle;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0047-chain-ipv6-mangle-output.conf;

#------------ FILTER
(
  echo "create chain ip6 filter FORWARD {";
  echo "    type filter hook forward priority filter;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0048-chain-ipv6-filter-forward.conf;

(
  echo "create chain ip6 filter INPUT {";
  echo "    type filter hook input priority filter;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0049-chain-ipv6-filter-input.conf;

(
  echo "create chain ip6 filter OUTPUT {";
  echo "    type filter hook output priority filter;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0050-chain-ipv6-filter-output.conf;


#------------ NAT
(
  echo "create chain ip6 nat PREROUTING {";
  echo "    type nat hook prerouting priority dstnat;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0051-chain-ipv6-filter-prerouting.conf;
(
  echo "create chain ip6 nat POSTROUTING {";
  echo "    type nat hook postrouting priority srcnat;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0051-chain-ipv6-filter-postrouting.conf;
(
  echo "create chain ip6 nat    OUTPUT {";
  echo "    type nat hook output priority -100;";
  echo "    policy accept;";
  echo "}";
) > /etc/nftables.d/0052-chain-ipv6-filter-output.conf;

Testando se arquivos estruturais estão sintaticamente corretos:

Bash
# Localizar bugs nos arquivos de firewall

# - Entrar na pasta:
cd /etc/nftables.d;

# - Limpar firewall:
nft flush ruleset;

# - Testar arquivo por arquivo:
for i in *; do echo; echo $i; nft -f $i || { echo "Falhou em $i"; break; }; done;

# Reiniciar para aplicar
systemctl restart nftables;

Se encontrou algum erro no script acima, revise o arquivo afetado e arrume-o.

Visualizando estrutura:

Bash
nft list ruleset;

Visualizando configurações na ordem de execução:

Bash
(
    cat /etc/nftables.d/*.conf 2>/dev/null;
    cat /etc/nftables.d/*.nft  2>/dev/null;
);

3 – Regras

Para criar regras, tabelas personalizadas e demais recursos do nftables, faço-o criando arquivos que sejam executados em ordem e após a configuração inicial.

Formato:

  • /etc/nftables.d/NNNN-NOME-DESCRICAO.nft

Onde NNNN deve manter a coerência da ordem, 4 dígitos numéricos.

4 – Exemplos de NAT

Exemplos de regras para fazer redirecionamento de portas, NAT e CGNAT.

4.1 – NAT Masquerade

NAT Masquerade faz NAT usando o IP principal da interface de rede de saída do tráfego.

Bash
# Criar SET (lista) de prefixos privados:
(
  echo "set ip  nat PRIVNETS_IPV4 {";
  echo "    type ipv4_addr;";
  echo "    flags interval;";
  echo "}";
) > /etc/nftables.d/1001-ipv4-nat-set-privnets.nft;
(
  echo "set ip6 nat PRIVNETS_IPV6 {";
  echo "    type ipv6_addr;";
  echo "    flags interval;";
  echo "}";
) > /etc/nftables.d/1002-ipv6-nat-set-privnets.nft;

# - Preencher SET com IPs privados IPv4
(
  echo "add element ip  nat PRIVNETS_IPV4 {";
  echo "    10.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12";
  echo "}";
) > /etc/nftables.d/1011-ipv4-nat-add-privnets.nft;
# - Preencher SET com IPs privados IPv6
(
  echo "add element ip6 nat PRIVNETS_IPV6 {";
  echo "    ::/3, 2001:db8::/32, 4000::/2, 8000::/1";
  echo "}";
) > /etc/nftables.d/1012-ipv6-nat-add-privnets.nft;


# NAT MASQUERADE na interface de saida
# - IPv4
echo 'add rule ip nat POSTROUTING ip saddr @PRIVNETS_IPV4 oifname "eth0" counter masquerade' > /etc/nftables.d/1901-ipv4-nat-masquerade.nft;
# ou SNAT:
#echo 'add rule ip nat POSTROUTING ip saddr @PRIVNETS_IPV4 oifname "eth0" counter snat ip  to 45.255.128.2' > /etc/nftables.d/1901-ipv4-nat-snat.nft;

# - IPv6
echo 'add rule ip6 nat POSTROUTING ip6 saddr @PRIVNETS_IPV6 oifname "eth0" counter masquerade' > /etc/nftables.d/1902-ipv6-nat-masquerade.nft;
# ou SNAT:
#echo 'add rule ip6 nat POSTROUTING ip6 saddr @PRIVNETS_IPV4 oifname "eth0" counter snat ip6 to 2804:cafe::2' > /etc/nftables.d/1902-ipv6-nat-masquerade.nft;

4.2 – Estatísticas de protocolos de rede (FRR)

Bash
    # Protocolos e contadores
    PROTO_LIST="
        bgpv4|ipv4|tcp:179
        bgpv6|ipv6|tcp:179

        ospfv4|ipv4|89
        ospfv6|ipv6|89

        ripv4|ipv4|udp:520
        ripv6|ipv6|udp:521

        isisv4|ipv4|124
        isisv6|ipv6|124

        fabricv4|ipv4|124
        fabricv6|ipv6|124

        pimv4|ipv4|103
        pimv6|ipv6|103

        ldpv4|ipv4|tcp:646
        ldpv6|ipv6|tcp:646

        ldpv4|ipv4|udp:646
        ldpv6|ipv6|udp:646

        eigrp|ipv4|88

        babel|ipv6|udp:6696

        bfdv4|ipv4|udp:3784
        bfdv6|ipv6|udp:3784

        bfdv4|ipv4|udp:4784
        bfdv6|ipv6|udp:4784

        vrrpv4|ipv4|112
        vrrpv6|ipv6|112
    ";

    # Criar tabelas personalizadas
    echo 'add chain ip  mangle counters' > /run/nftables.d/1001-chain-mangle-counters.nft;
    echo 'add chain ip6 mangle counters' > /run/nftables.d/1002-chain-mangle-counters.nft;

    # Criar tabelas de ipv4
    V4CHAINS="";
    V6CHAINS="";
    for reg in $PROTO_LIST; do
        name=$(echo $reg  | cut -f1 -d'|');
        ipversion=$(echo $reg | cut -f2 -d'|');

        # Criar tabela para o servico
        [ "$ipversion" = "ipv4" ] && V4CHAINS="$V4CHAINS ${name}";
        [ "$ipversion" = "ipv6" ] && V6CHAINS="$V6CHAINS ${name}";
    done
    # Sem repeticao
    V4CHAINS=$(for x in $V4CHAINS; do echo $x; done | sort -u);
    V6CHAINS=$(for x in $V6CHAINS; do echo $x; done | sort -u);
    for name in $V4CHAINS; do
        echo "add chain ip  mangle ${name}_input";
        echo "add chain ip  mangle ${name}_output";
        echo "flush chain ip mangle ${name}_input";
        echo "flush chain ip mangle ${name}_output";
    done > /run/nftables.d/1003-chain-mangle-counters-ipv4.nft;
    for name in $V6CHAINS; do
        echo "add chain ip6 mangle ${name}_input";
        echo "add chain ip6 mangle ${name}_output";
        echo "flush chain ip6 mangle ${name}_input";
        echo "flush chain ip6 mangle ${name}_output";
    done > /run/nftables.d/1004-chain-mangle-counters-ipv6.nft;


    # Fazer captura de trafego para as tabelas
    for reg in $PROTO_LIST; do
        name=$(echo $reg  | cut -f1 -d'|');
        ipversion=$(echo $reg | cut -f2 -d'|');
        rule=$(echo $reg  | cut -f3 -d'|');

        # Analisar regra
        rproto=$(echo $rule | cut -f1 -d':');
        rport=$(echo $rule | cut -f2 -d':' -s);

        # Tipo de regra
        if [ "x$rport" = "x" ]; then
            # Apenas protocolo IP fundamental
			# - ipv4
			[ "$ipversion" = "ipv4" ] && \
				echo "add rule ip  mangle INPUT  ip  protocol $rproto counter jump ${name}_input";
			[ "$ipversion" = "ipv4" ] && \
				echo "add rule ip  mangle OUTPUT ip  protocol $rproto counter jump ${name}_output";
			# - ipv6
			[ "$ipversion" = "ipv6" ] && \
				echo "add rule ip6 mangle INPUT  ip6 nexthdr $rproto  counter jump ${name}_input";
			[ "$ipversion" = "ipv6" ] && \
				echo "add rule ip6 mangle OUTPUT ip6 nexthdr $rproto  counter jump ${name}_output";
        else
            # Protocolo parseavel (TCP/UDP)
			# - ipv4
			[ "$ipversion" = "ipv4" ] && \
				echo "add rule ip  mangle INPUT  $rproto dport $rport counter jump ${name}_input";
			[ "$ipversion" = "ipv4" ] && \
				echo "add rule ip  mangle OUTPUT $rproto dport $rport counter jump ${name}_output";
			# - ipv6
			[ "$ipversion" = "ipv6" ] && \
				echo "add rule ip6 mangle INPUT  $rproto dport $rport counter jump ${name}_input";
			[ "$ipversion" = "ipv6" ] && \
				echo "add rule ip6 mangle OUTPUT $rproto dport $rport  counter jump ${name}_output";
        fi;
    done > /run/nftables.d/1005-chain-mangle-counters.nft;



… (adicionando mais no futuro, volte mais vezes) …

Terminamos por hoje!

Patrick Brandão, patrickbrandao@gmail.com