Cześć Habr, nazywam się Ilya, pracuję w zespole platformowym w Exness. Opracowujemy i wdrażamy podstawowe komponenty infrastruktury, z których korzystają nasze zespoły ds. rozwoju produktów.
W tym artykule chciałbym podzielić się swoimi doświadczeniami dotyczącymi wdrażania technologii szyfrowanego SNI (ESNI) w infrastrukturze publicznych stron internetowych.

Zastosowanie tej technologii zwiększy poziom bezpieczeństwa podczas pracy z publiczną witryną internetową i będzie zgodne z wewnętrznymi standardami bezpieczeństwa przyjętymi przez Spółkę.
Przede wszystkim chciałbym zaznaczyć, że technologia ta nie jest jeszcze znormalizowana i znajduje się w fazie roboczej, ale CloudFlare i Mozilla już ją obsługują (w ). To właśnie zmotywowało nas do przeprowadzenia takiego eksperymentu.
Trochę teorii
ESNI – jest rozszerzeniem protokołu TLS 1.3, które umożliwia szyfrowanie SNI w wiadomości handshake „Client Hello” TLS. Oto jak wygląda Client Hello z obsługą ESNI (zamiast zwykłego SNI widzimy ESNI):

Aby korzystać z ESNI, wymagane są trzy komponenty:
- Serwer DNS;
- Obsługa klienta;
- Wsparcie po stronie serwera.
DNS
Musisz dodać dwa rekordy DNS – Ai TXT (rekord TXT zawiera klucz publiczny, którym klient może zaszyfrować SNI) – patrz poniżej. Ponadto powinno być wsparcie Doh (DNS przez HTTPS), ponieważ dostępni klienci (patrz poniżej) nie aktywują obsługi ESNI bez DoH. Jest to logiczne, ponieważ ESNI oznacza szyfrowanie nazwy zasobu, do którego uzyskujemy dostęp, tj. nie ma sensu uzyskiwać dostępu do DNS przez UDP. Ponadto, używając umożliwia ochronę przed atakami typu cache poisoning w tym scenariuszu.
Obecnie dostępne , wśród nich:
CloudFlare (Sprawdź moją przeglądarkę → Zaszyfrowany SNI → Dowiedz się więcej), że ich serwery już obsługują ESNI, czyli dla serwerów CloudFlare w DNS mamy co najmniej dwa rekordy - A i TXT. W poniższym przykładzie żądamy Google DNS (przez HTTPS):
А wejście:
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 rekord, żądanie generowane jest według szablonu _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."
}
Zatem z perspektywy DNS powinniśmy użyć DoH (najlepiej z DNSSEC) i dodać dwa rekordy.
Obsługa klienta
Jeśli mówimy o przeglądarkach, to w tej chwili . podano instrukcje dotyczące aktywacji obsługi ESNI i DoH w FireFox. Po skonfigurowaniu przeglądarki powinniśmy zobaczyć coś takiego:

aby sprawdzić przeglądarkę.
Oczywiście, aby obsługiwać ESNI, należy użyć protokołu TLS 1.3, ponieważ ESNI jest rozszerzeniem protokołu TLS 1.3.
W celu przetestowania zaplecza ze wsparciem ESNI wdrożyliśmy klienta na go, Ale o tym później.
Wsparcie po stronie serwera
Obecnie ESNI nie jest obsługiwany przez serwery WWW, takie jak nginx/apache itp., ponieważ działają one z protokołem TLS za pośrednictwem OpenSSL/BoringSSL, które oficjalnie nie obsługują ESNI.
Dlatego postanowiliśmy stworzyć własny komponent front-end (ESNI reverse proxy), który obsługiwałby zakończenie TLS 1.3 z ESNI i przekierowywał ruch HTTP(S) do upstream, który nie obsługuje ESNI. Pozwala to na wykorzystanie technologii w już istniejącej infrastrukturze, bez zmiany głównych komponentów - to znaczy, wykorzystując obecne serwery WWW, które nie obsługują ESNI.
Dla jasności przedstawiam poniższy diagram:

Chciałbym zauważyć, że proxy zostało zaprojektowane z możliwością zakończenia połączenia TLS bez ESNI, aby obsługiwać klientów bez ESNI. Ponadto protokół do komunikacji z nadrzędnym serwerem może być albo HTTP albo HTTPS z wersją TLS niższą niż 1.3 (jeśli nadrzędny serwer nie obsługuje wersji 1.3). Ten schemat zapewnia maksymalną elastyczność.
Wdrożenie wsparcia ESNI w go pożyczyliśmy od . Od razu zauważę, że sama implementacja nie jest wcale trywialna, ponieważ wymaga zmian w bibliotece standardowej. krypto/tls i dlatego wymaga „łatania” GOROOT przed montażem.
Do wygenerowania kluczy ESNI użyliśmy (również dzieło CloudFlare). Te klucze służą do szyfrowania/odszyfrowywania SNI.
Przetestowaliśmy kompilację, używając wersji Go 1.13 Linux (Debian, Alpine) i MacOS.
Kilka słów o cechach operacyjnych
Odwrotny serwer proxy ESNI dostarcza metryki w formacie Prometheus, takie jak rps, opóźnienia w górę i kody odpowiedzi, nieudane/udane uzgadniania TLS i czas trwania uzgadniania TLS. Na pierwszy rzut oka wydawało się to wystarczające do oceny, jak serwer proxy obsługuje ruch.
Przeprowadziliśmy również testy obciążeniowe przed użyciem. Wyniki są poniżej:
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
Przeprowadziliśmy czysto jakościowe testy obciążenia, aby porównać schemat z odwrotnym proxy ESNI i bez niego. „Wylaliśmy” ruch lokalnie, aby wyeliminować „zakłócenia” w komponentach pośrednich.
Dzięki obsłudze ESNI i obsłudze proxy HTTP uzyskaliśmy około 550 obr./s z jednej instancji, biorąc pod uwagę średnie zużycie procesora i pamięci RAM przez odwrotny serwer proxy ESNI:
- 80% wykorzystania procesora (4 wirtualne procesory, hosty 4 GB RAM, Linux)
- 130 MB Pamięć RSS

Dla porównania, RPS dla tego samego serwera nginx upstream bez zakończenia TLS (protokół HTTP) wynosi ~ 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
Obecność przekroczeń limitu czasu wskazuje na brak zasobów (użyliśmy hostów z 4 procesorami wirtualnymi i 4 GB pamięci RAM, Linux), a w rzeczywistości potencjalny RPS jest wyższy (otrzymaliśmy dane do 2700 RPS przy mocniejszych zasobach).
Podsumowując, chciałbym zauważyć, że że technologia ESNI wygląda obiecująco. Nadal pozostaje wiele otwartych kwestii, na przykład kwestie przechowywania klucza publicznego ESNI w DNS i rotacji kluczy ESNI - kwestie te są aktywnie omawiane, a najnowsza wersja projektu (w momencie pisania) ESNI jest już .
Źródło: www.habr.com
