nft config para hacer público un servidor FTP con NAT local disponible

Aug 18 2020

Todo estará en una red aislada, la seguridad no es un problema.
eth0 está conectado a la red "pública". Dirección asignada por DHCP.
eth1 está conectado a un servidor de "red privada" que proporciona ssh, telnet, "otros" y ftp.
Este servidor tendrá una IP fija (192.168.1.2) en este ejemplo.

La puerta de enlace ejecuta debian buster y el kernel de Linux 4.19.94

nft se usa con NAT
Esta es mi configuración nft de "puerta de enlace" hasta ahora:

table ip my_nat {
    chain my_prerouting { type nat hook prerouting priority 0;
    tcp dport { 2222 } dnat to :22 # 2222 backdoor for ssh to the gateway
    tcp dport { 1-1023 } dnat to 192.168.1.2
  }
  chain my_postrouting {
    type nat hook postrouting priority 100;
    ip daddr 192.168.1.2  masquerade
  }
}

¿Qué necesito agregar para que FTP funcione?

Respuestas

1 A.B Aug 20 2020 at 19:17

TL; DR

# nft add ct helper ip my_nat ftp-incoming '{ type "ftp" protocol tcp; }'
# nft add chain ip my_nat my_helpers '{ type filter hook prerouting priority 10; }'
# nft add rule ip my_nat my_helpers iif eth0 ip daddr 192.168.1.2 tcp dport 21 ct helper set ftp-incoming
# modprobe nf_nat_ftp

con más detalles a continuación ...

Módulos auxiliares de protocolo para protocolos problemáticos

FTP es un protocolo antiguo y no es muy compatible con los cortafuegos: los comandos del canal de comandos FTP (21 / TCP) negocian un puerto efímero para usar en el siguiente comando de transferencia. Debido a esto, un firewall con estado tiene que espiar esos comandos y respuestas para prepermitir temporalmente el puerto adecuado que se va a utilizar.

En Linux, esto es proporcionado por módulos auxiliares específicos del protocolo que son complementos para conntrack , el subsistema Netfilter que rastrea las conexiones para NAT y firewall de estado. Con FTP, cuando se ha visto una negociación de puerto (principalmente PORT, EPRT, PASV o EPSV) para la siguiente transferencia en el puerto de comando FTP, el asistente agrega una entrada de corta duración en una tabla especial de conntrack (la tabla de expectativas de conntrack ) que espere la próxima conexión de datos relacionada y esperada.

Mi respuesta usa el manejo "seguro" moderno como se describe en este blog sobre iptables para generalidades y en la wiki de nftables y man nftpara el manejo en nftables que difiere de iptables .

Uso seguro de reglas auxiliares y nftables

El kernel de Linux 4.7+ (por lo que incluye 4.19) utiliza un enfoque seguro de forma predeterminada: tener el módulo auxiliar (aquí FTP) cargado ya no hará que rastree todos los paquetes que tengan un puerto TCP de origen o destino 21, hasta nftables específicos (o iptables ) Las declaraciones le dicen en qué caso (restringido) debe fisgonear. Esto evita el uso innecesario de la CPU y permite cambiar en cualquier momento los puertos FTP para espiar simplemente cambiando algunas reglas (o conjuntos).

La primera parte es declarar los flujos que pueden desencadenar el espionaje. Se maneja de manera diferente en nftables que en iptables . Aquí se declara usando un ct helperobjeto con estado, y los filtros para activarlo deben hacerse después de conntrack (mientras que iptables requiere acciones realizadas antes). man nftdice:

A diferencia de iptables, la asignación de ayuda debe realizarse después de que se haya completado la búsqueda de conntrack, por ejemplo, con la prioridad de enlace 0 predeterminada.

nft add ct helper ip my_nat ftp-incoming '{ type "ftp" protocol tcp; }'

nft add chain ip my_nat my_helpers '{ type filter hook prerouting priority 10; }'
nft add rule ip my_nat my_helpers iif eth0 ip daddr 192.168.1.2 tcp dport 21 ct helper set ftp-incoming

Elegí la misma tabla, pero esto podría haberse creado en otra tabla, siempre que la declaración del objeto con estado y la regla que hace referencia a ella estén en la misma tabla.

Por supuesto, se pueden elegir reglas menos restrictivas. Reemplazar la última regla por la siguiente regla tendría el mismo efecto que el modo heredado:

nft add rule ip my_nat my_helpers tcp dport 21 ct helper set ftp-incoming

Solo como referencia, el modo heredado, que ya no debe usarse, no requiere las reglas anteriores, solo este interruptor (y la carga manual del módulo del kernel relevante):

sysctl -w net.netfilter.nf_conntrack_helper=1

Asegurándose de que nf_nat_ftpesté cargado

El módulo del kernel nf_conntrack_ftpse carga automáticamente con la dependencia creada por ct helper ... type "ftp". Ese no es el caso nf_nat_ftp, pero también es necesario habilitar la manipulación de paquetes en el puerto de comando cuando se realiza NAT en los puertos de flujo de datos.

Por ejemplo, para que se extraiga el módulo nf_nat_ftpcada vez que nf_conntrack_ftpse cargue, el archivo /etc/modprobe.d/local-nat-ftp.confpodría agregarse con este contenido:

install nf_conntrack_ftp /sbin/modprobe --ignore-install nf_conntrack_ftp; /sbin/modprobe --ignore-install nf_nat_ftp

o en su lugar, simplemente agregue, por ejemplo, /etc/modules-load.d/local-nat-ftp.confcon:

nf_nat_ftp

De todos modos, en este momento, este comando debe realizarse para asegurarse de que esté cargado:

modprobe nf_nat_ftp

Acerca del cortafuegos

Aquí hay una nota adicional para el firewall. También puede haber reglas de cortafuegos con algunas restricciones en lugar de permitir cualquier nuevo flujo etiquetado como relacionado por conntrack .

Por ejemplo, aunque el módulo auxiliar FTP maneja los modos pasivo y activo, si por alguna razón uno quiere permitir solo el modo pasivo (con conexión de datos del cliente al servidor) y no el ftp "activo" (conexión de datos desde el puerto de origen del servidor 20 al cliente) se podrían usar, por ejemplo, estas reglas en la parte del firewall del conjunto de reglas, en lugar de lo habitual ct state established,related accept:

ct state established accept
ct state related ct helper "ftp" iif eth0 oif eth1 tcp sport 1024-65535 accept
ct state related ct helper "ftp" drop
ct state related accept 

Se mantienen aceptados otros tipos de flujos relacionados no relacionados con FTP (o podrían dividirse más por separado)


Ejemplo de manipulación por parte del ayudante

Aquí (en un entorno simulado) hay dos listas de eventos de conntrack medidos en la tabla de expectativas y la tabla de conntrack con las reglas de OP + las reglas adicionales anteriores con un cliente de Internet 203.0.113.101 haciendo FTP en modo pasivo con la dirección IP pública 192.0 del enrutador. 2.2 y usando un comando LIST después de haber iniciado sesión:

# conntrack -E expect
    [NEW] 300 proto=6 src=203.0.113.101 dst=192.0.2.2 sport=0 dport=37157 mask-src=0.0.0.0 mask-dst=0.0.0.0 sport=0 dport=65535 master-src=203.0.113.101 master-dst=192.0.2.2 sport=50774 dport=21 class=0 helper=ftp
[DESTROY] 300 proto=6 src=203.0.113.101 dst=192.0.2.2 sport=0 dport=37157 mask-src=0.0.0.0 mask-dst=0.0.0.0 sport=0 dport=65535 master-src=203.0.113.101 master-dst=192.0.2.2 sport=50774 dport=21 class=0 helper=ftp

simultaneamente:

# conntrack -E
    [NEW] tcp      6 120 SYN_SENT src=203.0.113.101 dst=192.0.2.2 sport=50774 dport=21 [UNREPLIED] src=192.168.1.2 dst=192.168.1.1 sport=21 dport=50774 helper=ftp
 [UPDATE] tcp      6 60 SYN_RECV src=203.0.113.101 dst=192.0.2.2 sport=50774 dport=21 src=192.168.1.2 dst=192.168.1.1 sport=21 dport=50774 helper=ftp
 [UPDATE] tcp      6 432000 ESTABLISHED src=203.0.113.101 dst=192.0.2.2 sport=50774 dport=21 src=192.168.1.2 dst=192.168.1.1 sport=21 dport=50774 [ASSURED] helper=ftp
    [NEW] tcp      6 120 SYN_SENT src=203.0.113.101 dst=192.0.2.2 sport=55835 dport=37157 [UNREPLIED] src=192.168.1.2 dst=192.168.1.1 sport=37157 dport=55835
 [UPDATE] tcp      6 60 SYN_RECV src=203.0.113.101 dst=192.0.2.2 sport=55835 dport=37157 src=192.168.1.2 dst=192.168.1.1 sport=37157 dport=55835
 [UPDATE] tcp      6 432000 ESTABLISHED src=203.0.113.101 dst=192.0.2.2 sport=55835 dport=37157 src=192.168.1.2 dst=192.168.1.1 sport=37157 dport=55835 [ASSURED]
 [UPDATE] tcp      6 120 FIN_WAIT src=203.0.113.101 dst=192.0.2.2 sport=55835 dport=37157 src=192.168.1.2 dst=192.168.1.1 sport=37157 dport=55835 [ASSURED]
 [UPDATE] tcp      6 30 LAST_ACK src=203.0.113.101 dst=192.0.2.2 sport=55835 dport=37157 src=192.168.1.2 dst=192.168.1.1 sport=37157 dport=55835 [ASSURED]
 [UPDATE] tcp      6 120 TIME_WAIT src=203.0.113.101 dst=192.0.2.2 sport=55835 dport=37157 src=192.168.1.2 dst=192.168.1.1 sport=37157 dport=55835 [ASSURED]

El inicio de la expectativa proto=6 src=203.0.113.101 dst=192.0.2.2 sport=0 dport=37157dice que la próxima conexión TCP de 203.0.113.101:* a 192.0.2.2:37157 estará asociada ( relacionada con el estado ) con la conexión FTP. No es visible directamente, pero dado que el módulo auxiliar NAT FTP también está cargado, los datos reales enviados por el servidor en respuesta al comando PASV / EPSV fueron interceptados y traducidos para que el cliente se conecte a 192.0.2.2 en lugar de 192.168.1.2, lo que habría por supuesto falló en el cliente.

A pesar de que el segundo flujo (segunda línea NUEVA ) no tiene una regla explícita en my_prerouting , se envió con éxito DNAT al servidor detrás del enrutador.


Notas

  • si el puerto de control FTP está encriptado ( AUTH TLS...), entonces el módulo auxiliar ya no puede rastrear el puerto negociado y esto no puede funcionar. Hay que recurrir a configurar un rango reservado de puertos tanto en la configuración del servidor FTP como en el firewall / enrutador NAT, y configurar el servidor para que envíe la dirección IP (pública) correcta al negociar en lugar de la suya propia. Y el modo FTP activo no se puede utilizar si el servidor no tiene una ruta a Internet (como parece ser el caso aquí).

  • nitpicking: la prioridad de prerouting de 10 asegura que incluso para kernels <4.18, NAT ya sucedió para nuevos flujos (OP eligió la prioridad de prerouting de hook 0 en lugar del habitual -100 ya que esto rara vez importa en nftables), por lo que el uso de daddr 192.168.1.2es posible. Si la prioridad fuera 0 (o menor que 0), tal vez sea posible (no verificado) que la regla vea el primer paquete aún sin NAT con una dirección IP pública de destino, pero capturaría los siguientes paquetes del mismo flujo, ya que son manejados directamente por conntrack con prioridad -200. Es mejor mantenerse seguro y usar 10. En realidad, esto no es relevante desde el kernel 4.18 (ver la referencia de confirmación en este parche pendiente) donde la prioridad de NAT solo es relevante para comparar entre múltiples cadenas nat (y permite mezclar NAT en iptables heredado con nftables).

GunnarHolm Sep 04 2020 at 11:56

Después de algunas pruebas y errores, se me ocurrió el siguiente nftables.conf. Esto funciona según lo previsto y es compatible con NAT en ambas direcciones, además de exportar todos los puertos de mi servidor a la red "pública" menos uno. El puerto 2222 todavía se usa como una "puerta trasera" para la puerta de enlace, que se usará si alguna vez necesito acceder a él nuevamente :-)

table ip my_nat {
        ct helper ftp-incoming {
                type "ftp" protocol tcp
                l3proto ip
        }

        chain my_prerouting {
                type nat hook prerouting priority 0; policy accept;
                iifname "eth0" tcp dport { 2222 } dnat to :ssh
                iifname "eth0" tcp dport { 1-2221, 2223-65535 } dnat to 192.168.0.2
        }

        chain my_postrouting {
                type nat hook postrouting priority 100; policy accept;
                ip daddr 192.168.0.2 masquerade
                oifname "eth0" masquerade
        }

        chain my_helpers {
                type filter hook prerouting priority 10; policy accept;
                iif "eth0" ip daddr 192.168.0.2 tcp dport ftp ct helper set "ftp-incoming"
        }

}