Cum să vă protejați site-ul public cu ESNI

Bună Habr, mă numesc Ilya, lucrez în echipa platformei de la Exness. Dezvoltăm și implementăm componentele de bază ale infrastructurii pe care le folosesc echipele noastre de dezvoltare a produselor.

În acest articol, aș dori să împărtășesc experiența mea de implementare a tehnologiei SNI criptate (ESNI) în infrastructura site-urilor web publice.

Cum să vă protejați site-ul public cu ESNI

Utilizarea acestei tehnologii va crește nivelul de securitate atunci când lucrați cu un site web public și va respecta standardele de securitate internă adoptate de Companie.

În primul rând, aș dori să subliniez că tehnologia nu este standardizată și este încă în proiect, dar CloudFlare și Mozilla o susțin deja (în proiect 01). Acest lucru ne-a motivat pentru un astfel de experiment.

Un pic de teorie

ESNI este o extensie a protocolului TLS 1.3 care permite criptarea SNI în mesajul de strângere de mână TLS „Client Hello”. Iată cum arată Client Hello cu suport ESNI (în loc de SNI obișnuit vedem ESNI):

Cum să vă protejați site-ul public cu ESNI

 Pentru a utiliza ESNI, aveți nevoie de trei componente:

  • DNS; 
  • Suport pentru clienți;
  • Suport pe partea serverului.

DNS

Trebuie să adăugați două înregistrări DNS - Ași TXT (Înregistrarea TXT conține cheia publică cu care clientul poate cripta SNI) - vezi mai jos. În plus, trebuie să existe sprijin DOH (DNS prin HTTPS) deoarece clienții disponibili (vezi mai jos) nu activează suportul ESNI fără DoH. Acest lucru este logic, deoarece ESNI implică criptarea numelui resursei pe care o accesăm, adică nu are sens să accesăm DNS prin UDP. Mai mult, utilizarea DNSSEC vă permite să vă protejați împotriva atacurilor de otrăvire a cache-ului în acest scenariu.

Disponibil acum mai mulți furnizori DoH, printre ei:

Cloudflare statele (Verificați Browserul meu → SNI criptat → Aflați mai multe) că serverele lor acceptă deja ESNI, adică pentru serverele CloudFlare din DNS avem cel puțin două înregistrări - A și TXT. În exemplul de mai jos interogăm Google DNS (prin HTTPS): 

А intrare:

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 înregistrare, cererea este generată conform unui șablon _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."
}

Deci, din perspectiva DNS, ar trebui să folosim DoH (de preferință cu DNSSEC) și să adăugăm două intrări. 

Relații Clienți

Dacă vorbim despre browsere, atunci în acest moment suportul este implementat numai în FireFox. Aici Iată instrucțiuni despre cum să activați suportul ESNI și DoH în FireFox. Odată ce browserul este configurat, ar trebui să vedem ceva de genul acesta:

Cum să vă protejați site-ul public cu ESNI

Legătură pentru a verifica browserul.

Desigur, TLS 1.3 trebuie utilizat pentru a susține ESNI, deoarece ESNI este o extensie a TLS 1.3.

În scopul testării backend-ului cu suport ESNI, am implementat clientul pe go, Dar mai multe despre asta mai târziu.

Suport pe partea serverului

În prezent, ESNI nu este acceptat de servere web precum nginx/apache etc., deoarece funcționează cu TLS prin OpenSSL/BoringSSL, care nu acceptă oficial ESNI.

Prin urmare, am decis să creăm propria noastră componentă front-end (ESNI reverse proxy), care ar suporta terminarea TLS 1.3 cu ESNI și trafic HTTP(S) proxy către upstream, care nu acceptă ESNI. Acest lucru permite ca tehnologia să fie utilizată într-o infrastructură deja existentă, fără a modifica componentele principale - adică folosind servere web actuale care nu acceptă ESNI. 

Pentru claritate, iată o diagramă:

Cum să vă protejați site-ul public cu ESNI

Observ că proxy-ul a fost conceput cu capacitatea de a termina o conexiune TLS fără ESNI, pentru a sprijini clienții fără ESNI. De asemenea, protocolul de comunicare cu upstream poate fi fie HTTP, fie HTTPS cu o versiune TLS mai mică decât 1.3 (dacă upstream nu acceptă 1.3). Această schemă oferă flexibilitate maximă.

Implementarea sprijinului ESNI pe go am împrumutat de la Cloudflare. Aș dori să notez imediat că implementarea în sine este destul de netrivială, deoarece implică modificări în biblioteca standard cripto/tls și, prin urmare, necesită „patching” GOROOT înainte de asamblare.

Pentru a genera cheile ESNI am folosit esnitool (de asemenea, creația CloudFlare). Aceste chei sunt folosite pentru criptarea/decriptarea SNI.
Am testat construcția folosind go 1.13 pe Linux (Debian, Alpine) și MacOS. 

Câteva cuvinte despre caracteristicile operaționale

Proxy-ul invers ESNI oferă valori în format Prometheus, cum ar fi rps, coduri de latență și răspuns în amonte, strângeri de mână TLS nereușite/reușite și durata strângerii de mână TLS. La prima vedere, acest lucru părea suficient pentru a evalua modul în care proxy-ul gestionează traficul. 

De asemenea, am efectuat teste de sarcină înainte de utilizare. Rezultate mai jos:

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 

Am efectuat teste de încărcare pur calitative pentru a compara schema folosind proxy invers ESNI și fără. Am „turnat” trafic local pentru a elimina „interferența” în componentele intermediare.

Deci, cu suport ESNI și proxy upstream de la HTTP, am primit aproximativ 550 rps dintr-o singură instanță, cu consumul mediu CPU/RAM al proxy-ului invers ESNI:

  • 80% utilizare CPU (4 vCPU, 4 GB RAM gazde, Linux)
  • 130 MB Mem RSS

Cum să vă protejați site-ul public cu ESNI

Pentru comparație, RPS pentru același nginx în amonte fără terminarea TLS (protocol HTTP) este de ~ 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 

Prezența timeout-urilor indică lipsa resurselor (am folosit 4 vCPU, 4 GB RAM gazde, Linux), iar de fapt potențialul RPS este mai mare (am primit cifre de până la 2700 RPS pe resurse mai puternice).

In concluzie, notez că tehnologia ESNI pare destul de promițătoare. Există încă multe întrebări deschise, de exemplu, problemele stocării cheii ESNI publice în DNS și rotația cheilor ESNI - aceste probleme sunt discutate activ, iar cea mai recentă versiune a proiectului ESNI (la momentul scrierii) este deja 7.

Sursa: www.habr.com

Adauga un comentariu