Apuntes · Capítulo 15Redes modernas

Seguridad de redes: amenazas, firewalls y VPNs

Seguridad de redes: amenazas, firewalls y VPNs

Durante catorce semanas armamos una red que funciona. Capturamos paquetes, configuramos switches y VLANs, conectamos sistemas autónomos con BGP, escribimos un transporte confiable y publicamos un servicio web sobre él. En casi todo ese camino dimos por sentado algo que ahora toca mirar de frente: que nadie en la red miente, nadie escucha lo que no le toca y nadie estorba a propósito. Ninguna de esas tres cosas es cierta en Internet.

Esta semana no agrega una capa nueva. Recorre las que ya conocemos y pregunta, en cada una, qué puede hacer alguien que quiere hacer daño, y qué defensa existe. ARP no autentica, y ya lo sabíamos desde la semana 3. IP deja falsificar la dirección de origen, y de ahí salen los ataques de amplificación de la semana 12 y los SYN flood de la semana 10. BGP cree lo que le dicen, como vimos en la semana 7. DNS responde sin firma, TLS protege el contenido pero no el nombre del servidor. La seguridad de redes no es un tema aparte: es releer todo el curso con otra pregunta.

Después de ese repaso, construimos dos defensas con las manos. En la Sesión A y en el laboratorio levantamos un firewall con estado con nftables, que separa una red interna, una DMZ y el mundo de afuera, y deja pasar solo lo que una política permite. Y levantamos una VPN: primero IPsec a mano, para ver sus piezas, y después WireGuard, un diseño más simple que casi todas las VPN nuevas usan. Capturamos el tráfico a cada lado del túnel para ver qué protege y qué no, porque esa diferencia es la fuente del error más común sobre las VPN.

La Sesión B es la presentación de los proyectos: cada equipo muestra wasi respondiendo sobre rimay en Qhapaq Ñan. Al final del capítulo hay un repaso guiado para el examen final.

Ruta de lectura.

Antes de clase (obligatorio, unos 15 a 20 minutos). Sesión A: La red de esta semana, Modelo de amenazas: quién, qué ve y qué puede cambiar, Capa de enlace: ARP spoofing, la introducción de Firewalls: sin estado y con estado con Zonas y DMZ, la de WireGuard: una VPN con menos piezas y Tres formas de proteger un camino. Sesión B: La Sesión B: presentación de los proyectos y el enunciado de P5.

En clase. Sesión A: Capa de red: IP spoofing y BCP 38, Capa de transporte: SYN flood y DoS, El firewall de la semana: el esqueleto y una regla, Antes y después: probarlo con nc y curl, Ver el estado por dentro: conntrack y los contadores, IPsec: cifrado en la capa de red, Levantar el túnel, El handshake y los timers, Qué protege y qué no: la captura a los dos lados y Ejemplo resuelto: seguir un paquete por el firewall y el túnel. Sesión B: la presentación de los proyectos.

Después de clase. Repasen con el Repaso guiado para el examen final, el Resumen y los Ejercicios, y preparen El laboratorio de la semana. Opcional: Zero trust, Sistemas de detección de intrusos, Identidad por clave: un origen que no corresponde y El costo de la VPN: MTU y throughput. Para profundizar, las lecturas del libro y las gratuitas de la semana:

Este capítulo acompaña a Kurose y Ross, Computer Networking: A Top-Down Approach (9.ª ed.). Para la Sesión A, las secciones 1.6 Networks Under Attack, 8.1 What Is Network Security?, 8.7 Network-Layer Security: IPsec and Virtual Private Networks y 8.9 Operational Security: Firewalls and Intrusion Detection Systems. Y las secciones 1 y 2 del artículo WireGuard: Next Generation Kernel Network Tunnel, de Jason A. Donenfeld. Complementarias: la sección 8.8 Securing Wireless LANs and 4G/5G Cellular Networks, y el RFC 4301, que define la arquitectura de IPsec. El código de la semana está en code/week-15/.

La red de esta semana

Dos sitios de una misma organización, separados por una red que no es de ellos. En el sitio A hay una red interna (las oficinas), una DMZ con un servidor web y un router fw que separa las dos de afuera. En el sitio B hay otra red interna, detrás de gw2. Entre los dos está wan, que hace de “Internet”, y ext, una máquina de afuera que nos sirve de atacante y de cliente externo. La topología está en code/week-15/sitios/sitios.clab.yml.

Red de dos sitios. A la izquierda, la red interna 10.215.1.0/24 con ofi 10.215.1.10, y la DMZ 100.64.15.0/26 con web 100.64.15.10, ambas conectadas a fw por eth1 y eth2. Al centro, la zona de afuera: fw se conecta por eth3, 100.64.15.66, con la eth1 de wan, 100.64.15.65, en el enlace 100.64.15.64/30. wan se conecta por eth3, 100.64.15.97, con ext 100.64.15.100, y por eth2, 100.64.15.69, con la eth1 de gw2, 100.64.15.70, en el enlace 100.64.15.68/30. A la derecha, el sitio B, sin ruta pública: gw2 y, detrás de su eth2 .1, b 10.215.3.10 en 10.215.3.0/24Red de dos sitios. A la izquierda, la red interna 10.215.1.0/24 con ofi 10.215.1.10, y la DMZ 100.64.15.0/26 con web 100.64.15.10, ambas conectadas a fw por eth1 y eth2. Al centro, la zona de afuera: fw se conecta por eth3, 100.64.15.66, con la eth1 de wan, 100.64.15.65, en el enlace 100.64.15.64/30. wan se conecta por eth3, 100.64.15.97, con ext 100.64.15.100, y por eth2, 100.64.15.69, con la eth1 de gw2, 100.64.15.70, en el enlace 100.64.15.68/30. A la derecha, el sitio B, sin ruta pública: gw2 y, detrás de su eth2 .1, b 10.215.3.10 en 10.215.3.0/24
La red de la semana. fw separa tres zonas, wan hace de Internet y ext es la máquina de afuera.

fw separa tres zonas: la red interna en eth1 (10.215.1.0/24, direcciones privadas), la DMZ en eth2 (100.64.15.0/26, con dirección “pública” del mundo de Qhapaq Ñan) y afuera en eth3. A propósito no hay NAT en esta red: queremos que las capturas muestren las direcciones reales, para ver qué pasa de verdad. Y wan sí sabe llegar a la red interna de fw, para que lo que la proteja sea el firewall y no la falta de una ruta. La red de b no tiene ruta pública: solo se alcanza por el túnel que armamos más adelante.

Como en toda la parte práctica del curso, cada dirección, cada número de puerto y cada tiempo que aparece salió de una corrida real en esta topología. Los puertos de origen y los tiempos cambian en cada ejecución; las velocidades dependen de la computadora, y lo decimos cuando aparece una. El comportamiento no cambia. Levanten la red y tengan las claves listas:

cd code/week-15
make up

Modelo de amenazas: quién, qué ve y qué puede cambiar

Antes de defender algo hay que decir contra quién. En seguridad eso se llama modelo de amenazas: una descripción honesta de qué puede hacer el atacante. Sin esa descripción, “seguro” no quiere decir nada.

Definición. Un modelo de amenazas responde tres preguntas: quién es el adversario, qué puede observar de la red y qué puede modificar. Una defensa se evalúa siempre contra un modelo; la misma medida sirve contra un adversario y es inútil contra otro.

Las posiciones importan más que las personas. Conviene distinguir dos:

  • Fuera del camino (off-path). El atacante puede enviar paquetes, y falsificar la dirección de origen, pero no ve el tráfico entre la víctima y su interlocutor. Tiene que adivinar lo que no ve: un número de secuencia, un ID de DNS, un puerto de origen. Ya lo medimos: en la semana 12, un envenenamiento de cache off-path tenía que acertar el ID de 16 bits y el puerto de origen, y por eso el puerto al azar lo volvía casi imposible.
  • En el camino (on-path). El atacante ve el tráfico. Un Wi-Fi abierto, un router comprometido en el camino, el administrador de una red. No adivina nada: lee los números y responde primero. Contra este no sirven los trucos de la semana 12; hace falta criptografía.

Un tercer eje es qué puede hacer con lo que ve: un atacante pasivo solo escucha (sniffing); uno activo modifica, inyecta o bloquea paquetes. Un man-in-the-middle es un atacante activo y on-path a la vez: se pone en el medio, habla con cada lado haciéndose pasar por el otro.

Idea clave. No existe “seguro” a secas. Una medida protege contra un adversario con ciertas capacidades. El puerto al azar de DNS detiene a quien no ve el tráfico y no hace nada contra quien sí lo ve. TLS detiene a quien está en el camino pero no oculta con quién hablamos. La primera pregunta siempre es: ¿contra quién?

Conviene tener a la vista las metas clásicas de la seguridad, que ya nombramos con TLS en la semana 13 y que aquí usamos para clasificar ataques y defensas:

  • Confidencialidad: solo el destinatario entiende el mensaje. Se rompe con sniffing.
  • Integridad y autenticación: el mensaje llega sin cambios y de quien dice venir. Se rompe con spoofing y con man-in-the-middle.
  • Disponibilidad: el servicio responde. Se rompe con un DoS.

Ataques por capa: un repaso del curso con otra pregunta

Esta sección recorre las capas que ya estudiamos. En cada una el ataque sale de una propiedad del protocolo que vimos funcionar, y la defensa también. No repetimos aquí los laboratorios de esas semanas; los nombramos para que el repaso sea un repaso.

Capa de enlace: ARP spoofing

En la semana 3 vimos que ARP no tiene autenticación: cualquier nodo de la red local puede responder “esa IP soy yo”. Un atacante en la misma red que la víctima responde a las consultas ARP del gateway con su propia MAC, y al revés, y queda en el medio de todo el tráfico local: un man-in-the-middle sin salir de la capa 2. Es un atacante on-path que se hace on-path.

La defensa es estructural, y ya la nombramos: separar dominios de broadcast con VLANs, de modo que el atacante solo pueda envenenar a los de su propio segmento, y en redes administradas, funciones del switch como DHCP snooping y Dynamic ARP Inspection, que descartan respuestas ARP que no concuerdan con lo que el switch vio por DHCP. Es una de las razones para separar redes con VLANs, como vimos en la semana 3.

Capa de red: IP spoofing y BCP 38

IP no verifica la dirección de origen de un datagrama. Quien controla una máquina puede escribir en el campo de origen la dirección que quiera. De ahí salen dos familias de ataques que ya vimos:

  • El envenenamiento de cache de DNS off-path de la semana 12 dependía de que el atacante pudiera enviar respuestas con la dirección de origen del servidor autoritativo.
  • Los ataques de amplificación de la semana 12: una pregunta chica con la dirección de origen de la víctima, y un servidor le manda a ella una respuesta mucho más grande. Lo medimos con la respuesta de .pe: 43 bytes de pregunta, 384 de respuesta. Por UDP, sin handshake, nada le impide al atacante mentir sobre quién pregunta.

La defensa principal no la aplica la víctima, sino las redes de origen. El BCP 38 (RFC 2827) pide que cada red descarte en su borde los paquetes cuya dirección de origen no le pertenece. Si todas lo hicieran, un atacante no podría falsificar direcciones fuera de su propio bloque, y la amplificación con reflejo dejaría de funcionar. En Linux esto se activa, para un router, con el reverse path filter: el kernel descarta un paquete si la ruta de vuelta hacia su dirección de origen no sale por la interfaz por la que llegó. El sysctl es net.ipv4.conf.<if>.rp_filter. En nuestros nodos viene en 0:

$ docker exec clab-w15-sitios-fw sysctl net.ipv4.conf.all.rp_filter
net.ipv4.conf.all.rp_filter = 0

Un operador que aplica BCP 38 lo pone en 1 (modo estricto) en las interfaces hacia sus clientes, y entonces un cliente ya no puede mandar paquetes mintiendo sobre su dirección. (Hay un modo laxo, 2, que solo descarta orígenes sin ninguna ruta de vuelta; sirve cuando el routing es asimétrico, pero no es el filtro de borde que pide el BCP 38 para un cliente.)

Lo que hace peligrosa a la amplificación es el factor de amplificación: cuántas veces más grande es la respuesta que la pregunta. Con la respuesta de .pe, el factor es 384 / 43 ≈ 9: por cada byte que el atacante gasta, la víctima recibe nueve. Un atacante con un enlace modesto, y con muchos servidores reflectores, multiplica su tráfico por ese factor contra una víctima que nunca pidió nada. Hay protocolos peores que DNS para esto —algunos servicios mal configurados daban factores de decenas o centenas—, y la cuenta es siempre la misma:

tráfico contra la víctima ≈ tráfico del atacante × factor de amplificación × número de reflectores

Por eso la defensa tiene dos frentes: el BCP 38 impide falsificar el origen (y sin origen falso no hay reflejo), y los servidores que podrían servir de reflectores limitan a quién responden y a qué tasa. Ninguno basta solo; los dos juntos hacen que el ataque deje de rendir.

Idea clave. La falsificación de origen se arregla cerca del atacante, no cerca de la víctima. Ningún servicio puede distinguir, por sí solo, un paquete con origen falso de uno legítimo. Por eso el BCP 38 es una responsabilidad de cada red con el resto de Internet, igual que los filtros de prefijos de BGP de la semana 7: protegen a los demás, no a quien los aplica.

Capa de transporte: SYN flood y DoS

En la semana 10 montamos un SYN flood y vimos las SYN cookies defenderlo: el servidor deja de guardar estado por cada SYN a medio completar y codifica lo necesario en el número de secuencia. Recordamos el límite que señalamos entonces: las SYN cookies protegen la memoria del servidor, no el ancho de banda de su enlace. Un DoS por volumen —y más aún un DDoS, desde miles de máquinas a la vez— satura el enlace antes de llegar al servidor, y ahí la única defensa es filtrar antes, en la red del proveedor o en un servicio de mitigación. Eso es lo que prometimos “ver en la parte de seguridad”, y es parte de por qué los firewalls y los sistemas de detección viven en el borde de la red y no solo en el host.

Hay una tensión que conviene nombrar. Un firewall con estado, que ya guarda una entrada por conexión, es él mismo un objetivo de DoS: un atacante que abre millones de conexiones a medias puede llenar su tabla de conntrack, igual que llenaba la cola de SYN del servidor. Por eso en el borde se combinan varias ideas que ya tienen nombre en el curso: límites de tasa (limit en nftables), SYN cookies en los servidores, y descartar lo antes posible —cuanto más cerca del atacante muere un paquete, menos recursos gasta en el camino—. No hay una sola defensa contra el DoS: hay una cadena de ellas, y la más barata es la que actúa primero.

Capa de aplicación y de routing: lo que ya vimos

  • Envenenamiento de DNS (semana 12): el ID y el puerto al azar frenan al atacante off-path; DNSSEC autentica las respuestas con firmas; DoT y DoH cifran el canal hasta el resolver.
  • Hijack de BGP (semana 7): un AS origina o propaga un prefijo que no le toca. Las defensas son los filtros de prefijos del RFC 7454 y RPKI, que valida quién puede originar cada prefijo.

Error común. Pensar que la seguridad es una caja que se pone al final. Cada uno de estos ataques existe por una decisión de diseño de un protocolo —no autenticar, no cifrar, confiar en el vecino— que en su momento fue razonable. La defensa casi nunca cambia el protocolo: lo envuelve (TLS sobre TCP), lo valida desde afuera (RPKI, DNSSEC) o lo filtra (firewalls, BCP 38).

Firewalls: sin estado y con estado

Definición. Un firewall es un punto de la red donde el tráfico se inspecciona y se acepta o se descarta según una política. Un firewall sin estado (stateless) decide cada paquete por separado, mirando solo sus campos: direcciones, puertos, protocolo. Un firewall con estado (stateful) recuerda las conexiones que ya vio, y decide un paquete según la conexión a la que pertenece.

La diferencia importa mucho. Piensen en la regla que queremos casi siempre: “los de adentro pueden iniciar conexiones hacia afuera, pero los de afuera no pueden iniciar conexiones hacia adentro”. Con un firewall sin estado es incómodo: para dejar volver las respuestas de una conexión que salió, habría que aceptar todo el tráfico entrante que parezca una respuesta (por ejemplo, todo TCP con puerto de origen 80), y eso también deja entrar a un atacante que ponga 80 como puerto de origen. Un firewall con estado no tiene ese problema: deja volver un paquete solo si pertenece a una conexión que él vio salir.

En Linux, el que lleva la cuenta de las conexiones es conntrack, y nftables lo consulta con ct state. Los estados que usamos:

  • new: el primer paquete de una conexión que conntrack no conocía.
  • established: un paquete de una conexión que ya está en curso, en cualquiera de los dos sentidos.
  • related: una conexión nueva pero ligada a una existente (por ejemplo, un error ICMP sobre un flujo conocido).
  • invalid: un paquete que conntrack no puede ubicar en ninguna conexión. Casi siempre es basura o un intento de evadir el firewall, y se descarta.

Error común. Escribir ct state != established,related drop como primera regla, pensando que descarta “lo que no es una respuesta”. Descarta también los paquetes new legítimos que sí queremos aceptar más abajo, y a veces los related. La forma correcta es al revés: primero se acepta lo establecido, después se decide qué conexiones nuevas se permiten. La regla que acepta va antes que las que filtran.

Zonas y DMZ

La política se escribe en términos de zonas, no de máquinas sueltas. Nuestra red tiene tres, y cada una es una interfaz de fw:

  • La red interna (eth1): se confía en ella para iniciar conexiones hacia afuera, pero no se la expone.
  • La DMZ (demilitarized zone, eth2): donde viven los servicios que sí deben ser accesibles desde afuera, como el servidor web. Está separada de la red interna a propósito: si alguien compromete el servidor público, queda en la DMZ y todavía tiene al firewall entre él y las oficinas.
  • Afuera (eth3): nadie de confianza.

La política que queremos:

De \ Hacia Red interna DMZ Afuera
Red interna — sí sí
DMZ solo respuestas — sí
Afuera no solo al web, puertos 80/443 —

El firewall de la semana: el esqueleto y una regla

El firewall completo es el laboratorio de la semana: ustedes lo escriben en lab/firewall.nft. Aquí vemos su estructura y una de sus reglas con detalle; el resto sale de la tabla de la política de arriba. Es para nftables, el sucesor de iptables, con una sola tabla inet que cubre IPv4 e IPv6. El esqueleto de la cadena de reenvío es siempre el mismo:

chain forward {
    type filter hook forward priority filter; policy drop;
    ct state established,related accept
    ct state invalid drop
    ...  # aquí van las reglas que abren cada camino de la tabla de la política
}

policy drop es la decisión por defecto: lo que no calce con ninguna regla se descarta. Es la postura correcta de un firewall: denegar por defecto, permitir lo necesario. Lo que olviden queda cerrado, no abierto. El orden sí importa: nftables aplica la primera regla que da un veredicto, y un drop o un reject puesto antes de un accept lo anula. Las dos primeras reglas son las del párrafo anterior: aceptar lo establecido, descartar lo inválido. Van primero a propósito, para que una respuesta no tenga que calzar con una regla de zona.

De las reglas que abren cada camino de la tabla, la más delicada es la única puerta de afuera hacia adentro. No basta con “de eth3 a eth2”: hay que atar la regla a la dirección del servidor y a sus puertos, porque es la superficie que queda expuesta a Internet:

    iifname "eth3" oifname "eth2" ip daddr 100.64.15.10 tcp dport { 80, 443 } accept

Solo a la dirección del servidor web de la DMZ, solo en los puertos 80 y 443, solo desde afuera hacia la DMZ. Las demás reglas —dejar salir a la red interna y a la DMZ, y dejar que la red interna consulte la DMZ— siguen el mismo patrón, cada una con su iifname/oifname; y conviene rechazar explícitamente (con reject) lo que venga de afuera hacia la red interna, en vez de dejar que policy drop lo descarte en silencio: la diferencia la vemos abajo, y es la misma del escenario ports de la semana 10.

Hay una segunda cadena que no hay que olvidar. input protege al propio router, que es distinto del tráfico que lo atraviesa: empieza igual (denegar por defecto, aceptar lo establecido, descartar lo inválido), deja administrar el router desde la red interna (ping y SSH), y deja entrar por eth3 solo el UDP del túnel WireGuard que armamos más adelante (udp dport 51820). Todo lo demás que vaya al router, se descarta.

Error común. Proteger solo forward y olvidar input. Un firewall que filtra el tráfico que atraviesa pero deja abierto todo servicio del propio router (su SSH, su DNS, su panel de administración) tiene una puerta grande en el costado. Las dos cadenas son hooks distintos del mismo flujo: forward es lo que pasa a través, input es lo que termina en la máquina.

Sigamos un cliente legítimo de afuera que pide la página del servidor web, para ver qué regla lo deja pasar. Su primer paquete es un SYN de ext hacia 100.64.15.10:80. En fw, entra por eth3 y sale hacia eth2, así que lo evalúa la cadena forward: no es established todavía, no es invalid, no calza con las tres primeras reglas de salida, y sí calza con iifname "eth3" oifname "eth2" ip daddr 100.64.15.10 tcp dport { 80, 443 } accept. Pasa, y conntrack anota la conexión. El SYN-ACK que vuelve del servidor, y todo el resto de la conexión en los dos sentidos, calzan con ct state established,related accept, la primera regla: no hace falta una regla de vuelta. Esa es la economía del firewall con estado: una regla abre la conexión, y el estado se encarga del resto.

En la práctica. Esta misma política, con otros nombres, es la de casi toda la infraestructura moderna. Los security groups de una nube, las reglas de un load balancer o las network policies de Kubernetes son firewalls con estado escritos como “esta zona puede iniciar hacia esta otra, en estos puertos”. Cambia la sintaxis, no las ideas: denegar por defecto, permitir por zona y por puerto, y dejar que el estado siga las conexiones. Quien entiende este firewall.nft entiende aquellas.

Antes y después: probarlo con nc y curl

Levantamos un servidor web en la DMZ y otro en la oficina, y antes de cargar el firewall fw solo hace forwarding: afuera llega a todo.

$ python3 scenarios.py firewall
Antes del firewall (fw solo hace forwarding): afuera llega a todo.
    ext -> web (DMZ): 200
    ext -> ofi (red interna): 200

Cargamos el firewall con nft -f, editando el archivo, y repetimos:

Cargamos el firewall:
  fw# nft -f firewall.nft
Después del firewall:
    ext -> web (DMZ)   : 200
    ext -> ofi (interna): falla en 0.00 s
    ofi -> web (interna a DMZ): 200

La DMZ sigue accesible desde afuera; la red interna ya no; y desde adentro se sigue saliendo. Eso es exactamente la tabla de la política. Ahora la diferencia entre descartar y rechazar, midiendo cuánto tarda cada intento:

Tres resultados distintos, medidos con el tiempo de cada intento:
    ext -> web:443 (aceptado por fw, sin servidor): no conecta, 0.04 s
    ext -> web:22  (sin regla, drop en forward) : no conecta, 3.04 s
    ext -> ofi:8080 (red interna, reject)       : no conecta, 1.06 s

Una captura en ext dice qué pasó en cada caso (recortamos las opciones de TCP):

100.64.15.100.42203 > 100.64.15.10.443: Flags [S], seq 1033595322, ...
100.64.15.10.443 > 100.64.15.100.42203: Flags [R.], seq 0, ack 1033595323, win 0, length 0
100.64.15.100.41917 > 100.64.15.10.22: Flags [S], seq 174046449, ...
100.64.15.100.41917 > 100.64.15.10.22: Flags [S], seq 174046449, ...
100.64.15.100.41917 > 100.64.15.10.22: Flags [S], seq 174046449, ...
100.64.15.100.33287 > 10.215.1.10.8080: Flags [S], seq 1200128341, ...
100.64.15.66 > 100.64.15.100: ICMP host 10.215.1.10 unreachable - admin prohibited filter, length 68
100.64.15.100.33287 > 10.215.1.10.8080: Flags [S], seq 1200128341, ...
100.64.15.66 > 100.64.15.100: ICMP host 10.215.1.10 unreachable - admin prohibited filter, length 68

El puerto 443 lo acepta el firewall, pero no hay servidor escuchando, y el propio web contesta con un RST: falla al instante. El puerto 22 sí tiene un servidor, pero no tiene regla: cae en policy drop, el cliente no recibe nada, retransmite el SYN y nc se rinde a los tres segundos. Hacia la red interna, fw contesta con un ICMP “admin prohibited”, enviado desde su propia dirección. Llega enseguida, y curl falla en 0.00 s. nc tarda un segundo porque su conexión retransmitió el SYN una vez y recibió un segundo ICMP: el tiempo mide también al cliente, no solo al firewall. Es la distinción de la semana 10 entre un puerto cerrado (RST inmediato) y uno filtrado (silencio): un firewall que descarta se ve, desde afuera, como un puerto filtrado.

Animación de ext, fw, web y ofi, con la cadena forward de fw debajo, donde se resalta la regla que calza. Primero, un SYN de ext al puerto 80 de web calza con la regla que abre 80 y 443, conntrack lo anota y el SYN-ACK vuelve por la regla de lo establecido. Segundo, un SYN al puerto 443 pasa por la misma regla, y web contesta con un RST: 0,04 s. Tercero, tres SYN al puerto 22 llegan a fw y caen en policy drop, y el contador sube hasta 3 paquetes y 180 bytes: 3,04 s. Cuarto, dos SYN al puerto 8080 de ofi calzan con el rechazo de eth3 a eth1, y fw contesta a cada uno con un ICMP desde 100.64.15.66: 1,06 sAnimación de ext, fw, web y ofi, con la cadena forward de fw debajo, donde se resalta la regla que calza. Primero, un SYN de ext al puerto 80 de web calza con la regla que abre 80 y 443, conntrack lo anota y el SYN-ACK vuelve por la regla de lo establecido. Segundo, un SYN al puerto 443 pasa por la misma regla, y web contesta con un RST: 0,04 s. Tercero, tres SYN al puerto 22 llegan a fw y caen en policy drop, y el contador sube hasta 3 paquetes y 180 bytes: 3,04 s. Cuarto, dos SYN al puerto 8080 de ofi calzan con el rechazo de eth3 a eth1, y fw contesta a cada uno con un ICMP desde 100.64.15.66: 1,06 s
El cliente legítimo y los tres intentos anteriores, regla por regla. La misma red da tres respuestas distintas, y desde afuera solo se distinguen por lo que vuelve y cuánto tarda.

En la práctica. Que un firewall descarte en silencio o rechace con un ICMP es una decisión real. El silencio da menos información a quien escanea desde afuera (no sabe si hay algo ahí), al costo de que un cliente legítimo que se equivoca de puerto espera el timeout completo. El rechazo con ICMP hace lo contrario: falla rápido, pero confirma que el firewall está ahí. Nuestro firewall mezcla las dos: descarta en silencio los puertos sin regla de la DMZ, y rechaza con un ICMP los intentos de afuera hacia la red interna. No hay una respuesta única correcta; depende de a quién se le quiere dar, o negar, información.

Ver el estado por dentro: conntrack y los contadores

El firewall no es una caja opaca: se puede mirar lo que hace. Dos herramientas ayudan a depurar una política, y a convencerse de que hace lo que uno cree.

La primera es conntrack, la tabla de conexiones que nftables consulta con ct state. Cada flujo que fw ve aparece como una entrada con sus dos sentidos, su estado y un temporizador que se reinicia con cada paquete. Una conexión TCP establecida se ve con su estado y el tiempo que le queda antes de expirar; una respuesta que vuelve por esa conexión calza como established y no necesita ninguna regla propia. Cuando una política se comporta raro, conntrack -L suele mostrar por qué: un flujo marcado [UNREPLIED] que nunca recibió respuesta, o uno que no aparece porque el primer paquete se descartó.

La segunda son los contadores. Cada regla puede llevar un counter, y la regla final de policy drop es un buen lugar para uno: cuenta exactamente lo que ninguna política dejó pasar. Después de que, desde afuera, alguien intenta conectarse al puerto 22 de la DMZ —que no tiene regla—, el contador del forward lo registra:

$ docker exec clab-w15-sitios-fw sh -c 'nft list chain inet fw forward | grep counter'
counter packets 3 bytes 180 comment "forward que no calza con ninguna politica"

Tres paquetes de 60 bytes: son los tres SYN de la captura anterior, el original y dos retransmisiones, hasta que nc -w3 se rindió. El contador es la prueba de que esos paquetes llegaron a fw y murieron ahí. En una red real, un contador que crece sin parar en la regla de descarte es la primera señal de que algo, como un escaneo, está golpeando el firewall.

En la práctica. La diferencia entre “mi regla funciona” y “creo que mi regla funciona” son los contadores. Antes de dar por buena una política, generen el tráfico que debería bloquear y miren si el contador sube; y el que debería pasar, y miren si no sube. Una regla que nunca se dispara suele estar mal escrita, o puesta después de otra que ya atrapó el tráfico.

Zero trust

El diseño de zonas tiene un supuesto cómodo: que “adentro” es confiable. Durante años las redes se construyeron así, con un perímetro duro y un interior blando. El problema es que basta un equipo comprometido adentro —una laptop con malware, un servidor con una falla— para que el atacante esté dentro del perímetro, donde ya nadie lo revisa.

Definición. Zero trust es el principio de no confiar en alguien solo por estar dentro de la red. Cada acceso se autentica y se autoriza por su cuenta, sin importar desde dónde venga. No es un producto: es dejar de usar la posición en la red como si fuera una credencial.

En la práctica esto empuja la autenticación hacia cada servicio y cada conexión: TLS mutuo entre servicios internos, identidad por dispositivo y por usuario, y micro-segmentación en vez de una sola gran red interna. WireGuard, que vemos enseguida, encaja bien con esta idea: identifica a cada peer por su clave, no por su dirección.

Sistemas de detección de intrusos

Un firewall decide por una política fija. Un sistema de detección de intrusos (IDS) mira el tráfico buscando patrones de ataque conocidos o comportamiento anómalo, y avisa; si además bloquea, se llama IPS (sistema de prevención). Un IDS por firmas (como Snort o Suricata) reconoce ataques conocidos; uno por anomalía aprende lo normal y marca lo que se sale. El costo son los falsos positivos y los falsos negativos: ningún IDS los evita del todo. En nuestro entorno no hay un IDS instalado, pero nftables ya trae las piezas con las que uno empieza: counter para contar, limit para marcar tasas excesivas y meter para seguir muchas direcciones a la vez. Un IDS real agrega el reensamblado de flujos, miles de firmas y el análisis del contenido de la aplicación.

IPsec: cifrado en la capa de red

Un firewall decide quién pasa. No protege lo que viaja: entre fw y gw2, todo cruza wan, que no es nuestra, y ahí cualquiera puede leer y modificar. Para eso existe una VPN (virtual private network): un túnel cifrado que hace que dos redes separadas se comporten como una sola, privada, sobre una red pública. Es la idea de túnel de la semana 4 —tratar una red entera como si fuera un enlace— pero ahora con criptografía.

La forma estándar de hacer esto en la capa de red es IPsec, definido en el RFC 4301. IPsec no es un protocolo sino un marco con varias piezas:

  • AH (Authentication Header): autentica el paquete e impide que lo modifiquen, pero no lo cifra. Protege integridad y origen, no confidencialidad.
  • ESP (Encapsulating Security Payload): cifra y autentica la carga. Es lo que se usa casi siempre.
  • Security association (SA): el estado de una dirección del túnel —qué algoritmo, qué clave, qué números de secuencia—. Hay una SA por sentido, identificada por un número llamado SPI.
  • Modo transporte o modo túnel: en transporte se protege la carga del datagrama y se deja el encabezado IP original; sirve entre dos hosts. En túnel se protege el datagrama IP entero y se le pone un encabezado IP nuevo por fuera; es el que se usa entre dos sitios, porque oculta también las direcciones internas.

En un despliegue real, las claves no se escriben a mano: un demonio como strongSwan negocia las SA con IKEv2, un protocolo de intercambio de claves. Aquí, para ver las piezas desnudas, creamos las SA a mano con ip xfrm y claves fijas. (No hay un demonio de IKE en el entorno; lo hacemos manual a propósito, y así se ve que IKE es justamente lo que nos ahorraríamos.)

Con ESP en modo transporte entre ext y wan, capturamos en el medio. Se ve el protocolo ESP (número 50) y un SPI, y nada de la carga:

$ python3 scenarios.py ipsec
ESP en modo transporte (cifra la carga): clave manual, una SA por sentido.
...
  100.64.15.100 > 100.64.15.97: ESP(spi=0x00000201,seq=0x2), length 100
  100.64.15.97 > 100.64.15.100: ESP(spi=0x00000202,seq=0x2), length 100

El ping sigue funcionando entre los dos extremos, pero quien mira en wan no puede decir que es un ping: ve dos SA (una por SPI, una por sentido) y bytes cifrados. Con AH, en cambio, el mismo ping viaja legible, con una cabecera de autenticación por delante:

AH en modo transporte (autentica, NO cifra): el ICMP se lee, con cabecera AH.
  100.64.15.100 > 100.64.15.97: AH(length=4(24-bytes),spi=0x00000301,...): ICMP echo request, id 216, seq 0, length 64
  100.64.15.97 > 100.64.15.100: AH(length=4(24-bytes),spi=0x00000302,...): ICMP echo reply, id 216, seq 0, length 64

tcpdump lee ICMP echo request: AH no oculta nada, solo garantiza que nadie lo cambió y que viene de quien comparte la clave. Y en modo túnel, el datagrama interno entero va cifrado dentro de uno nuevo, por eso el paquete externo es más grande. Cambiando a mode tunnel en las SA y las políticas, a mano, el total del paquete IP pasa de 120 a 140 bytes:

  100.64.15.100 > 100.64.15.97: ESP(spi=0x00000401,seq=0x1), length 120
Tres paquetes, cada uno con su captura. AH en modo transporte: IP de .100 a .97, AH con SPI 0x301 e ICMP echo request, que se lee. ESP en modo transporte: IP de .100 a .97, ESP con SPI 0x201 e ICMP cifrado, 120 bytes en total. ESP en modo túnel: IP nuevo de .100 a .97, ESP con SPI 0x401, e IP original e ICMP cifrados, 140 bytes en totalTres paquetes, cada uno con su captura. AH en modo transporte: IP de .100 a .97, AH con SPI 0x301 e ICMP echo request, que se lee. ESP en modo transporte: IP de .100 a .97, ESP con SPI 0x201 e ICMP cifrado, 120 bytes en total. ESP en modo túnel: IP nuevo de .100 a .97, ESP con SPI 0x401, e IP original e ICMP cifrados, 140 bytes en total
Los tres casos capturados. Los 20 bytes de diferencia entre 120 y 140 son el IP original, que en modo túnel viaja cifrado.

Idea clave. AH autentica, ESP cifra. Modo transporte protege la carga entre dos hosts; modo túnel protege el datagrama entero entre dos sitios y oculta las direcciones internas. Cuando se dice “una VPN IPsec sitio a sitio”, casi siempre es ESP en modo túnel.

Fíjense en los SPI de las capturas: 0x201 y 0x202 son las dos SA de ESP, una por sentido; 0x301 y 0x302, las de AH. Una SA es unidireccional por diseño: cada sentido tiene su clave, su contador de secuencia —que crece y nunca se repite, para bloquear la repetición de un paquete viejo— y su SPI, que es lo que el receptor usa para encontrar en su tabla con qué clave descifrar. Un túnel completo son al menos dos SA.

Lo que no vimos, porque lo pusimos a mano, es de dónde salen las claves. En un despliegue real eso es trabajo de IKEv2: autentica a los dos extremos —con certificados o con una clave compartida—, negocia qué algoritmos usar, deriva las claves de sesión con forward secrecy, y las rota cada cierto tiempo. Es un protocolo grande, con su propia máquina de estados y su propio historial de vulnerabilidades. Nuestras claves fijas, en cambio, sí autentican —a quien tenga la clave—, pero no rotan y no están ligadas a la identidad de nadie: alguien que copie la clave es indistinguible de su owner. Sirven para ver AH y ESP, no para producción. Ese contraste —IKE hace mucho, y por eso pesa— es justo el punto de partida de WireGuard.

Error común. Confundir “tengo IPsec” con “estoy seguro”. IPsec con una pre-shared key débil, o con una suite de cifrado vieja negociada por IKE, protege poco. La fortaleza está en el intercambio de claves y en los algoritmos, no en la sigla. WireGuard responde a esto quitando la negociación: un solo conjunto de algoritmos, sin opción a equivocarse.

IPsec funciona y es correcto. Su problema, como vamos a ver, es la complejidad: el marco tiene muchas opciones, AH y ESP, dos modos, IKEv1 e IKEv2, y una capa de transformación (xfrm) separada de las reglas del firewall. Esa complejidad es la que WireGuard decide no tener.

WireGuard: una VPN con menos piezas

WireGuard nace de una observación incómoda, que su autor expone en la introducción del artículo: IPsec está correctamente diseñado —cada cosa en su capa, con rigor académico— y aun así es tan complejo que desplegarlo y, sobre todo, implementarlo sin errores, es prohibitivo. WireGuard rompe a propósito esa separación de capas limpia a cambio de algo mucho más simple de usar y de auditar: todo el código del kernel cabe en menos de 4000 líneas.

Las decisiones de diseño, de las secciones 1 y 2 del artículo:

  • Una interfaz virtual, no una capa de transformación. WireGuard es una interfaz de red —wg0— que se configura con las herramientas de siempre (ip, wg). Las reglas de firewall se escriben sobre esa interfaz como sobre cualquier otra, con la garantía de que todo lo que sale por wg0 va cifrado y autenticado, y todo lo que entra por wg0 tiene un origen auténtico.
  • Identidad por clave pública. Cada peer se identifica por una clave pública Curve25519 de 32 bytes, al estilo de las claves de OpenSSH. No hay certificados ni autoridades: los dos extremos intercambian sus claves públicas por el medio que quieran (un correo, un chat), y eso basta.
  • Cryptokey routing. El corazón del diseño. Cada peer tiene una lista de allowed-ips: las direcciones de origen que puede usar. Al enviar, WireGuard mira la dirección de destino y elige la clave del peer cuya lista la contiene. Al recibir, descifra, y solo acepta el paquete si su dirección de origen está en la lista del peer que firmó. La asociación clave-direcciones es toda la configuración de routing del túnel.
  • Sin agilidad de algoritmos. WireGuard no negocia cifradores: usa Curve25519, ChaCha20, Poly1305 y BLAKE2s, y punto. Si algún día se rompe uno, todos actualizan. Esa rigidez elimina la clase entera de ataques de downgrade que arrastran TLS e IKE.
  • Corre sobre UDP, en un solo puerto, y encapsula paquetes IP (capa 3). Un intercambio de claves de una sola ida y vuelta (1-RTT), basado en el framework Noise, levanta la sesión; y un conjunto de timers la mantiene sin que el administrador tenga que pensar en conexiones: desde afuera, la interfaz parece sin estado.

Levantar el túnel

Cada extremo necesita un par de claves. wg genkey crea la privada; wg pubkey deriva la pública. El Makefile las crea en build/keys/ cuando corren make, con permisos restringidos, y make clean las borra: así make clean && make test nunca reutiliza una clave vieja.

wg genkey | tee fw.key | wg pubkey > fw.pub

Configuramos wg0 en fw y en gw2. En fw, el peer es gw2 (dirección pública 100.64.15.70), y sus allowed-ips son la red interna del otro sitio (10.215.3.0/24) más la dirección del túnel del peer:

fw#  wg set wg0 private-key fw.key listen-port 51820
fw#  wg set wg0 peer <gw2.pub> endpoint 100.64.15.70:51820 \
         allowed-ips 10.215.3.0/24,10.215.9.2/32 persistent-keepalive 25
fw#  ip addr add 10.215.9.1/30 dev wg0 ; ip link set wg0 up
fw#  ip route add 10.215.3.0/24 dev wg0

En gw2 es el espejo: el peer es fw, y sus allowed-ips son la oficina (10.215.1.0/24). Con eso, y tras pasar un poco de tráfico, wg show reporta todo el estado del túnel en pocas líneas:

$ docker exec clab-w15-sitios-fw wg show
interface: wg0
  public key: 8hLvGNRwY7QuCiwB9IAjU8iWlXPv6RI2T640ssLrdG4=
  private key: (hidden)
  listening port: 51820

peer: Rzrh502FJ2FPXlFBThOdjXE8uiGG0RRzMqHwplJtHFM=
  endpoint: 100.64.15.70:51820
  allowed ips: 10.215.3.0/24, 10.215.9.2/32
  latest handshake: 8 seconds ago
  transfer: 1.07 KiB received, 1.16 KiB sent
  persistent keepalive: every 25 seconds

Fíjense en lo poco que hay: una clave propia y un puerto, y por cada peer su clave pública, su endpoint, su lista de direcciones, cuándo fue el último handshake y cuántos bytes se movieron. No hay número de conexión ni comandos de “conectar”: uno declara los peers y el túnel existe. Por eso el artículo dice que, desde el punto de vista de la administración, la interfaz se comporta como si fuera sin estado —aunque por dentro sí mantiene el estado de la sesión, como muestran las líneas de handshake y transferencia—. La MTU de wg0 quedó en 1420, un número al que volvemos al hablar del costo:

12: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000

El handshake y los timers

Detrás de esa simplicidad hay un intercambio de claves y una máquina de timers que el administrador no toca. El detalle está en las secciones 5 y 6 del artículo, que no son lectura obligatoria. En lo que nos importa:

  • Un handshake de una sola ida y vuelta (1-RTT). El iniciador manda un primer mensaje, el respondedor contesta uno, y con eso los dos derivan las claves de la sesión. El intercambio usa el framework Noise (variante IK): autentica a los dos extremos por su clave pública y da forward secrecy, porque las claves de sesión son efímeras y se tiran al rotar. Es el mismo objetivo que TLS 1.3 de la semana 13, con muchas menos piezas y sin certificados.
  • Silencio por defecto. Una interfaz WireGuard no manda nada si no hay tráfico que enviar. No hay keepalives de protocolo, ni anuncios: callar es parte del diseño, y dificulta que alguien sepa siquiera que el túnel existe.
  • El keepalive es opcional. Pusimos persistent-keepalive 25 porque detrás de un NAT la “fila” que abre el tráfico de salida se cierra tras unos segundos de silencio; un paquete vacío cada 25 s la mantiene abierta para que el otro extremo pueda iniciar. Sin NAT de por medio no haría falta.
  • La sesión se renueva sola. Tras unos minutos, o un volumen de datos, WireGuard rehace el handshake para rotar las claves. Si un mensaje de handshake se pierde, lo reintenta. Nada de esto aparece en la configuración.

Idea clave. WireGuard no tiene “conectar” ni “desconectar”. Uno declara los peers y sus direcciones, y el túnel existe cuando hay algo que mandar. El handshake, la rotación de claves y la reconexión pasan solos. Esa es la diferencia de uso más grande con IPsec, donde un demonio de IKE mantiene el estado de la sesión y hay que vigilarlo.

En la práctica. El endpoint de un peer se puede aprender del paquete que llega, no solo fijarlo de antemano (sección 2.1 del artículo). Como la identidad es la clave y no la dirección, un peer puede cambiar de red —de Wi-Fi a datos móviles, por ejemplo— y el túnel lo sigue: el otro extremo actualiza el endpoint al ver la nueva dirección de origen de un paquete bien autenticado. Es el roaming que hace a WireGuard cómodo en laptops y celulares.

Qué protege y qué no: la captura a los dos lados

Esta es la parte importante de la semana. Un ping de la oficina al host b del otro sitio funciona, aunque b no tenga ninguna ruta pública: viaja por el túnel. Lo capturamos en tres puntos a la vez.

En fw eth1, el lado interno de salida, el ICMP va en claro:

  en fw eth1 (interno): ICMP en claro
IP 10.215.1.10 > 10.215.3.10: ICMP echo request, id 104, seq 0, length 64
IP 10.215.3.10 > 10.215.1.10: ICMP echo reply, id 104, seq 0, length 64

En wan eth1, en medio de la red ajena, no hay ningún ICMP: solo UDP al puerto 51820, cifrado, entre las direcciones públicas de los dos routers:

  en wan eth1 (externo): UDP 51820, cifrado
IP 100.64.15.66.51820 > 100.64.15.70.51820: UDP, length 128
IP 100.64.15.70.51820 > 100.64.15.66.51820: UDP, length 128

Y en b eth1, el lado interno del destino, el ICMP vuelve a verse en claro:

  en b eth1 (interno, destino): ICMP en claro otra vez
IP 10.215.1.10 > 10.215.3.10: ICMP echo request, id 104, seq 0, length 64

Ese contraste es toda la lección. Entre los dos extremos del túnel, el tráfico va cifrado y quien mira wan solo ve “dos direcciones hablan UDP por el puerto 51820”, sin las direcciones internas, sin saber que es un ping. Fuera del túnel —en la red interna de cada sitio, o más allá de b— el tráfico está en claro, como siempre.

Error común. Creer que una VPN hace anónimo o seguro a todo. Protege el tramo entre sus dos extremos. Lo que una VPN comercial hace es mover el extremo de salida a otro lugar: su proveedor ve todo lo que sale descifrado, igual que lo vería su ISP sin la VPN, y el servidor de destino sigue viendo una conexión en claro desde el extremo de la VPN. En nuestra captura, todo lo que pasa en la red de b después de salir de wg0 viaja sin cifrar. La VPN cambia quién puede ver su tráfico, no lo hace invisible.

Identidad por clave: un origen que no corresponde

El cryptokey routing es también una defensa. gw2 solo acepta de fw paquetes cuyo origen esté en su lista de allowed-ips:

$ python3 scenarios.py allowed
  gw2# wg show wg0 allowed-ips
2PtwHBSu04WEnQ7/rNNZUPmpw3W18K012xjpKAVapRI=	10.215.1.0/24 10.215.9.1/32
  origen 100.64.15.66: 2 packets transmitted, 0 packets received, 100% packet loss
    UDP del túnel en gw2: 2 paquetes; ICMP en b: 0 paquetes
  origen 10.215.9.1: 2 packets transmitted, 2 packets received, 0% packet loss
    UDP del túnel en gw2: 4 paquetes; ICMP en b: 4 paquetes

El escenario hace ping desde fw hacia b con dos orígenes. 100.64.15.66 es el eth3 de fw y no está en la lista: el paquete llega cifrado a gw2 (los 2 UDP), gw2 lo descifra, ve que ese origen no le corresponde al peer fw y lo descarta. A b no llega nada. 10.215.9.1 sí está en la lista, y cruza: 2 pedidos y 2 respuestas, 4 paquetes en cada punto.

Animación de fw, wan, gw2 y b, con las allowed-ips de cada extremo: en fw, para el peer gw2, 10.215.3.0/24 y 10.215.9.2/32; en gw2, para el peer fw, 10.215.1.0/24 y 10.215.9.1/32. Primero, al enviar, el destino 10.215.3.10 elige el peer gw2. Después, un ping con origen 100.64.15.66 cruza cifrado, gw2 lo descifra y lo descarta porque el origen no está en la lista: 2 paquetes UDP en gw2 y 0 ICMP en b. Por último, un ping con origen 10.215.9.1, que sí está en la lista, cruza: 4 paquetes UDP en gw2 y 4 ICMP en bAnimación de fw, wan, gw2 y b, con las allowed-ips de cada extremo: en fw, para el peer gw2, 10.215.3.0/24 y 10.215.9.2/32; en gw2, para el peer fw, 10.215.1.0/24 y 10.215.9.1/32. Primero, al enviar, el destino 10.215.3.10 elige el peer gw2. Después, un ping con origen 100.64.15.66 cruza cifrado, gw2 lo descifra y lo descarta porque el origen no está en la lista: 2 paquetes UDP en gw2 y 0 ICMP en b. Por último, un ping con origen 10.215.9.1, que sí está en la lista, cruza: 4 paquetes UDP en gw2 y 4 ICMP en b
La misma lista decide en los dos sentidos: al enviar, a qué peer va un destino; al recibir, si ese peer puede usar ese origen.

La clave da identidad, y la identidad solo autoriza ciertas direcciones. Es la sección 2 del artículo hecha regla: “cualquier paquete que llegue por una interfaz de WireGuard tendrá un origen auténtico”. Sobre eso, el firewall del otro sitio puede confiar en la dirección de origen de lo que sale de wg0, algo que nunca puede hacer con el tráfico que llega de Internet.

El costo de la VPN: MTU y throughput

Una VPN no es gratis. Cuesta en tamaño de paquete y en throughput.

El tamaño primero. La sección 5.4.6 del artículo describe el mensaje de datos de WireGuard: un encabezado de 16 bytes (tipo, un índice de receptor y un contador), la carga rellenada hasta un múltiplo de 16 bytes, y la etiqueta de autenticación Poly1305 de 16 bytes. O sea, el UDP lleva redondeo16(interno) + 32. Lo comprobamos variando el tamaño de un ping. Por defecto manda 56 bytes de datos, que con los 8 del ICMP y los 20 del IP hacen un datagrama interno de 84 bytes; con -s 68 el interno es 96 (ya múltiplo de 16) y con -s 69, 97. Capturamos el UDP externo en el medio:

$ python3 scenarios.py overhead
    -s 56: interno 84, afuera UDP, length 128
    -s 68: interno 96, afuera UDP, length 128
    -s 69: interno 97, afuera UDP, length 144

La cuenta: 84 y 96 se redondean a 96, y 96 + 32 = 128; 97 se redondea a 112, y 112 + 32 = 144.

El salto entre -s 68 y -s 69 es la prueba del relleno: un byte más de carga empuja el redondeo al siguiente múltiplo de 16 y el paquete externo crece 16 bytes de golpe. Si el overhead fuera constante, habría subido solo un byte.

A eso hay que sumarle, por fuera, los 20 bytes del nuevo encabezado IP y los 8 del UDP. Por eso la MTU de wg0 es 1420: WireGuard reserva 80 bytes sobre los 1500 del enlace para su propio peso en el peor caso (un encabezado externo IPv6, más el encabezado y la etiqueta). Un paquete que entre al túnel más grande que eso no cabe. Lo interesante es cómo se entera el emisor: con PMTUD, el descubrimiento de MTU del camino. Tras una transferencia grande de la oficina hacia b, la oficina cachea la MTU del túnel en su ruta:

$ python3 scenarios.py pmtu
IP (tos 0xc0, ttl 64, id 19096, offset 0, flags [none], proto ICMP (1), length 576)
    10.215.1.1 > 10.215.1.10: ICMP 10.215.3.10 unreachable - need to frag (mtu 1420), length 556
	IP (tos 0x0, ttl 64, id 59619, offset 0, flags [DF], proto TCP (6), length 1500)
    10.215.1.10.41512 > 10.215.3.10.5301: Flags [.], seq 3218855649:3218857097, ack 2592732410, win 502, options [nop,nop,TS val 3576300894 ecr 58448353], length 1448
  ofi# ip route get 10.215.3.10
10.215.3.10 via 10.215.1.1 dev eth1 src 10.215.1.10 uid 0 
    cache expires 596sec mtu 1420

fw recibió un segmento de 1500 bytes con el bit “no fragmentar” (DF). No cabía en wg0, así que contestó con un ICMP que trae adentro el inicio del paquete que causó el problema. La oficina lo guardó en su cache de rutas: de ahí la última línea.

De ahí sale una consecuencia práctica para los firewalls: si alguien bloquea esos ICMP, PMTUD se rompe y las conexiones grandes se cuelgan sin motivo aparente. Es uno de los errores de configuración más difíciles de diagnosticar. En esta topología el ICMP lo genera el propio fw (su cadena output acepta por defecto), así que llega; pero cuando quien lo genera es un router más allá del firewall, el ICMP entra como una conexión nueva ligada a un flujo conocido, y es la regla ct state established,related accept la que lo deja pasar. Bloquear el tráfico related es, justamente, una de las formas de romper PMTUD sin darse cuenta.

El throughput después. Para no mezclar efectos, medimos el mismo camino de cuatro saltos de dos formas: con rutas temporales que lo llevan sin cifrar, y por el túnel. Las cifras dependen de la computadora y de lo que esté corriendo al mismo tiempo:

$ python3 scenarios.py cost
    sin cifrar    7479 Mbits/sec
    por el túnel  1407 Mbits/sec
    sin cifrar    6823 Mbits/sec
    por el túnel  1158 Mbits/sec
    sin cifrar    5800 Mbits/sec
    por el túnel  1397 Mbits/sec

El túnel va unas cinco veces más lento en esta máquina. Los encabezados no lo explican. Con MTU 1420, un segmento lleva 1368 bytes de datos (1420 menos 40 de IP y TCP, y 12 de timestamps) en un paquete externo de 1480, o sea 92 % de datos; sin túnel son 1448 de 1500, 97 %. Esa diferencia cuesta cerca de un 5 %. El resto lo atribuimos al trabajo de CPU de cifrar y descifrar, sin haberlo medido por separado: aquí nada limita el enlace salvo la propia CPU. En un enlace lento mandaría el enlace y se notaría casi solo el 5 %. El ejercicio 10 les pide comprobarlo.

En la práctica. El MSS que cada extremo anuncia en el SYN no conoce el túnel: b y la oficina anuncian 1460, calculado con la MTU de 1500 de sus propias LAN. El ajuste a la MTU del túnel lo hace PMTUD en el camino. Cuando PMTUD no es confiable (ICMP filtrado en algún lado), los firewalls de VPN suelen bajar a mano el MSS de los SYN que los cruzan, al valor del túnel: es el MSS clamping. En nuestra red no hace falta, porque PMTUD funciona.

Tres formas de proteger un camino

A lo largo del curso vimos tres maneras de cifrar y autenticar el tráfico, en tres capas distintas. Conviene ponerlas lado a lado, porque la pregunta de examen y la de la vida real es la misma: ¿qué protege cada una, y hasta dónde?

Capa Qué protege Qué deja ver Autentica con
TLS 1.3 (semana 13) transporte (sobre TCP) una conexión entre una aplicación y un servidor direcciones, tamaños y el SNI certificados y autoridades
IPsec red todo el tráfico IP entre dos extremos las direcciones externas (en modo túnel, solo las de los routers) certificados o clave compartida, vía IKE
WireGuard red todo el tráfico IP entre dos peers que dos direcciones hablan UDP por un puerto claves públicas intercambiadas fuera de banda

Las tres dan confidencialidad, integridad y autenticación en su tramo, con forward secrecy. Las diferencias importan:

  • TLS protege una conexión, no una red. Cada aplicación la usa por su cuenta; lo que no va por esa conexión no está protegido. Y como vimos en la semana 13, revela el SNI: quien observa la red sabe a qué sitio se conectan, aunque no qué piden.
  • IPsec y WireGuard protegen el tráfico IP entero, de cualquier aplicación, sin que esta se entere. Por eso son la base de las VPN: hacen que dos redes se comporten como una. El costo es que trabajan por debajo de las aplicaciones, y no saben nada de ellas.
  • Ninguna oculta que hay tráfico. Un observador siempre ve que dos direcciones intercambian datos cifrados, y cuántos. Esconder eso —la existencia misma de la comunicación— es un problema mucho más difícil, que estas herramientas no resuelven.

Idea clave. La pregunta no es “¿esto es seguro?”, sino “¿qué tramo protege, y qué sigue viendo quien observa la red?”. Para TLS, el tramo es una conexión y se ve el SNI. Para una VPN, el tramo llega hasta su otro extremo y se ven las direcciones públicas. Más allá de ahí, nada de esto ayuda.

Ejemplo resuelto: seguir un paquete por el firewall y el túnel

Juntemos todo en el ejercicio que el examen final suele pedir: seguir un paquete salto por salto, diciendo en cada tramo qué encabezados tiene y qué direcciones quedan visibles. Tomamos el ping de la oficina (10.215.1.10) al host b del otro sitio (10.215.3.10), con el firewall y el túnel arriba. Lo que sigue sale de la captura en tres puntos de la sección anterior.

Tramo 1, red interna del sitio A (ofi → fw, visto en fw eth1). El paquete es un datagrama IP normal, con las direcciones internas. fw lo recibe. Como su destino 10.215.3.0/24 tiene ruta por wg0, el firewall lo evalúa en la cadena forward con oifname wg0: la regla iifname eth1 oifname wg0 accept lo deja pasar, y conntrack anota la conexión para dejar volver la respuesta.

Tramo 2, la red ajena (fw → wan → gw2, visto en wan eth1). Al salir por wg0, WireGuard cifra el datagrama interno entero y lo mete en un UDP entre las direcciones públicas de los dos routers. El datagrama interno desaparece de la vista.

Quien observe wan solo ve esto: dos direcciones hablando UDP por el puerto 51820, con una carga de 128 bytes (los 84 del ping interno, rellenados a 96 y con 32 bytes de encabezado y etiqueta de WireGuard). No ve 10.215.1.10, ni 10.215.3.10, ni que es un ping.

Tramo 3, red interna del sitio B (gw2 → b, visto en b eth1). gw2 recibe el UDP, descifra, comprueba que el origen interno está en la lista de allowed-ips del peer fw, y lo reinyecta en su red. El datagrama vuelve a ser el original.

Animación del ping de ofi a b por el camino ofi, fw, wan, gw2, b, con tres puntos de captura: fw eth1, wan eth1 y b eth1. En fw eth1 el paquete es IP de 10.215.1.10 a 10.215.3.10 con un ICMP echo request, en claro. Al salir por wg0 se vuelve IP de 100.64.15.66 a 100.64.15.70, UDP del puerto 51820 al 51820 y 128 bytes de datos cifrados, y eso es lo que se ve en wan eth1. gw2 lo descifra, comprueba las allowed-ips y en b eth1 es otra vez el ICMP originalAnimación del ping de ofi a b por el camino ofi, fw, wan, gw2, b, con tres puntos de captura: fw eth1, wan eth1 y b eth1. En fw eth1 el paquete es IP de 10.215.1.10 a 10.215.3.10 con un ICMP echo request, en claro. Al salir por wg0 se vuelve IP de 100.64.15.66 a 100.64.15.70, UDP del puerto 51820 al 51820 y 128 bytes de datos cifrados, y eso es lo que se ve en wan eth1. gw2 lo descifra, comprueba las allowed-ips y en b eth1 es otra vez el ICMP original
Los tres tramos, con los encabezados que se ven en cada uno. Entre fw y gw2 no queda a la vista ninguna dirección interna.

b responde, y todo pasa al revés: su respuesta entra a gw2, se cifra, cruza wan como UDP, y fw la descifra y la entrega a ofi. En fw, la respuesta calza como established en conntrack, así que no necesita una regla propia.

Ahora la extensión que el examen suele agregar: ¿y si el sitio A tuviera un NAT de salida, como el de la semana 4? La clave está en el orden. El NAT y el túnel se aplican a tramos distintos: el tráfico hacia b va por wg0 y no se traduce, porque para el NAT el destino 10.215.3.0/24 no es “afuera” sino la otra parte de la red privada, alcanzable por el túnel. Solo el tráfico que sale de verdad hacia Internet (hacia eth3) pasaría por el NAT. Por eso, en un router que hace las dos cosas, el orden de las tablas importa: primero decidir si un paquete va al túnel, y recién después aplicar el NAT a lo que quede. Equivocar ese orden —mandar al NAT lo que debía ir al túnel— es un error clásico que rompe la VPN sin que se caiga ningún enlace.

Idea clave. En cada salto, pregúntense tres cosas: ¿qué encabezados tiene el paquete?, ¿qué direcciones son visibles?, ¿qué decisión toma este nodo (forwarding, firewall, cifrar, traducir)? La respuesta cambia en cada tramo, y esa es toda la historia de cómo viaja un paquete por una red real.

El laboratorio de la semana

Trabajen en code/week-15/, dentro del contenedor del curso. El laboratorio tiene dos mitades: el firewall y la VPN, sobre la misma topología de dos sitios.

cd code/week-15
make          # crea las claves de WireGuard
make up       # levanta la topología
  1. El firewall. Abran lab/firewall.nft. Tiene la estructura y los TODO: completen las reglas para que de adentro se salga, la DMZ sea accesible solo en 80 y 443 desde afuera, y la red interna quede protegida. Cárguenlo con docker exec -i clab-w15-sitios-fw nft -f - < lab/firewall.nft. Editen el archivo, no usen el guardado en vivo del router.
  2. Probar cada regla. Con nc y curl desde ext, ofi y web, comprueben la tabla de la política. Anoten, para cada prueba, qué esperaban y qué pasó. Fíjense en la diferencia entre un puerto que el firewall rechaza y uno aceptado sin servidor detrás.
  3. El túnel. Con scenarios.py wireguard o a mano, levanten WireGuard entre fw y gw2. Confirmen con wg show el handshake, y con un ping de ofi a b que la oficina alcanza el otro sitio.
  4. Capturar los dos lados. Capturen en fw eth1, en wan eth1 y en b eth1 a la vez, y comparen el paquete interno con el externo. Expliquen, con su captura, qué protege el túnel y qué no.
  5. Medir el costo. Con iperf3, comparen el throughput por el túnel contra un camino sin cifrar, y revisen la MTU cacheada con ip route get. Repitan las mediciones; den tendencias, no cifras exactas.

Como el firewall de lab/ empieza con TODO, make test salta las pruebas del firewall hasta que lo completen, igual que en las semanas 4 y 10 con el código del laboratorio. Las pruebas del túnel y de IPsec corren de todas formas.

make test

make test crea las claves, levanta la topología, corre test_net.py y siempre la destruye, incluso si algo falla. Las pruebas comprueban lo que afirman estos apuntes: que el firewall deja la DMZ accesible y la red interna no, que la DMZ no abre conexiones hacia adentro, que las cadenas descartan por defecto, que en el medio del túnel solo se ve UDP cifrado y no el ICMP interno, que un origen fuera de allowed-ips no se enruta, que PMTUD cachea la MTU del túnel, y que ESP oculta la carga mientras AH no. Las mediciones usan márgenes amplios a propósito: la red es compartida y ruidosa, y una prueba que exija una cifra exacta fallaría sin que nada esté mal.

$ make test
...
Ran 17 tests in 70.967s
OK

make clean destruye la topología, borra las claves y las capturas, y quita el estado de Containerlab, para que cada corrida empiece de cero.

La Sesión B: presentación de los proyectos

La Sesión B no es una clase: cada equipo presenta P5. Muestran wasi respondiendo sobre rimay en Qhapaq Ñan, con su nombre en DNS, desde un host de otro AS, cruzando BGP. El docente corta un enlace con otro camino disponible mientras se baja un archivo grande, y la transferencia debe terminar correctamente: ahí se ve si rimay de verdad recupera la pérdida y si la red encuentra otro camino. Las preguntas van sobre las decisiones de diseño y las mediciones —rimay frente a TCP—, no sobre el código línea por línea. El enunciado y las reglas están en projects/p5-wasi/README.md; aquí no los repetimos.

Repaso guiado para el examen final

El examen final (semana 16) cubre todo el curso, con énfasis en las semanas 9 a 15. El formato está en exams/README.md. Este repaso no agrega temas: ordena lo que ya vieron alrededor de las cosas que el examen suele pedir.

  • Seguir un paquete, salto por salto, con túneles. La especialidad de esta semana. Tomen el ping de la oficina a b: en fw eth1 es 10.215.1.10 -> 10.215.3.10, ICMP; al entrar a wg0 se cifra; en wan es 100.64.15.66 -> 100.64.15.70, UDP 51820; al salir por wg0 en gw2 vuelve a ser el ICMP interno. Sepan decir, en cada salto, qué encabezados hay y por qué. Combínenlo con lo de la semana 4: añadan un NAT y digan qué cambia.
  • Transporte y congestión (semanas 9 a 11). Go-Back-N y Selective Repeat a mano; el diagrama de cwnd de Reno con una pérdida; el cálculo del RTO; por qué AIMD converge. Lean una captura de TCP: handshake, MSS, ACK duplicados, fast retransmit.
  • Aplicaciones (semanas 12 y 13). Una resolución de DNS de la raíz hacia abajo; qué autentica DNSSEC y qué cifra DoH; el handshake de TLS 1.3 y qué garantiza; por qué QUIC corre sobre UDP.
  • Redes modernas (semanas 14 y 15). Por qué un data center usa Clos y ECMP reparte por flujo; qué separa SDN; y, de esta semana, para un ataque de cada capa, la defensa y su límite.
  • Qué NO protege cada defensa. El examen pregunta límites: RPKI no valida el AS_PATH completo; DNSSEC no da privacidad; TLS no oculta el SNI ni los tamaños; una VPN no protege más allá de su extremo; las SYN cookies no protegen el ancho de banda; el BCP 38 depende de que lo apliquen las otras redes. Tener esa lista clara vale varias preguntas.

La mejor preparación sigue siendo la del exams/README.md: resolver las preguntas de repaso de cada guía, predecir lo que hará un laboratorio antes de correrlo, y releer su propio proyecto, donde ya tomaron muchas de las decisiones que el examen pregunta.

Resumen

  • Modelo de amenazas: toda defensa es contra un adversario con ciertas capacidades. Importa si está en el camino (ve el tráfico, no adivina) o fuera (no ve, tiene que adivinar), y si es pasivo o activo. “Seguro” sin decir contra quién no significa nada.
  • Ataques por capa: ARP spoofing (enlace), IP spoofing y amplificación (red), SYN flood y DoS (transporte), envenenamiento de DNS (aplicación), hijack de BGP (routing). Casi todos salen de que un protocolo no autentica o no cifra, y casi todas las defensas envuelven, validan o filtran sin cambiar el protocolo.
  • Firewall con estado: deniega por defecto, acepta lo establecido primero, y define la política por zonas (interna, DMZ, afuera). conntrack sigue las conexiones; ct state las consulta. Proteger input además de forward. Descartar se ve como filtrado. Rechazar da un error enseguida, pero con ICMP y EHOSTUNREACH, no con el RST y el ECONNREFUSED de un puerto cerrado.
  • Zero trust: no confiar en alguien solo por estar dentro de la red; autenticar cada acceso.
  • IPsec: AH autentica, ESP cifra; modo transporte entre hosts, modo túnel entre sitios; una SA por sentido; IKE negocia las claves. Correcto pero complejo.
  • WireGuard: una interfaz wg0, identidad por clave pública, cryptokey routing con allowed-ips, sin agilidad de algoritmos, 1-RTT sobre UDP. Simple de usar y de auditar.
  • Qué protege una VPN: el tramo entre sus dos extremos. En el medio solo se ve UDP cifrado entre las direcciones públicas; fuera del túnel, todo va en claro. No hace anónimo ni seguro a todo.
  • Costo de la VPN: MTU más chica (wg0 1420; PMTUD la propaga, y el firewall no debe bloquear los ICMP que la anuncian) y menos throughput por el cifrado en CPU.

Ejercicios

  1. Expliquen, para el ataque de amplificación de la semana 12, por qué usa UDP y direcciones falsificadas, y por qué un ISP que aplica BCP 38 lo detiene mientras que el servidor víctima no puede. ¿Qué tendría que pasar en Internet para que la amplificación con reflejo desapareciera?
  2. Escriban, para un servidor web en una DMZ, las reglas de un firewall con estado que: deje entrar desde afuera solo a 80 y 443; deje al servidor iniciar conexiones de salida (para actualizaciones); y bloquee todo lo demás. Indiquen qué pasa, con sus reglas, si el servidor es comprometido e intenta conectarse a la red interna.
  3. ¿Qué diferencia hay entre el modo túnel y el modo transporte de IPsec? ¿Cuál se usa entre dos sitios y por qué? Dibujen, para un ping de 10.215.1.10 a 10.215.3.10, cómo se ve el paquete en el medio con ESP en cada modo, y digan qué direcciones quedan visibles.
  4. ¿Qué protege WireGuard y qué no? Usen la captura de los tres puntos del laboratorio para justificar la respuesta, y expliquen qué ve exactamente alguien que observa wan.
  5. Para un ataque de cada capa vista en el curso (enlace, red, transporte, aplicación, routing), indiquen la defensa que estudiaron y una limitación concreta de esa defensa.
  6. En el laboratorio, carguen el firewall y luego quiten la regla ct state established,related accept, dejando el resto. Predigan qué conexiones dejan de funcionar y por qué, y compruébenlo. ¿Por qué esa regla va primero y no al final?
  7. Con el túnel arriba, agreguen en fw una regla que descarte los ICMP fragmentation needed hacia la oficina, e intenten una transferencia grande de ofi a b. ¿Qué pasa? Expliquen la relación con PMTUD y con la MTU de 1420 de wg0, y propongan dos formas de arreglarlo.
  8. Comparen el número de allowed-ips de un peer de WireGuard con un filtro de prefijos de BGP de la semana 7: ¿en qué se parecen como mecanismo de “solo acepto estas direcciones de ti”? ¿Qué garantiza WireGuard, además, que un filtro de BGP no puede garantizar sobre el origen?
  9. Un compañero afirma: “uso una VPN, así que mi navegación es anónima y segura”. Desármenlo con precisión: ¿qué tramo protege la VPN?, ¿qué ve su proveedor de VPN?, ¿qué sigue viendo el servidor de destino?, ¿y qué relación tiene esto con el SNI de TLS de la semana 13?
  10. Midan con iperf3, repitiendo varias veces, el throughput por el túnel y por un camino directo equivalente. Reporten el rango de cada uno, no una cifra. ¿Cuánto del costo atribuyen al cifrado y cuánto a la MTU más chica? Diseñen una medición que separe los dos efectos, y digan qué esperarían.
  11. Expliquen por qué un firewall debe filtrar la cadena input además de forward. Den un ejemplo concreto de un servicio del propio router que quedaría expuesto si solo se protege forward, y escriban la regla que lo cierra.
  12. Zero trust: tomen la red de dos sitios del laboratorio y describan cómo cambiaría si no se confiara en “estar adentro”. ¿Qué autenticarían entre ofi y web? ¿Qué papel jugaría una clave por servicio, y en qué se parece a lo que hace WireGuard entre los dos sitios?