Hur du skyddar din offentliga webbplats med ESNI

Hej Habr, jag heter Ilya och jobbar i plattformsteamet på Exness. Vi utvecklar och implementerar grundläggande infrastrukturkomponenter som våra produktutvecklingsteam använder.

I den här artikeln vill jag dela med mig av mina erfarenheter av att implementera krypterad SNI-teknik (ESNI) i infrastrukturen för offentliga webbplatser.

Hur du skyddar din offentliga webbplats med ESNI

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

Först och främst vill jag påpeka att tekniken inte är standardiserad och fortfarande är i utkastfasen, men CloudFlare och Mozilla stöder den redan (i utkast01). Det är detta som motiverade oss att genomföra ett sådant experiment.

Lite teori

ESNI – är en utökning av TLS 1.3-protokollet som låter dig kryptera SNI i TLS-handskakningsmeddelandet "Client Hello". Så här ser ett Client Hello med ESNI-stöd ut (istället för det vanliga SNI ser vi ESNI):

Hur du skyddar din offentliga webbplats med ESNI

 För att använda ESNI krävs tre komponenter:

  • DNS; 
  • Kundsupport;
  • Serversidesupport.

DNS

Du behöver lägga till två DNS-poster – AOch TXT (TXT-posten innehåller den publika nyckeln som klienten kan kryptera SNI med) – se nedan. Dessutom bör det finnas stöd för DoH (DNS över HTTPS), eftersom de tillgängliga klienterna (se nedan) inte aktiverar ESNI-stöd utan DoH. Detta är logiskt, eftersom ESNI innebär kryptering av resursnamnet vi åtkommer, d.v.s. det är meningslöst att komma åt DNS via UDP. Dessutom, att använda DNSSEC låter dig skydda mot cache-förgiftningsattacker i det här scenariot.

För närvarande tillgänglig 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 att för CloudFlare-servrar i DNS har vi minst två poster - A och TXT. I exemplet nedan begär vi Google DNS (via 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, begäran 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 närvarande stödet implementeras endast i FireFox. Här Instruktioner om hur man aktiverar stöd för ESNI och DoH i FireFox finns. När webbläsaren har konfigurerats bör vi se något liknande:

Hur du skyddar din offentliga webbplats med ESNI

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

Självklart måste TLS 1.3 användas för att stödja ESNI, eftersom ESNI är en utökning av TLS 1.3.

För att testa backend-systemet med ESNI-stöd implementerade vi en klient på go, Men mer om det senare.

Serversidesupport

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

Därför bestämde vi oss för att skapa vår egen frontend-komponent (ESNI reverse proxy), som skulle stödja TLS 1.3-terminering med ESNI och proxya HTTP(S)-trafik till uppströms, vilket inte stöder ESNI. Detta möjliggör användning av tekniken i en redan befintlig infrastruktur, utan att ändra huvudkomponenterna – det vill säga att använda befintliga webbservrar som inte stöder ESNI. 

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

Hur du skyddar din offentliga webbplats med ESNI

Jag vill påpeka att proxyn utformades med möjligheten att avsluta en TLS-anslutning utan ESNI, för att stödja klienter utan ESNI. Dessutom kan protokollet för kommunikation med uppströmsleverantören vara antingen HTTP eller HTTPS med en TLS-version under 1.3 (om uppströmsleverantören inte stöder 1.3). Detta schema ger maximal flexibilitet.

Implementering av ESNI-stöd på go vi lånade från CloudFlareJag vill omedelbart 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 "patchning" GOROOT före montering.

För att generera ESNI-nycklar använde vi esnitool (också en CloudFlare-skapelse). Dessa nycklar används för att kryptera/dekryptera SNI.
Vi testade bygget med go 1.13 på Linux (Debian, Alpine) och MacOS. 

Några ord om operativa funktioner

ESNI:s omvända proxy tillhandahåller mätvärden i Prometheus-format, såsom rps, uppströmslatens och svarskoder, misslyckade/lyckade TLS-handskakningar och TLS-handskakningens varaktighet. Vid första anblicken verkade detta tillräckligt för att bedöma hur proxyn hanterar trafik. 

Vi utförde även belastningstester före användning. Resultaten är 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 kvalitativa belastningstester för att jämföra schemat med och utan ESNI reverse proxy. Vi "hällde" trafik lokalt för att eliminera "störningar" i mellanliggande komponenter.

Så, med ESNI-stöd och uppströmsproxy med HTTP, fick vi runt ~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 minnes-RSS

Hur du skyddar din offentliga webbplats med ESNI

Som jämförelse är RPS för samma nginx uppströms utan TLS-terminering (HTTP-protokoll) ~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 saknas resurser (vi använde 4 vCPU, 4 GB RAM-värdar, Linux), och faktum är att den potentiella RPS är högre (vi fick siffror på upp till 2700 RPS på kraftfullare resurser).

Sammanfattningsvis vill jag påpeka att att ESNI-tekniken ser ganska lovande ut. Det finns fortfarande många öppna frågor, till exempel frågorna om att lagra den publika ESNI-nyckeln i DNS och rotera ESNI-nycklar – dessa frågor diskuteras aktivt, och den senaste versionen av utkastet (i skrivande stund) av ESNI finns redan 7.

Källa: will.com

Köp pålitlig hosting för webbplatser med DDoS-skydd, VPS VDS-servrar 🔥 Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster