Come proteggere il tuo sito web pubblico con ESNI

Ciao Habr, mi chiamo Ilya, lavoro nel team della piattaforma di Exness. Sviluppiamo e implementiamo i componenti principali dell'infrastruttura utilizzati dai nostri team di sviluppo prodotto.

In questo articolo vorrei condividere la mia esperienza nell'implementazione della tecnologia SNI crittografata (ESNI) nell'infrastruttura dei siti Web pubblici.

Come proteggere il tuo sito web pubblico con ESNI

L'uso di questa tecnologia aumenterà il livello di sicurezza quando si lavora con un sito Web pubblico e sarà conforme agli standard di sicurezza interni adottati dalla Società.

Innanzitutto vorrei sottolineare che la tecnologia non è standardizzata ed è ancora in bozza, ma CloudFlare e Mozilla già la supportano (in bozza01). Questo ci ha motivato per un simile esperimento.

Un po 'di teoria

ESNI è un'estensione del protocollo TLS 1.3 che consente la crittografia SNI nel messaggio "Client Hello" dell'handshake TLS. Ecco come appare Client Hello con il supporto ESNI (al posto del solito SNI vediamo ESNI):

Come proteggere il tuo sito web pubblico con ESNI

 Per utilizzare ESNI sono necessari tre componenti:

  • DNS; 
  • Supporto al cliente;
  • Supporto lato server.

DNS

Devi aggiungere due record DNS: AE TXT (Il record TXT contiene la chiave pubblica con cui il client può crittografare SNI) - vedere di seguito. Inoltre, deve esserci supporto DoH (DNS su HTTPS) perché i client disponibili (vedi sotto) non abilitano il supporto ESNI senza DoH. Questo è logico, poiché ESNI implica la crittografia del nome della risorsa a cui accediamo, ovvero non ha senso accedere al DNS tramite UDP. Inoltre, l'uso DNSSEC consente di proteggersi dagli attacchi di avvelenamento della cache in questo scenario.

Attualmente disponibile diversi fornitori DoH, tra loro:

CloudFlare заявляет (Controlla il mio browser → SNI crittografato → Ulteriori informazioni) che i loro server supportano già ESNI, ovvero per i server CloudFlare nel DNS abbiamo almeno due record: A e TXT. Nell'esempio seguente interroghiamo Google DNS (su HTTPS): 

А Record:

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 record, la richiesta viene generata secondo un modello _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."
}

Quindi, dal punto di vista DNS, dovremmo utilizzare DoH (preferibilmente con DNSSEC) e aggiungere due voci. 

Servizio Clienti

Se stiamo parlando di browser, al momento il supporto è implementato solo in FireFox. Qui Ecco le istruzioni su come attivare il supporto ESNI e DoH in FireFox. Dopo aver configurato il browser, dovremmo vedere qualcosa di simile a questo:

Come proteggere il tuo sito web pubblico con ESNI

Collegamento per controllare il browser.

Naturalmente, TLS 1.3 deve essere utilizzato per supportare ESNI, poiché ESNI è un'estensione di TLS 1.3.

Allo scopo di testare il backend con il supporto ESNI, abbiamo implementato il client su go, Ma ne parleremo più avanti.

Supporto lato server

Attualmente, ESNI non è supportato da server web come nginx/apache, ecc., poiché funzionano con TLS tramite OpenSSL/BoringSSL, che non supportano ufficialmente ESNI.

Pertanto, abbiamo deciso di creare il nostro componente front-end (proxy inverso ESNI), che supporterebbe la terminazione TLS 1.3 con ESNI e il traffico proxy HTTP(S) verso l'upstream, che non supporta ESNI. Ciò consente di utilizzare la tecnologia in un'infrastruttura già esistente, senza modificare i componenti principali, ovvero utilizzando gli attuali server Web che non supportano ESNI. 

Per chiarezza ecco uno schema:

Come proteggere il tuo sito web pubblico con ESNI

Prendo atto che il proxy è stato progettato con la possibilità di terminare una connessione TLS senza ESNI, per supportare i client senza ESNI. Inoltre, il protocollo di comunicazione con upstream può essere HTTP o HTTPS con una versione TLS inferiore a 1.3 (se upstream non supporta 1.3). Questo schema offre la massima flessibilità.

Attuazione del supporto ESNI su go abbiamo preso in prestito da CloudFlare. Vorrei subito notare che l'implementazione in sé non è affatto banale, poiché comporta modifiche nella libreria standard cripto/TLS e quindi richiede un “patch” GORO prima del montaggio.

Per generare le chiavi ESNI abbiamo utilizzato esnitool (anche il frutto di CloudFlare). Queste chiavi vengono utilizzate per la crittografia/decrittografia SNI.
Abbiamo testato la build utilizzando go 1.13 su Linux (Debian, Alpine) e MacOS. 

Qualche parola sulle caratteristiche operative

Il proxy inverso ESNI fornisce metriche in formato Prometheus, come rps, latenza upstream e codici di risposta, handshake TLS non riusciti/riusciti e durata dell'handshake TLS. A prima vista, questo sembrava sufficiente per valutare come il proxy gestisce il traffico. 

Abbiamo anche eseguito test di carico prima dell'uso. Risultati di seguito:

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 

Abbiamo effettuato test di carico puramente qualitativi per confrontare lo schema utilizzando il proxy inverso ESNI e senza. Abbiamo "versato" il traffico localmente per eliminare le "interferenze" nei componenti intermedi.

Quindi, con il supporto ESNI e il proxy upstream da HTTP, abbiamo ottenuto circa 550 rps da un'istanza, con il consumo medio di CPU/RAM del proxy inverso ESNI:

  • Utilizzo della CPU all'80% (4 vCPU, host RAM da 4 GB, Linux)
  • RSS di memoria da 130 MB

Come proteggere il tuo sito web pubblico con ESNI

Per fare un confronto, l'RPS per lo stesso nginx upstream senza terminazione TLS (protocollo 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 

La presenza di timeout indica che mancano risorse (abbiamo utilizzato 4 vCPU, host 4 GB RAM, Linux), ed infatti il ​​potenziale RPS è più alto (abbiamo ricevuto cifre fino a 2700 RPS su risorse più potenti).

In conclusione, prendo atto che la tecnologia ESNI sembra piuttosto promettente. Ci sono ancora molte domande aperte, ad esempio le questioni relative alla memorizzazione della chiave ESNI pubblica nel DNS e alla rotazione delle chiavi ESNI: queste questioni vengono discusse attivamente e l'ultima versione della bozza ESNI (al momento in cui scrivo) è già 7.

Fonte: habr.com

Aggiungi un commento