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.
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
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):
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
Tillgänglig för tillfället
CloudFlare
А 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
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:
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
För att generera ESNI-nycklar använde vi
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
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
Källa: will.com