Hur du skyddar din offentliga webbplats med ESNI

Hej Habr, jag heter Ilya, jag arbetar i plattformsteamet på Exness. Vi utvecklar och implementerar kärninfrastrukturkomponenterna som våra produktutvecklingsteam använder.

I den här artikeln vill jag dela med mig av min erfarenhet av att implementera krypterad SNI-teknik (ESNI) i infrastrukturen på offentliga webbplatser.

Hur du skyddar din offentliga webbplats med ESNI

Användningen av denna teknik kommer att öka säkerhetsnivån när man arbetar med en offentlig webbplats och följa interna säkerhetsstandarder som antagits av företaget.

Först och främst vill jag påpeka att tekniken inte är standardiserad och fortfarande finns i utkastet, men CloudFlare och Mozilla stöder det redan (i utkast 01). Detta motiverade oss till ett sådant experiment.

Lite teori

ESNI är ett tillägg till TLS 1.3-protokollet som tillåter SNI-kryptering i TLS-handskakningsmeddelandet "Client Hello". Så här ser Client Hello ut med ESNI-stöd (istället för den vanliga SNI ser vi ESNI):

Hur du skyddar din offentliga webbplats med ESNI

 För att använda ESNI behöver du tre komponenter:

  • DNS; 
  • Kundsupport;
  • Support på serversidan.

DNS

Du måste lägga till två DNS-poster – AOch TXT (TXT-posten innehåller den publika nyckeln med vilken klienten kan kryptera SNI) - se nedan. Dessutom måste det finnas stöd DoH (DNS över HTTPS) eftersom tillgängliga klienter (se nedan) inte aktiverar ESNI-stöd utan DoH. Detta är logiskt, eftersom ESNI innebär kryptering av namnet på resursen vi kommer åt, det vill säga det är ingen mening att komma åt DNS över UDP. Dessutom användningen DNSSEC låter dig skydda mot cacheförgiftningsattacker i detta scenario.

Tillgänglig för tillfället flera DoH-leverantörer, bland dem:

CloudFlare stater (Kontrollera Min webbläsare → Krypterad SNI → Läs mer) att deras servrar redan stöder ESNI, det vill säga för CloudFlare-servrar i DNS har vi minst två poster - A och TXT. I exemplet nedan frågar vi Google DNS (över HTTPS): 

А inträde:

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 post, förfrågan genereras enligt en mall _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."
}

Så ur ett DNS-perspektiv bör vi använda DoH (helst med DNSSEC) och lägga till två poster. 

Kundsupport

Om vi ​​pratar om webbläsare, så för tillfället support implementeras endast i FireFox. Här Här finns instruktioner om hur du aktiverar ESNI- och DoH-stöd i FireFox. Efter att webbläsaren har konfigurerats bör vi se något i stil med detta:

Hur du skyddar din offentliga webbplats med ESNI

Länk för att kontrollera webbläsaren.

Naturligtvis måste TLS 1.3 användas för att stödja ESNI, eftersom ESNI är en förlängning av TLS 1.3.

I syfte att testa backend med ESNI-stöd implementerade vi klienten på go, Men mer om det senare.

Support på serversidan

För närvarande stöds inte ESNI av webbservrar som nginx/apache, etc., eftersom de arbetar med TLS via OpenSSL/BoringSSL, som inte officiellt stöder ESNI.

Därför beslutade vi att skapa vår egen front-end-komponent (ESNI omvänd proxy), som skulle stödja TLS 1.3-terminering med ESNI och proxy HTTP(S)-trafik till uppströms, som inte stöder ESNI. Detta gör att tekniken kan användas i en redan befintlig infrastruktur, utan att huvudkomponenterna ändras – det vill säga att använda nuvarande webbservrar som inte stöder ESNI. 

För tydlighetens skull, här är ett diagram:

Hur du skyddar din offentliga webbplats med ESNI

Jag noterar att proxyn utformades med möjligheten att avsluta en TLS-anslutning utan ESNI, för att stödja klienter utan ESNI. Dessutom kan kommunikationsprotokollet med uppströms vara antingen HTTP eller HTTPS med en TLS-version lägre än 1.3 (om uppströms inte stöder 1.3). Detta schema ger maximal flexibilitet.

Implementering av ESNI-stöd på go vi lånade av CloudFlare. Jag skulle genast vilja notera att själva implementeringen är ganska icke-trivial, eftersom den innebär förändringar i standardbiblioteket krypto/tls och kräver därför "lappning" GOROOT före montering.

För att generera ESNI-nycklar använde vi esnitool (också idén till CloudFlare). Dessa nycklar används för SNI-kryptering/dekryptering.
Vi testade bygget med go 1.13 på Linux (Debian, Alpine) och MacOS. 

Några ord om operativa funktioner

ESNI omvänd proxy tillhandahåller mätvärden i Prometheus-format, såsom rps, uppströms latens och svarskoder, misslyckade/lyckade TLS-handskakningar och TLS-handskakningsvaraktighet. Vid första anblicken verkade detta tillräckligt för att utvärdera hur proxyn hanterar trafik. 

Vi utförde även belastningstester före användning. Resultat nedan:

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 

Vi utförde rent kvalitativ belastningstestning för att jämföra schemat med ESNI omvänd proxy och utan. Vi "hällde" trafik lokalt för att eliminera "störningar" i mellanliggande komponenter.

Så, med ESNI-stöd och proxy till uppströms från HTTP, fick vi cirka ~550 rps från en instans, med den genomsnittliga CPU/RAM-förbrukningen för ESNI omvänd proxy:

  • 80 % CPU-användning (4 vCPU, 4 GB RAM-värdar, Linux)
  • 130 MB Mem RSS

Hur du skyddar din offentliga webbplats med ESNI

Som jämförelse är RPS för samma nginx uppströms utan TLS (HTTP-protokoll)-avslutning ~ 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 

Förekomsten av timeouts indikerar att det finns en brist på resurser (vi använde 4 vCPU: er, 4 GB RAM-värdar, Linux), och faktiskt är den potentiella RPS högre (vi fick siffror på upp till 2700 RPS på mer kraftfulla resurser).

Avslutningsvis noterar jag att ESNI-tekniken ser ganska lovande ut. Det finns fortfarande många öppna frågor, till exempel frågorna om lagring av den offentliga ESNI-nyckeln i DNS och roterande ESNI-nycklar - dessa frågor diskuteras aktivt, och den senaste versionen av ESNI-utkastet (i skrivande stund) är redan 7.

Källa: will.com

Lägg en kommentar