Como proteger seu site público com ESNI

Olá Habr, meu nome é Ilya, sou da equipe de plataforma da empresa Exness. Desenvolvemos e implementamos componentes básicos de infraestrutura utilizados pelas nossas equipes de desenvolvimento de produtos.

Neste artigo, gostaria de compartilhar a experiência de implementação da tecnologia encrypted SNI (ESNI) na infraestrutura de sites públicos.

Como proteger seu site público com ESNI

O uso dessa tecnologia permitirá aumentar o nível de segurança ao trabalhar com sites públicos e estar em conformidade com os padrões internos de segurança adotados na empresa.

Antes de tudo, gostaria de destacar que a tecnologia não é padronizada e ainda está em rascunho, no entanto, o CloudFlare e a Mozilla já a apoiam (no draft01). Isso nos motivou a realizar esse experimento.

Um pouco de teoria

ESNI é uma extensão do protocolo TLS 1.3 que permite criptografar o SNI na mensagem 'Client Hello' do handshake TLS. Veja como é a mensagem Client Hello com suporte ao ESNI (em vez do habitual SNI, vemos ESNI):

Como proteger seu site público com ESNI

 Para usar o ESNI, são necessárias três partes:

  • DNS; 
  • Suporte do cliente;
  • Suporte do servidor.

DNS

É necessário adicionar duas entradas DNS - A, quanto TXT (a entrada TXT contém a chave pública que o cliente pode usar para criptografar o SNI) - veja abaixo. Além disso, deve haver suporte para DoH (DNS over HTTPS), pois os clientes disponíveis (veja abaixo) não ativam o suporte ao ESNI sem DoH. Isso faz sentido, pois o ESNI envolve a criptografia do nome do recurso que estamos acessando, ou seja, não faz sentido consultar o DNS via UDP. Além disso, o uso de DNSSEC ajuda a se proteger contra ataques de 'cache poisoning' nesse cenário.

Atualmente, estão disponíveis vários provedores de DoH, entre eles:

CloudFlare afirma (Check My Browser → Encrypted SNI → Learn More) que seus servidores já suportam ESNI, ou seja, para os servidores do CloudFlare no DNS, temos pelo menos duas entradas - A e TXT. No exemplo abaixo, solicitamos o DNS do Google (via HTTPS): 

A 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 entrada, a consulta é formada de acordo com o padrão _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ão, do ponto de vista do DNS, devemos usar DoH (preferencialmente com DNSSEC) e adicionar dois registros. 

Suporte do lado do cliente

Se falarmos sobre navegadores, atualmente o suporte está implementado apenas no FireFox. Aqui aqui está um guia sobre como ativar o suporte a ESNI e DoH no FireFox. Depois que o navegador estiver configurado, devemos ver algo como:

Como proteger seu site público com ESNI

Link para testar o navegador.

Obviamente, para suportar ESNI, deve-se usar TLS 1.3, uma vez que ESNI é uma extensão do TLS 1.3.

Para fins de teste do backend com suporte a ESNI, implementamos um cliente em go, mas falaremos sobre isso mais tarde.

Suporte do lado do servidor

Atualmente, ESNI não é suportado por servidores web como nginx/apache, etc., pois eles operam com TLS através do OpenSSL/BoringSSL, onde ESNI não é oficialmente suportado.

Portanto, decidimos criar nosso componente front-end (proxy reverso ESNI), que suportaria a terminação de TLS 1.3 com ESNI e o proxy de tráfego HTTP(S) para um upstream que não suporta ESNI. Isso permite aplicar a tecnologia em uma infraestrutura já existente, sem alterar os componentes principais – ou seja, usar servidores web atuais que não suportam ESNI. 

Para ilustrar, aqui está um diagrama:

Como proteger seu site público com ESNI

Vale ressaltar que o proxy foi pensado com a possibilidade de terminar a conexão TLS sem ESNI, para apoiar clientes sem ESNI. Além disso, o protocolo de comunicação com o upstream pode ser tanto HTTP quanto HTTPS com uma versão de TLS inferior a 1.3 (se o upstream não suportar 1.3). Essa configuração oferece máxima flexibilidade.

A implementação do suporte a ESNI em go foi inspirada em CloudFlare. Desde já, destaco que a implementação em si é bastante não trivial, pois implica em alterações na biblioteca padrão crypto/tls e, portanto, requer "patching" GOROOT antes da compilação.

Para a geração de chaves ESNI, utilizamos esnitool (também uma criação da CloudFlare). Essas chaves são usadas para criptografar/descriptografar SNI.
Testamos a compilação usando go 1.13 em Linux (Debian, Alpine) e MacOS. 

Algumas palavras sobre características operacionais

O proxy reverso ESNI fornece métricas no formato Prometheus, como rps, latência de upstream e códigos de resposta, handshakes TLS falhados/sucessos e duração do handshake TLS. À primeira vista, isso parecia suficiente para avaliar como o proxy lida com o tráfego. 

Antes de usar, também realizamos testes de carga. Os resultados estão abaixo:

wrk -t50 -c1000 -d360s 'https://esni-rev-proxy.npw:443' --timeout 15s
Executando teste de 6m @ https://esni-rev-proxy.npw:443
  50 threads e 1000 conexões
  Estatísticas da Thread   Média     Desvio Padrão     Máx   +/- Desvio Padrão
    Latência     1.77s     1.21s    7.20s    65.43%
    Req/Sec    13.78      8.84    140.00     83.70%
  206357 solicitações em 6.00m, 6.08GB lidos
Solicitações/sec:    573.07
Transferência/sec:     17.28MB 

Realizamos os testes de carga de forma qualitativa, para comparar os esquemas usando o proxy reverso ESNI e sem ele. Nós ‘injetamos’ tráfego localmente para excluir ‘interferências’ em componentes intermediários.

Assim, com suporte a ESNI e proxy para upstream via HTTP, conseguimos cerca de ~ 550 rps a partir de uma instância, enquanto o consumo médio de CPU/RAM do proxy reverso ESNI foi:

  • 80% de Uso de CPU (hosts com 4 vCPU, 4 GB RAM, Linux)
  • 130 MB Mem RSS

Como proteger seu site público com ESNI

Para comparação, o RPS para o mesmo upstream nginx sem terminação TLS (protocolo HTTP) é ~ 1100:

wrk -t50 -c1000 -d360s 'http://lb.npw:80' --timeout 15s
Executando teste de 6m @ http://lb.npw:80
  50 threads e 1000 conexões
  Estatísticas da Thread   Média     Desvio Padrão     Máx   +/- Desvio Padrão
    Latência     1.11s     2.30s    15.00s    90.94%
    Req/Sec    23.25     13.55    282.00     79.25%
  393093 solicitações em 6.00m, 11.35GB lidos
  Erros de socket: conectar 0, ler 0, escrever 0, timeout 9555
  Respostas não-2xx ou 3xx: 8111
Solicitações/sec:   1091.62
Transferência/sec:     32.27MB 

A presença de timeouts indica que há falta de recursos (usamos hosts com 4 vCPU, 4 GB RAM, Linux), e na verdade o RPS potencial é maior (obtivemos números de até 2700 RPS em recursos mais poderosos).

Em conclusão, vale ressaltar que a tecnologia ESNI parece bastante promissora. Há ainda muitas perguntas em aberto, como questões sobre o armazenamento da chave pública ESNI no DNS e rotação de chaves ESNI – essas questões estão sendo ativamente discutidas, e a última versão do rascunho (no momento da redação) do ESNI já 7.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster