Jak chronić swoją publiczną witrynę internetową za pomocą ESNI

Witaj Habr, mam na imię 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 z wdrażania technologii szyfrowanej SNI (ESNI) w infrastrukturze publicznych stron internetowych.

Jak chronić swoją publiczną witrynę internetową za pomocą ESNI

Zastosowanie tej technologii zwiększy poziom bezpieczeństwa podczas pracy z ogólnodostępnym serwisem internetowym oraz zgodność z wewnętrznymi standardami bezpieczeństwa przyjętymi przez Spółkę.

Na wstępie zaznaczę, że technologia nie jest ustandaryzowana i jest jeszcze w fazie roboczej, ale CloudFlare i Mozilla już ją wspierają (w szkic01). To nas zmotywowało do takiego eksperymentu.

Trochę teorii

ESNI to rozszerzenie protokołu TLS 1.3, które umożliwia szyfrowanie SNI w wiadomości „Client Hello” podczas uzgadniania TLS. Oto jak wygląda Client Hello ze wsparciem ESNI (zamiast zwykłego SNI widzimy ESNI):

Jak chronić swoją publiczną witrynę internetową za pomocą ESNI

 Aby korzystać z ESNI, potrzebujesz trzech komponentów:

  • DNSy; 
  • Wsparcie klienta;
  • Wsparcie po stronie serwera.

DNS

Musisz dodać dwa rekordy DNS – Ai TXT (Rekord TXT zawiera klucz publiczny, za pomocą którego klient może zaszyfrować SNI) - patrz poniżej. Poza tym musi być wsparcie Doh (DNS przez HTTPS), ponieważ dostępni klienci (patrz poniżej) nie umożliwiają obsługi ESNI bez DoH. Jest to logiczne, ponieważ ESNI implikuje szyfrowanie nazwy zasobu, do którego uzyskujemy dostęp, to znaczy nie ma sensu uzyskiwać dostępu do DNS przez UDP. Co więcej, użycie DNSSEC pozwala w tym scenariuszu chronić się przed atakami polegającymi na zatruwaniu pamięci podręcznej.

Aktualnie dostępne kilku dostawców DoH, pomiędzy nimi:

CloudFlare deklaruje (Sprawdź moją przeglądarkę → Zaszyfrowane SNI → Dowiedz się więcej), że ich serwery obsługują już ESNI, czyli dla serwerów CloudFlare w DNS mamy co najmniej dwa rekordy - A i TXT. W poniższym przykładzie odpytujemy 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 rekordu, żą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 punktu widzenia DNS powinniśmy użyć DoH (najlepiej z DNSSEC) i dodać dwa wpisy. 

Wsparcie klienta

Jeśli mówimy o przeglądarkach, to w tej chwili obsługa jest zaimplementowana tylko w FireFox. Tutaj Oto instrukcje, jak aktywować obsługę ESNI i DoH w FireFox. Po skonfigurowaniu przeglądarki powinniśmy zobaczyć coś takiego:

Jak chronić swoją publiczną witrynę internetową za pomocą ESNI

Połączenie aby sprawdzić przeglądarkę.

Oczywiście do obsługi ESNI należy używać protokołu TLS 1.3, ponieważ ESNI jest rozszerzeniem protokołu TLS 1.3.

Na potrzeby testów backendu z obsługą ESNI zaimplementowaliśmy klienta na go, Ale o tym później.

Wsparcie po stronie serwera

Obecnie ESNI nie jest obsługiwane przez serwery internetowe, takie jak nginx/apache itp., ponieważ współpracują z TLS poprzez OpenSSL/BoringSSL, które oficjalnie nie obsługują ESNI.

Dlatego zdecydowaliśmy się stworzyć własny komponent front-endowy (odwrotne proxy ESNI), który obsługiwałby terminację TLS 1.3 z ruchem ESNI i proxy 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 – czyli wykorzystania obecnych serwerów WWW, które nie obsługują ESNI. 

Dla jasności oto diagram:

Jak chronić swoją publiczną witrynę internetową za pomocą ESNI

Zwracam uwagę, że serwer proxy został zaprojektowany z możliwością zakończenia połączenia TLS bez ESNI, aby obsługiwać klientów bez ESNI. Ponadto protokołem komunikacyjnym z upstream może być HTTP lub HTTPS z wersją TLS niższą niż 1.3 (jeśli upstream nie obsługuje wersji 1.3). Ten schemat zapewnia maksymalną elastyczność.

Wdrożenie wsparcia ESNI w dniu go pożyczyliśmy od CloudFlare. Od razu zaznaczę, że sama implementacja jest dość nietrywialna, gdyż wiąże się ze zmianami w standardowej bibliotece krypto/tls i dlatego wymaga „łatania” GOROOT przed montażem.

Do wygenerowania kluczy ESNI użyliśmy esnitool (także pomysł CloudFlare). Klucze te służą do szyfrowania/deszyfrowania SNI.
Przetestowaliśmy tę kompilację przy użyciu wersji 1.13 na systemach Linux (Debian, Alpine) i MacOS. 

Kilka słów o funkcjach operacyjnych

Odwrotne proxy ESNI zapewnia metryki w formacie Prometheus, takie jak liczba obrotów na sekundę, opóźnienia i kody odpowiedzi nadawczego, nieudane/udane uzgadnianie TLS i czas trwania uzgadniania TLS. Na pierwszy rzut oka wydawało się to wystarczające, aby ocenić, jak serwer proxy obsługuje ruch. 

Przed użyciem przeprowadziliśmy również testy obciążeniowe. Wyniki 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 użyciem odwrotnego proxy ESNI i bez niego. „Wylewaliśmy” ruch lokalnie, aby wyeliminować „interferencję” w elementach pośrednich.

Tak więc, dzięki obsłudze ESNI i proxy do przesyłania danych z HTTP, uzyskaliśmy około 550 obr/s z jednej instancji, przy średnim zużyciu procesora/RAM przez odwrotne proxy ESNI:

  • 80% użycia procesora (4 vCPU, 4 GB RAM hosty, Linux)
  • 130 MB pamięci RSS

Jak chronić swoją publiczną witrynę internetową za pomocą ESNI

Dla porównania, RPS dla tego samego upstream nginx 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ść timeoutów wskazuje na brak zasobów (użyliśmy 4 vCPU, 4 GB RAM hostów, Linux), a w rzeczywistości potencjalny RPS jest wyższy (otrzymaliśmy dane do 2700 RPS na mocniejszych zasobach).

Podsumowując, zauważam że technologia ESNI wygląda całkiem obiecująco. Nadal pozostaje wiele otwartych kwestii, np. kwestia przechowywania publicznego klucza ESNI w DNS i rotacji kluczy ESNI – te kwestie są aktywnie omawiane, a najnowsza wersja projektu ESNI (w momencie pisania tego tekstu) jest już 7.

Źródło: www.habr.com

Dodaj komentarz