Cómo proteger tu sitio web público con ESNI

Hola Habr, mi nombre es Ilya, trabajo en el equipo de plataforma de Exness. Desarrollamos e implementamos los componentes de infraestructura central que utilizan nuestros equipos de desarrollo de productos.

En este artículo, me gustaría compartir mi experiencia en la implementación de tecnología SNI cifrada (ESNI) en la infraestructura de sitios web públicos.

Cómo proteger tu sitio web público con ESNI

El uso de esta tecnología aumentará el nivel de seguridad al trabajar con un sitio web público y cumplirá con los estándares de seguridad internos adoptados por la Compañía.

En primer lugar, me gustaría señalar que la tecnología no está estandarizada y aún está en borrador, pero CloudFlare y Mozilla ya la admiten (en borrador01). Esto nos motivó a realizar tal experimento.

Un poco de teoría

ESNI es una extensión del protocolo TLS 1.3 que permite el cifrado SNI en el mensaje "Cliente Hola" del protocolo de enlace TLS. Así es como se ve Client Hello con soporte ESNI (en lugar del SNI habitual vemos ESNI):

Cómo proteger tu sitio web público con ESNI

 Para utilizar ESNI, necesita tres componentes:

  • DNS; 
  • Atención al cliente;
  • Soporte del lado del servidor.

DNS

Debe agregar dos registros DNS: AY TXT (El registro TXT contiene la clave pública con la que el cliente puede cifrar SNI); consulte a continuación. Además, debe haber apoyo Departamento de Salud (DNS sobre HTTPS) porque los clientes disponibles (ver más abajo) no habilitan la compatibilidad con ESNI sin DoH. Esto es lógico, ya que ESNI implica cifrar el nombre del recurso al que accedemos, es decir, no tiene sentido acceder a DNS a través de UDP. Es más, el uso DNSSEC le permite protegerse contra ataques de envenenamiento de caché en este escenario.

Actualmente disponible varios proveedores de DoH, entre ellos:

CloudFlare cita (Consulte Mi navegador → SNI cifrado → Más información) que sus servidores ya admiten ESNI, es decir, para los servidores CloudFlare en el DNS tenemos al menos dos registros: A y TXT. En el siguiente ejemplo, consultamos el DNS de Google (a través de HTTPS): 

А registro:

curl 'https://dns.google.com/resolve?name=www.cloudflare.com&type=A' 
-s -H 'accept: application/dns+json'
{
  "Status": 0,
  "TC": false,
  "RD": true,
  "RA": true,
  "AD": true,
  "CD": false,
  "Question": [
    {
      "name": "www.cloudflare.com.",
      "type": 1
    }
  ],
  "Answer": [
    {
      "name": "www.cloudflare.com.",
      "type": 1,
      "TTL": 257,
      "data": "104.17.210.9"
    },
    {
      "name": "www.cloudflare.com.",
      "type": 1,
      "TTL": 257,
      "data": "104.17.209.9"
    }
  ]
}

TXT registro, la solicitud se genera según una plantilla _esni.FQDN:

curl 'https://dns.google.com/resolve?name=_esni.www.cloudflare.com&type=TXT' 
-s -H 'accept: application/dns+json'
{
  "Status": 0,
  "TC": false,
  "RD": true,
  "RA": true,
  "AD": true,
  "CD": false,
  "Question": [
    {
    "name": "_esni.www.cloudflare.com.",
    "type": 16
    }
  ],
  "Answer": [
    {
    "name": "_esni.www.cloudflare.com.",
    "type": 16,
    "TTL": 1799,
    "data": ""/wEUgUKlACQAHQAg9SiAYQ9aUseUZr47HYHvF5jkt3aZ5802eAMJPhRz1QgAAhMBAQQAAAAAXtUmAAAAAABe3Q8AAAA=""
    }
  ],
  "Comment": "Response from 2400:cb00:2049:1::a29f:209."
}

Entonces, desde una perspectiva de DNS, deberíamos usar DoH (preferiblemente con DNSSEC) y agregar dos entradas. 

Atención al cliente

Si hablamos de navegadores, entonces en este momento el soporte se implementa solo en Firefox. es Aquí hay instrucciones sobre cómo activar la compatibilidad con ESNI y DoH en Firefox. Una vez configurado el navegador, deberíamos ver algo como esto:

Cómo proteger tu sitio web público con ESNI

Enlace para comprobar el navegador.

Por supuesto, se debe utilizar TLS 1.3 para admitir ESNI, ya que ESNI es una extensión de TLS 1.3.

Con el fin de probar el backend con soporte ESNI, implementamos el cliente en go, Pero más sobre eso más adelante.

Soporte del lado del servidor

Actualmente, ESNI no es compatible con servidores web como nginx/apache, etc., ya que funcionan con TLS a través de OpenSSL/BoringSSL, que no son compatibles oficialmente con ESNI.

Por lo tanto, decidimos crear nuestro propio componente de front-end (proxy inverso ESNI), que admitiría la terminación TLS 1.3 con ESNI y el tráfico proxy HTTP(S) hacia el flujo ascendente, que no admite ESNI. Esto permite utilizar la tecnología en una infraestructura ya existente, sin cambiar los componentes principales, es decir, utilizando servidores web actuales que no son compatibles con ESNI. 

Para mayor claridad, aquí hay un diagrama:

Cómo proteger tu sitio web público con ESNI

Observo que el proxy fue diseñado con la capacidad de terminar una conexión TLS sin ESNI, para admitir clientes sin ESNI. Además, el protocolo de comunicación con upstream puede ser HTTP o HTTPS con una versión de TLS inferior a 1.3 (si upstream no es compatible con 1.3). Este esquema ofrece la máxima flexibilidad.

Implementación del soporte de ESNI en go tomamos prestado de CloudFlare. Me gustaría señalar de inmediato que la implementación en sí no es nada trivial, ya que implica cambios en la biblioteca estándar. cripto / tls y por lo tanto requiere “parchear” GORO antes del montaje.

Para generar claves ESNI utilizamos esnitool (también creación de CloudFlare). Estas claves se utilizan para el cifrado/descifrado SNI.
Probamos la compilación usando go 1.13 en Linux (Debian, Alpine) y MacOS. 

Algunas palabras sobre las características operativas.

El proxy inverso ESNI proporciona métricas en formato Prometheus, como rps, latencia ascendente y códigos de respuesta, protocolos de enlace TLS fallidos/exitosos y duración del protocolo de enlace TLS. A primera vista, esto parecía suficiente para evaluar cómo maneja el tráfico el proxy. 

También realizamos pruebas de carga antes de su uso. Resultados a continuación:

wrk -t50 -c1000 -d360s 'https://esni-rev-proxy.npw:443' --timeout 15s
Running 6m test @ https://esni-rev-proxy.npw:443
  50 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.77s     1.21s    7.20s    65.43%
    Req/Sec    13.78      8.84   140.00     83.70%
  206357 requests in 6.00m, 6.08GB read
Requests/sec:    573.07
Transfer/sec:     17.28MB 

Llevamos a cabo pruebas de carga puramente cualitativas para comparar el esquema utilizando y sin proxy inverso ESNI. "Vertimos" tráfico localmente para eliminar la "interferencia" en los componentes intermedios.

Entonces, con el soporte de ESNI y el proxy ascendente con HTTP, obtuvimos alrededor de ~550 rps de una instancia, con el consumo promedio de CPU/RAM del proxy inverso de ESNI:

  • 80% de uso de CPU (4 vCPU, hosts de 4 GB de RAM, Linux)
  • 130 MB Memoria RSS

Cómo proteger tu sitio web público con ESNI

A modo de comparación, el RPS para el mismo nginx upstream sin terminación TLS (protocolo HTTP) es ~ 1100:

wrk -t50 -c1000 -d360s 'http://lb.npw:80' –-timeout 15s
Running 6m test @ http://lb.npw:80
  50 threads and 1000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.11s     2.30s   15.00s    90.94%
    Req/Sec    23.25     13.55   282.00     79.25%
  393093 requests in 6.00m, 11.35GB read
  Socket errors: connect 0, read 0, write 0, timeout 9555
  Non-2xx or 3xx responses: 8111
Requests/sec:   1091.62
Transfer/sec:     32.27MB 

La presencia de tiempos de espera indica que hay falta de recursos (utilizamos 4 vCPU, hosts de 4 GB de RAM, Linux) y, de hecho, el RPS potencial es mayor (obtuvimos cifras de hasta 2700 RPS en recursos más potentes).

En conclusión, observo que la tecnología ESNI parece bastante prometedora. Todavía quedan muchas preguntas abiertas, por ejemplo, las cuestiones de almacenar una clave ESNI pública en el DNS y la rotación de claves ESNI; estas cuestiones se están discutiendo activamente y la última versión del borrador de ESNI (en el momento de escribir este artículo) ya está disponible. 7.

Fuente: habr.com

Añadir un comentario