Як захистити свій публічний сайт із ESNI

Привіт Хабре, мене звуть Ілля, я працюю у платформній команді компанії Exness. Ми розробляємо та впроваджуємо базові інфраструктурні компоненти, які використовують наші продуктові команди розробки.

У цій статті я хотів би поділитися досвідом впровадження технології encrypted SNI (ESNI) в інфраструктурі публічних веб-сайтів.

Як захистити свій публічний сайт із ESNI

Використання цієї технології дозволить підвищити рівень безпеки під час роботи з публічним веб-сайтом та відповідати внутрішнім стандартам безпеки, прийнятим у Компанії.

Перш за все хочу звернути увагу, що технологія не стандартизована і все ще знаходиться в драфті, проте CloudFlare і Mozilla вже підтримують її (у чернетка01). Це й мотивувало нас на такий експеримент.

Трохи теорії

ЕСНІ – це розширення до протоколу TLS 1.3, яке дозволяє шифрувати SNI у повідомленні "Client Hello" TLS handshake. Ось як виглядає Client Hello з підтримкою ESNI (замість звичного SNI ми бачимо ESNI):

Як захистити свій публічний сайт із ESNI

 Щоб використати ESNI, необхідні три складові:

  • DNS; 
  • Підтримка з боку клієнта;
  • Підтримка сервера.

DNS

Необхідно додати два DNS записи – A, І TXT (TXT запис містить публічний ключ, за допомогою якого клієнт може зашифрувати SNI) – див. нижче. Крім того, має бути підтримка DoH (DNS over HTTPS), оскільки доступні клієнти (див. нижче) не активують підтримку ESNI без DoH. Це логічно, оскільки ESNI має на увазі шифрацію імені ресурсу, до якого ми звертаємося, тобто безглуздо звертатися до DNS за UDP. Більше того, використання DNSSEC дозволяє захиститися від "cache poisoning" атак у цьому сценарії.

На даний момент доступно кілька DoH провайдерів, серед них:

CloudFlare заявляє (Check My Browser → Encrypted SNI → Learn More), що їхні сервери вже зараз підтримують ESNI, тобто для серверів CloudFlare в DNS ми маємо як мінімум два записи – А та 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) та додати два записи. 

Підтримка з боку клієнта

Якщо ми говоримо про браузери, то на сьогоднішній момент підтримка реалізована лише у FireFox. Тут наведено інструкцію, як активувати підтримку ESNI та DoH у FireFox. Після того, як браузер налаштований, ми маємо побачити приблизно таку картину:

Як захистити свій публічний сайт із ESNI

Посилання для перевірки браузера.

Зрозуміло, для підтримки 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. 

Для наочності наведемо схему:

Як захистити свій публічний сайт із ESNI

Зазначу, що проксі замислювався з можливістю термінувати TLS з'єднання без ESNI для підтримки клієнтів без ESNI. Також, протокол спілкування з апстрімом може бути як HTTP, так і HTTPS з версією TLS нижче 1.3 (якщо апстрім не підтримує 1.3). Така схема дає максимальну гнучкість.

Реалізація підтримки ESNI на go ми запозичили у CloudFlare. Відразу зазначу, що сама реалізація досить нетривіальна, оскільки передбачає зміни у стандартній бібліотеці crypto/tls і тому вимагає «патчингу» ГОРООТ перед збиранням.

Для генерації ключів ESNI ми використовували esnitool (теж дітище CloudFlare). Ці ключі використовуються для шифрування/дешифрації SNI.
Ми протестували збирання з використанням 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

Як захистити свій публічний сайт із ESNI

Для порівняння, 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 вже 7.

Джерело: habr.com

Додати коментар або відгук