Como protexer o teu sitio web público con ESNI

Ola Habr, chámome Ilya, traballo no equipo da plataforma de Exness. Desenvolvemos e implementamos os compoñentes básicos da infraestrutura que usan os nosos equipos de desenvolvemento de produtos.

Neste artigo, gustaríame compartir a miña experiencia na implementación da tecnoloxía SNI cifrada (ESNI) na infraestrutura de sitios web públicos.

Como protexer o teu sitio web público con ESNI

O uso desta tecnoloxía aumentará o nivel de seguridade cando se traballe cun sitio web público e cumprirá as normas de seguridade interna adoptadas pola Empresa.

En primeiro lugar, gustaríame sinalar que a tecnoloxía non está estandarizada e aínda está no borrador, pero CloudFlare e Mozilla xa a admiten (en borrador 01). Isto motivounos para tal experimento.

Un pouco de teoría

ESNI é unha extensión do protocolo TLS 1.3 que permite o cifrado SNI na mensaxe "Client Hello" do protocolo TLS. Este é o aspecto do Client Hello co soporte de ESNI (en lugar do SNI habitual vemos ESNI):

Como protexer o teu sitio web público con ESNI

 Para usar ESNI, necesitas tres compoñentes:

  • DNS; 
  • Apoio ao cliente;
  • Soporte do lado do servidor.

DNS

Debe engadir dous rexistros DNS - AE Txt (O rexistro TXT contén a clave pública coa que o cliente pode cifrar SNI) - ver a continuación. Ademais, debe haber apoio DoH (DNS sobre HTTPS) porque os clientes dispoñibles (ver abaixo) non activan a compatibilidade con ESNI sen DoH. Isto é lóxico, xa que ESNI implica o cifrado do nome do recurso ao que accedemos, é dicir, non ten sentido acceder a DNS a través de UDP. Ademais, o uso DNSSEC permítelle protexerse contra ataques de envelenamento da caché neste escenario.

Actualmente dispoñible varios provedores de DoH, entre eles:

CloudFlare declara (Comproba o meu navegador → SNI cifrado → Máis información) que os seus servidores xa admiten ESNI, é dicir, para os servidores de CloudFlare no DNS temos polo menos dous rexistros: A e TXT. No seguinte exemplo consultamos o DNS de Google (a través de HTTPS): 

А entrada:

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 rexistro, a solicitude xérase segundo un modelo _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."
}

Entón, desde a perspectiva de DNS, deberíamos usar DoH (preferentemente con DNSSEC) e engadir dúas entradas. 

Atención ó Cliente

Se estamos a falar de navegadores, polo momento o soporte só se implementa en FireFox. Aquí Aquí tes instrucións sobre como activar a compatibilidade con ESNI e DoH en FireFox. Despois de configurar o navegador, deberíamos ver algo así:

Como protexer o teu sitio web público con ESNI

Ligazón para comprobar o navegador.

Por suposto, debe usarse TLS 1.3 para soportar ESNI, xa que ESNI é unha extensión de TLS 1.3.

Co propósito de probar o backend co soporte de ESNI, implementamos o cliente go, Pero máis sobre iso máis tarde.

Soporte do lado do servidor

Actualmente, ESNI non é compatible con servidores web como nginx/apache, etc., xa que funcionan con TLS a través de OpenSSL/BoringSSL, que non admiten oficialmente ESNI.

Polo tanto, decidimos crear o noso propio compoñente front-end (proxy inverso ESNI), que admitiría a terminación de TLS 1.3 con ESNI e o tráfico HTTP(S) de proxy cara ascendente, que non admite ESNI. Isto permite que a tecnoloxía se utilice nunha infraestrutura xa existente, sen cambiar os compoñentes principais, é dicir, utilizando servidores web actuais que non admiten ESNI. 

Para máis claridade, aquí tes un diagrama:

Como protexer o teu sitio web público con ESNI

Observo que o proxy foi deseñado coa capacidade de finalizar unha conexión TLS sen ESNI, para soportar clientes sen ESNI. Ademais, o protocolo de comunicación con upstream pode ser HTTP ou HTTPS cunha versión TLS inferior a 1.3 (se upstream non admite 1.3). Este esquema dá a máxima flexibilidade.

Implantación do apoio ESNI en go tomamos prestado CloudFlare. Gustaríame notar de inmediato que a implementación en si non é nada trivial, xa que implica cambios na biblioteca estándar cripto/tls e, polo tanto, require un "parcheo" GOROOT antes da montaxe.

Para xerar claves ESNI usamos esnitool (tamén unha creación de CloudFlare). Estas chaves úsanse para o cifrado/descifrado SNI.
Probamos a compilación usando go 1.13 en Linux (Debian, Alpine) e MacOS. 

Algunhas palabras sobre as características operativas

O proxy inverso de ESNI ofrece métricas en formato Prometheus, como rps, códigos de resposta e latencia ascendente, apretóns de mans TLS fallidos/exitosos e duración do enlace TLS. A primeira vista, isto parecía suficiente para avaliar como o proxy xestiona o tráfico. 

Tamén realizamos probas de carga antes do 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 

Realizamos probas de carga puramente cualitativas para comparar o esquema usando o proxy inverso de ESNI e sen. "Vertemos" tráfico localmente para eliminar a "interferencia" nos compoñentes intermedios.

Entón, co soporte de ESNI e o proxy para upstream desde HTTP, recibimos preto de 550 rps dunha instancia, co consumo medio de CPU/RAM do proxy inverso de ESNI:

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

Como protexer o teu sitio web público con ESNI

A modo de comparación, o RPS para o mesmo nginx ascendente sen a terminación de TLS (protocolo HTTP) é ~ 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 

A presenza de tempo de espera indica que faltan recursos (utilizamos 4 vCPU, 4 GB de RAM, Linux), e de feito o RPS potencial é maior (recibimos cifras de ata 2700 RPS en recursos máis potentes).

En conclusión, observo que a tecnoloxía ESNI parece bastante prometedora. Aínda hai moitas preguntas abertas, por exemplo, os problemas de almacenar a clave ESNI pública no DNS e rotar as claves ESNI; estes problemas están sendo discutidos activamente e a última versión do borrador de ESNI (no momento de escribir este artigo) xa está 7.

Fonte: www.habr.com

Engadir un comentario