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

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

За да се използва ESNI, са необходими три компонента:
- DNS;
- Поддръжка от клиента;
- Поддръжка от сървъра.
DNS
Необходимо е да добавите две DNS записа – A, и TXT (TXT записа съдържа публичния ключ, с който клиентът може да шифрова SNI) – вижте по-долу. Освен това, трябва да има поддръжка DoH (DNS over HTTPS), тъй като наличните клиенти (вижте по-долу) не активират поддръжката на ESNI без DoH. Това е логично, тъй като ESNI предвижда шифроване на името на ресурса, до който се обръщаме, следователно е безсмислено да се обръщаме към DNS по UDP. Освен това, използването на позволява защита от атаки за „cache poisoning“ в този сценарий.
В момента са налични , сред които:
CloudFlare (Check My Browser → Encrypted SNI → Learn More), което означава, че техните сървъри вече поддържат ESNI, т.е. за сървърите на CloudFlare в DNS имаме поне два записа – A и TXT. В примера по-долу запитваме Google DNS (over 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. След като браузърът е настроен, трябва да видим нещо подобно:

за проверка на браузъра.
Разбира се, за подкрепа на ESNI трябва да се използва TLS 1.3, тъй като ESNI е разширение на TLS 1.3.
За целите на тестване на бэкента с подкрепа на ESNI разработихме клиент на go, но за това малко по-късно.
Подкрепа от страна на сървера
В момента ESNI не се поддържа от уеб сървъри като nginx/apache и т.н., тъй като те работят с TLS през OpenSSL/BoringSSL, в които ESNI официално не се поддържа.
Затова решихме да създадем собствен front-end компонент (ESNI reverse proxy), който да поддържа терминирането на TLS 1.3 с ESNI и проксирането на HTTP(S) трафика към upstream, който не поддържа ESNI. Това позволява технологиятa да се използва в съществуваща инфраструктура, без да се променят основните компоненти – тоест да се използват текущи уеб сървъри, които не поддържат ESNI.
За по-ясно представяне, ето схемата:

Обръщам внимание, че проксито е замислено с възможността да терминира TLS връзка без ESNI, за да поддържа клиенти без ESNI. Също така, протоколът за комуникация с upstream може да бъде както HTTP, така и HTTPS с версия на TLS под 1.3 (ако upstream не поддържа 1.3). Тази схема дава максимална гъвкавост.
Реализацията на подкрепата за ESNI на go познахме от . Веднага отбелязвам, че самата реализация е достатъчно нетривиална, тъй като предвижда промени в стандартната библиотека crypto/tls и затова изисква "пачинг" GOROOT преди компилация.
За генериране на ESNI ключове използвахме (също творение на CloudFlare). Тези ключове се използват за шифроване/дешифриране на SNI.
Тествахме компилацията с използване на go 1.13 на Linux (Debian, Alpine) и MacOS.
Няколко думи за експлоатационните особености.
ESNI обратный прокси предоставя метрики в формате Prometheus, например, като rps, upstream latency & response codes, неуспешни/успешни TLS handshake-ове & продължителност на TLS handshake. На пръв поглед това изглеждаше достатъчно, за да оценим как проксито се справя с трафика.
Също така, преди използването на решението проведохме натоварвателно тестване. Резултатите са по-долу:
wrk -t50 -c1000 -d360s 'https://esni-rev-proxy.npw:443' --timeout 15s
Изпълнение на 6-минутно тестване @ https://esni-rev-proxy.npw:443
50 нишки и 1000 връзки
Статистика за нишките Avg Stdev Max +/- Stdev
Latency 1.77s 1.21s 7.20s 65.43%
Req/Sec 13.78 8.84 140.00 83.70%
206357 заявки в 6.00m, 6.08GB прочетени
Заявки/сек: 573.07
Прехвърляне/сек: 17.28MB Натоварвателното тестване, което проведохме, беше чисто качествено, за да сравним схемата с използването на ESNI обратен прокси и без него. Ние „наляхме“ трафик локално, за да изключим „смутовете“ в междинните компоненти.
Така че, с поддръжка на ESNI и проксиране към upstream с HTTP, получихме около ~ 550 rps от един инстанс, при това средното потребление на CPU/RAM на ESNI обратния прокси:
- 80% CPU Usage (4 vCPU, 4 GB RAM хостове, Linux)
- 130 MB Mem RSS

За сравнение, RPS за същия upstream nginx без терминиране на TLS (HTTP протокол) ~ 1100:
wrk -t50 -c1000 -d360s 'http://lb.npw:80' --timeout 15s
Изпълнение на 6-минутно тестване @ http://lb.npw:80
50 нишки и 1000 връзки
Статистика за нишките Avg Stdev Max +/- Stdev
Latency 1.11s 2.30s 15.00s 90.94%
Req/Sec 23.25 13.55 282.00 79.25%
393093 заявки в 6.00m, 11.35GB прочетени
Грешки в сокетите: connect 0, read 0, write 0, timeout 9555
Не-2xx или 3xx отговори: 8111
Заявки/сек: 1091.62
Прехвърляне/сек: 32.27MB Наличието на таймаути показва, че има недостатъчност на ресурси (използвахме 4 vCPU, 4 GB RAM хостове, Linux), и реалният потенциален RPS е по-висок (получихме цифри до 2700 RPS на по-мощни ресурси).
В заключение, бих хотел да подчертая, че технологията ESNI изглежда доста обещаваща. Има още много отворени въпроси, например, въпросите за съхранение на публичния ESNI ключ в DNS и ротация на ESNI ключовете – тези въпроси се обсъждат активно, а последната версия на черновата (към момента на писане) на ESNI вече .
Източник: habr.com
