Привіт Хабре, мене звуть Ілля, я працюю у платформній команді компанії Exness. Ми розробляємо та впроваджуємо базові інфраструктурні компоненти, які використовують наші продуктові команди розробки.
У цій статті я хотів би поділитися досвідом впровадження технології encrypted SNI (ESNI) в інфраструктурі публічних веб-сайтів.
Використання цієї технології дозволить підвищити рівень безпеки під час роботи з публічним веб-сайтом та відповідати внутрішнім стандартам безпеки, прийнятим у Компанії.
Перш за все хочу звернути увагу, що технологія не стандартизована і все ще знаходиться в драфті, проте CloudFlare і Mozilla вже підтримують її (у
Трохи теорії
ЕСНІ – це розширення до протоколу 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. Більше того, використання
На даний момент доступно
CloudFlare
А запис:
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 має бути використаний TLS 1.3, оскільки ESNI – це розширення TLS 1.3.
Для цілей тестування бекенду з підтримкою ESNI ми реалізували клієнта на go, Але про це трохи пізніше.
Підтримка сервера
На даний момент ESNI не підтримується web-серверами типу nginx/apache тощо, оскільки вони працюють із TLS за допомогою OpenSSL/BoringSSL, у яких ESNI офіційно не підтримується.
Тому ми вирішили створити свій front-end компонент (ESNI reverse proxy), який би підтримував термінацію TLS 1.3 з ESNI та проксіювання HTTP(S) трафіку на апстрім, що не підтримує ESNI. Це дозволяє застосовувати технологію в інфраструктурі, що вже склалася, без зміни основних компонентів – тобто використовувати поточні web-сервери, що не підтримують ESNI.
Для наочності наведемо схему:
Зазначу, що проксі замислювався з можливістю термінувати TLS з'єднання без ESNI для підтримки клієнтів без ESNI. Також, протокол спілкування з апстрімом може бути як HTTP, так і HTTPS з версією TLS нижче 1.3 (якщо апстрім не підтримує 1.3). Така схема дає максимальну гнучкість.
Реалізація підтримки ESNI на go ми запозичили у
Для генерації ключів ESNI ми використовували
Ми протестували збирання з використанням go 1.13 на Linux (Debian, Alpine) та MacOS.
Пари слів про експлуатаційні особливості
ESNI reverse proxy надає метрики у форматі Prometheus, наприклад, такі, як rps, upstream latency & response codes, failed/successful TLS handshakes & TLS handshake duration. На перший погляд, це здалося достатнім для оцінки того, як проксі справляється з трафіком.
Також перед використанням ми провели тестування навантаження. Результати нижче:
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 reverse proxy і без. Ми «наливали» трафік локально для того, щоб унеможливити «перешкоди» в проміжних компонентах.
Отже, з підтримкою ESNI та проксуванням на апстрім з HTTP, ми отримали в районі ~ 550 rps з однієї інстансу, при цьому середнє споживання CPU/RAM ESNI reverse proxy:
- 80% CPU Usage (4 vCPU, 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 вже
Джерело: habr.com