Здраво Хабр, јас се викам Илја, работам во тимот на платформата во Exness. Ние ги развиваме и имплементираме основните инфраструктурни компоненти што ги користат нашите тимови за развој на производи.
Во оваа статија, би сакал да го споделам моето искуство за имплементирање на шифрирана технологија SNI (ESNI) во инфраструктурата на јавните веб-страници.

Употребата на оваа технологија ќе го зголеми нивото на безбедност при работа со јавна веб-страница и ќе биде во согласност со стандардите за внатрешна безбедност усвоени од Компанијата.
Најпрво, би сакал да истакнам дека технологијата не е стандардизирана и сè уште е во нацрт, но CloudFlare и Mozilla веќе ја поддржуваат (во ). Ова не мотивираше за ваков експеримент.
Малку теорија
ЕСНИ е продолжување на протоколот TLS 1.3 што овозможува шифрирање на SNI во пораката „Клиент Здраво“ за ракување TLS. Еве како изгледа Client Hello со поддршка на ESNI (наместо вообичаениот SNI го гледаме ESNI):

За да користите ESNI, потребни ви се три компоненти:
- DNS;
- Поддршка на клиентите;
- Поддршка од страна на серверот.
DNS
Треба да додадете два записи DNS - AИ TXT (Записот TXT го содржи јавниот клуч со кој клиентот може да го шифрира SNI) - видете подолу. Покрај тоа, мора да има поддршка Д. (DNS преку HTTPS) бидејќи достапните клиенти (види подолу) не овозможуваат поддршка за ESNI без DoH. Ова е логично, бидејќи ESNI подразбира шифрирање на името на ресурсот до кој пристапуваме, односно нема смисла да се пристапува до DNS преку UDP. Покрај тоа, употребата ви овозможува да се заштитите од напади на труење со кеш во ова сценарио.
Моментално достапни , меѓу нив:
CloudFlare (Проверете My Browser → Encrypted SNI → Learn More) дека нивните сервери веќе поддржуваат ESNI, односно за CloudFlare серверите во DNS имаме најмалку два записи - A и TXT. Во примерот подолу бараме Google DNS (преку HTTPS):
А влез:
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 запис, барањето се генерира според шаблон _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."
}
Значи, од перспектива на DNS, треба да користиме DoH (по можност со DNSSEC) и да додадеме два записи.
Поддршка за корисници
Ако зборуваме за прелистувачи, тогаш во моментот . Еве инструкции како да ја активирате поддршката за ESNI и DoH во FireFox. Откако ќе се конфигурира прелистувачот, треба да видиме нешто како ова:

за да го проверите прелистувачот.
Се разбира, TLS 1.3 мора да се користи за поддршка на ESNI, бидејќи ESNI е продолжение на TLS 1.3.
За да го тестираме задниот дел со поддршка на ESNI, го имплементиравме клиентот на go, Но повеќе за тоа подоцна.
Поддршка од страна на серверот
Во моментов, ESNI не е поддржан од веб-сервери како nginx/apache, итн., бидејќи тие работат со TLS преку OpenSSL/BoringSSL, кои официјално не поддржуваат ESNI.
Затоа, решивме да создадеме наша сопствена предна компонента (ESNI обратен прокси), која ќе поддржува TLS 1.3 завршување со ESNI и прокси HTTP(S) сообраќај до горе, што не поддржува ESNI. Ова овозможува технологијата да се користи во веќе постоечка инфраструктура, без промена на главните компоненти - односно користење на тековни веб-сервери кои не поддржуваат ESNI.
За јасност, еве дијаграм:

Забележувам дека проксито е дизајнирано со можност за прекинување на TLS конекција без ESNI, за поддршка на клиенти без ESNI. Исто така, протоколот за комуникација со upstream може да биде или HTTP или HTTPS со TLS верзија пониска од 1.3 (ако upstream не поддржува 1.3). Оваа шема дава максимална флексибилност.
Имплементација на поддршката ESNI на go позајмивме од . Би сакал веднаш да забележам дека самата имплементација е сосема нетривијална, бидејќи вклучува промени во стандардната библиотека крипто/тлс и затоа бара „крпење“ ГОРОТ пред склопување.
За да генерираме ESNI клучеви што ги користевме (исто така замисла на CloudFlare). Овие клучеви се користат за шифрирање/декрипција на SNI.
Ја тестиравме конструкцијата користејќи go 1.13 на Linux (Debian, Alpine) и MacOS.
Неколку зборови за оперативните карактеристики
Обратно прокси ESNI обезбедува метрика во формат Prometheus, како што се rps, шифри за доцнење и одговор нагоре, неуспешни/успешни TLS ракувања и времетраење на TLS ракување. На прв поглед, ова се чинеше доволно за да се оцени како посредникот се справува со сообраќајот.
Исто така, извршивме тестирање на оптоварување пред употреба. Резултати подолу:
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
Спроведовме чисто квалитативно тестирање на оптоварување за да ја споредиме шемата користејќи обратен прокси ESNI и без. Локално го „истуривме“ сообраќајот за да ги елиминираме „пречките“ во меѓукомпонентите.
Така, со поддршка на ESNI и прокси до спротиводно од HTTP, добивме околу ~ 550 вртежи во секунда од еден пример, со просечна потрошувачка на CPU/RAM на ESNI обратен прокси:
- 80% користење на процесорот (4 vCPU, 4 GB RAM хостови, Linux)
- 130 MB Mem RSS

За споредба, RPS за истиот nginx upstream без TLS (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
Присуството на тајмаути укажува на недостаток на ресурси (користевме 4 vCPU, 4 GB RAM хостови, Linux), и всушност потенцијалниот RPS е поголем (добивме бројки до 2700 RPS на помоќни ресурси).
Како заклучок, забележувам дека ESNI технологијата изгледа доста ветувачки. Сè уште има многу отворени прашања, на пример, прашањата за складирање на јавниот клуч ESNI во DNS и ротирачките клучеви ESNI - овие прашања активно се дискутираат, а најновата верзија на нацртот на ESNI (во моментот на пишување) е веќе .
Извор: www.habr.com
