Здравейте Habr, казвам се Иля, работя в екипа на платформата в Exness. Ние разработваме и внедряваме основните инфраструктурни компоненти, които нашите екипи за разработка на продукти използват.
В тази статия бих искал да споделя опита си от внедряването на криптирана SNI (ESNI) технология в инфраструктурата на публични уебсайтове.

Използването на тази технология ще повиши нивото на сигурност при работа с публичен уебсайт и ще спазва вътрешните стандарти за сигурност, приети от Компанията.
Първо, бих искал да отбележа, че технологията не е стандартизирана и все още е в процес на разработка, но CloudFlare и Mozilla вече я поддържат (в ). Това ни мотивира за такъв експеримент.
Малко теория
ЕСНИ е разширение на протокола TLS 1.3, което позволява SNI криптиране в TLS съобщението за ръкостискане „Client Hello“. Ето как изглежда Client Hello с поддръжка на ESNI (вместо обичайния SNI виждаме ESNI):

За да използвате ESNI, имате нужда от три компонента:
- DNS;
- Поддръжка на клиенти;
- Поддръжка от страна на сървъра.
DNS
Трябва да добавите два DNS записа – AИ TXT (TXT записът съдържа публичния ключ, с който клиентът може да шифрова SNI) - вижте по-долу. Освен това трябва да има подкрепа DoH (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 взехме назаем от . Бих искал веднага да отбележа, че самата реализация е доста нетривиална, тъй като включва промени в стандартната библиотека крипто/tls и следователно изисква „кръпка“ GOROOT преди монтажа.
За генериране на 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 rps от един екземпляр, със средната консумация на CPU/RAM на ESNI обратен прокси:
- 80% използване на процесора (4 виртуални процесора, 4 GB RAM хостове, Linux)
- 130 MB Mem RSS

За сравнение, RPS за същия nginx нагоре по веригата без 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
