Sobre NSGs de Azure y VNET peerings.
Las redes en la Nube se están volviendo cada vez más cómodas y estandarizadas. Pero eso no significa que no estén exentas de particularidades específicas de cada proveedor. En este caso, al intentar implementar Network Security Groups de Azure (las ACL de Azure) bajo una configuración concreta mostró un comportamiento que me permitió aprender un par de cosas sobre networking en Azure.
La configuración planeada: NSGs para bloquear acceso desde Internet
La configuración planeada era bastante directa y la teníamos implementada en otras partes de nuestra infraestructura: Limitar el acceso público (desde Internet) a nuestros servicios sólo ciertas IPs. Para eso, implementamos NSGs en las subnets de las VNETs dónde se encuentran los servidores. Es un método más que probado, así que los NSGs fueron configurados y se agregaron las excepciones por IP. Pero esta vez no funcionó: los servicios eran accesibles desde Internet para cualquiera. ¿Por qué? ¿Qué era diferente en esta configuración? Bastantes cosas, en realidad.
La configuración inicial: Más componentes a tener en cuenta
Los servidores estaban distribuidos en una VNET (vamos a llamarla Example VNET) con cuatro subredes, pero lo reduciré a dos para este ejemplo: ServersA_subnet y ServersB_subnet. Y luego las Azure Application Gateways que controlaban el acceso estaban en otra subnet (Gateways_subnet). Eso son 3 subredes dónde podíamos aplicar un NSG para prevenir el acceso no autorizado a cualquiera de las subredes de los servidores y las AppGW que estaban gestionando la mayor parte del tráfico.
La configuración del NSG era bastante simple. Sólo agregar reglas de entrada, ya que no queríamos limitar la salida:
- Permitir tráfico HTTPS de entrada si viene de ciertas IP públicas
- Permitir tráfico de entrada si viene de la VNET (esta es una regla por defecto de Azure para permitir la comunicación intra-VNET)
- Denegar el resto del tráfico de entrada
Mi IP claramente no era una de las IP públicas permitidas, pero podía acceder a los servidores. Otros compañeros también podían. ¿Qué estaba mal en la configuración? ¿No se estaban aplicando los NSG correctamente? Improbable, pero después de validar que la configuración de los NSG era correcta me lancé a investigar qué tenía este entorno que los demás no tenían. ¡Hora de trazar el flujo de red!
El flujo de entrada desde la solicitud en el navegador hasta que llegaba a los servidores era el siguiente:
- Primero un CNAME del tipo
customer-service.ourdomain.com
en nuestro proveedor externo de DNS, mapeado a un A record (también en el mismo proveedor externo de DNS). - A RECORD mapeado a la IP pública de uno de nuestros Azure Firewalls.
- Una regla Dynamic NAT (DNAT) en el Firewall que traducía la IP pública a la IP privada de la Application Gateway correspondiente.
- La AppGW enviaba la solicitud a uno de los servidores en la backend pool correspondiente.
La configuración era diferente, principalmente porque tenía un Firewall y además estaba haciendo NAT. Este método con NSGs se utilizaba en entornos que no utilizaban Azure Firewall. Además, el CNAME estaba proxied, lo que significa que una vez llega al proveedor externo de DNS entra en su red y llega al destino desde una de sus IPs Públicas. ¿Ves venir lo que estaba pasando?
Lo que estaba pasando: aquello ya no era tráfico público
El problema con nuestro enfoque fue, ante todo, que nuestro Firewall estaba realizando DNAT: Las IP públicas de nuestro proveedor de DNS estaban permitidas y, como el CNAME estaba proxied, cualquier petición a ese CNAME customer-service.ourdomain.com
aparecía como proveniente de esas IPs públicas permitidas. Todo bien, eso debería ocurrir. Pero en ese punto la IP original de la máquina que hacía la petición ya era irrelevante. Pero más cosas interesantes estaban ocurriendo que muestran un par de cosas sobre el funcionamiento interno de las VNETs en Azure.
Al realizar la traducción para la regla de DNAT, el Firewall de Azure traduce las IP de origen a IPs privadas dentro del rango de la subnet del propio Firewall. Según este techcommunity doc:
(El texto original está en Inglés)
“Cuando un nuevo flujo coincide con una regla DNAT en Azure Firewall, tanto la dirección IP de origen como la de destino se traducirán a nuevos valores. Cuando el destino es una dirección IP privada en la red virtual, la dirección IP de origen se traducirá a una de las direcciones IP en la subred AzureFirewall de la red virtual, mientras que la dirección IP de destino se traducirá a lo que se ha configurado en la regla DNAT como la dirección traducida”.
Así que técnicamente, después de ejecutar la regla DNAT, cualquier petición desde el exterior (Internet) para ese CNAME era ya parte de la subred del Firewall. Aún así, eso no significaba que la petición tuviera que llegar a los servidores. En realidad debería haber sido al revés, ¿no?. En el NSG la Regla 1 era permitir una serie de IPs públicas que ya no tenían sentido (todas las peticiones en este punto tenían una IP privada en el rango de la subnet del Firewall) y la Regla 2 era permitir cualquier tráfico desde la misma VNET, y el Firewall estaba en una VNET completamente diferente. En todo caso, nuestra configuración original debería haber denegado todas las peticiones, incluso aquellas que vinieran desde las IPs permitidas (que a estas alturas en el flujo de la petición eran irrelevantes). Pero en lugar de eso el NSG estaba permitiéndolo todo. ¿Por qué? Bueno… la VNET del Firewall y nuestra Example VNET estaban peered (emparejadas)
Según la documentación oficial de Microsoft si el peering se marca como “Permitir a vnet-1 acceder a vnet-2” (y viene marcado por defecto) la service tag VirtualNetwork
-utilizada por los NSGs- incluye tanto la VNET original como la VNET emparejada (peered). Lo que significa que, para el NSG, la VNET del Firewall y la Example VNET a estas alturas eran indistinguibles.
Así que, lógicamente, el NSG estaba haciendo su trabajo. Mis peticiones desde el exterior eran primero proxied a través de nuestro proveedor externo de DNS y adquirían una de sus IP públicas, que estaban permitidas en nuestro Firewall pero ya no eran my IP pública original. Después el Firewall ejecutaba una regla DNAT para traducir la IP de destino de mis peticiones a una IP privada en el rango correcto del recurso correcto (una Application Gateway).
En el proceso también traducía (otra vez) la IP de origen para que estuviera en el rango de las IPs privadas de su propia subred en la VNET del Firewall. Ahora, como parte de una VNET que estaba emparejada (peered) con la Example VNET, mi petición era exactamente igual a cualquier otra petición desde el interior de Example VNET. Según la Regla 2, el NSG permitía la petición. Y eso era todo, misterio resuelto.
La solución
No podíamos sencillamente bloquear el tráfico intra-VNET, nuestros servidores tenían que comunicarse unos con otros y con otros recursos. Una configuración más compleja como esta no iba a funcionar con NSGs. Y retirar el proxy del CNAME para que las peticiones no llegasen con una IP Pública de nuestro proveedor estaba fuera de la discusión (nos proporcionaba muchas otras ventajas). Nuestra mejor opción fue tratar de resolverlo lo más cerca posible del borde, aprovechando otro tipo de ACLs en nuestro proveedor externo de DNS.