Hallo Habr, mijn naam is Ilya, ik werk bij het platformteam van Exness. We ontwikkelen en implementeren basisinfrastructuurcomponenten die door onze productontwikkelingsteams worden gebruikt.
In dit artikel wil ik mijn ervaringen delen met de implementatie van encrypted SNI (ESNI) technologie in de infrastructuur van openbare websites.

Het gebruik van deze technologie zal het beveiligingsniveau bij het beheren van een openbare website verhogen en voldoen aan de interne beveiligingsnormen die binnen het bedrijf zijn vastgesteld.
Allereerst wil ik benadrukken dat de technologie nog niet is gestandaardiseerd en zich nog in de draftfase bevindt, maar CloudFlare en Mozilla ondersteunen deze al (in ). Dit heeft ons gemotiveerd om met deze experimenten te beginnen.
Een beetje theorie
ESNI is een uitbreiding van het TLS 1.3-protocol die het mogelijk maakt om SNI in het "Client Hello"-bericht van de TLS-handshake te versleutelen. Zo ziet een Client Hello met ondersteuning voor ESNI eruit (in plaats van de gebruikelijke SNI zien we ESNI):

Om ESNI te gebruiken zijn drie componenten nodig:
- DNS;
- Ondersteuning van de client;
- Ondersteuning van de server.
DNS
Er moeten twee DNS-records worden toegevoegd - A, en TXT (Het TXT-record bevat de openbare sleutel waarmee de client SNI kan versleutelen) - zie hieronder. Daarnaast moet er ondersteuning zijn voor DoH (DNS over HTTPS), aangezien de beschikbare clients (zie hieronder) geen ondersteuning voor ESNI activeren zonder DoH. Dit is logisch, omdat ESNI betekent dat de naam van de bron die we aanroepen versleuteld moet worden, wat betekent dat het zinloos is om DNS via UDP aan teroepen. Bovendien biedt het gebruik van bescherming tegen 'cache poisoning'-aanvallen in deze scenario.
Op dit moment zijn er , waaronder:
CloudFlare (Check My Browser → Encrypted SNI → Learn More), dat hun servers al ESNI ondersteunen, wat betekent dat we voor CloudFlare-servers in DNS minstens twee records hebben - A en TXT. In het onderstaande voorbeeld vragen we Google DNS (over HTTPS) aan:
A record:
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 record, het verzoek wordt volgens sjabloon gevormd _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."
}Dus, vanuit het oogpunt van DNS, moeten we DoH (bij voorkeur met DNSSEC) gebruiken en twee records toevoegen.
Ondersteuning van de klant
Als we het over browsers hebben, dan is op dit moment . hier is een instructie over hoe je ESNI en DoH in FireFox activeert. Nadat de browser is ingesteld, zouden we een afbeelding moeten zien zoals:

voor het controleren van de browser.
Uiteraard moet TLS 1.3 worden gebruikt om ESNI te ondersteunen, omdat ESNI een uitbreiding is van TLS 1.3.
Voor testdoeleinden van de backend met ESNI-ondersteuning hebben we een client geïmplementeerd op go, maar daarover later meer.
Ondersteuning van de server
Op dit moment wordt ESNI niet ondersteund door webservers zoals nginx/apache, omdat ze met TLS werken via OpenSSL/BoringSSL, waarin ESNI officieel niet wordt ondersteund.
Daarom hebben we besloten een eigen front-end component te creëren (ESNI reverse proxy) dat TLS 1.3 met ESNI kan termineren en HTTP(S) verkeer kan proxen naar upstream dat ESNI niet ondersteunt. Dit stelt ons in staat om de technologie in een reeds bestaande infrastructuur toe te passen, zonder de belangrijkste componenten te wijzigen - dat wil zeggen, het huidige webservers te gebruiken die ESNI niet ondersteunen.
Ter illustratie geven we een schema:

Ik wil opmerken dat de proxy is ontworpen met de mogelijkheid om een TLS-verbinding zonder ESNI te termineren, voor de ondersteuning van klanten zonder ESNI. Bovendien kan het communicatieprotocol met upstream zowel HTTP als HTTPS zijn met een versie van TLS lager dan 1.3 (als de upstream 1.3 niet ondersteunt). Dit schema biedt maximale flexibiliteit.
De implementatie van ESNI-ondersteuning op go hebben we overgenomen van . Laat me meteen benadrukken dat de implementatie zelf vrij complex is, aangezien deze veranderingen in de standaardbibliotheek vereist. crypto/tls en daarom 'patching' vereist GOROOT voor de build.
Voor het genereren van ESNI-sleutels hebben we (ook een creatie van CloudFlare) gebruikt. Deze sleutels worden gebruikt voor de encryptie/decryptie van SNI.
We hebben de build getest met go 1.13 op Linux (Debian, Alpine) en MacOS.
Een paar woorden over operationele kenmerken
De ESNI reverse proxy biedt metrics in Prometheus-formaat, zoals rps, upstream latentie & responsecodes, mislukte/succesvolle TLS-handshakes & de duur van de TLS-handshake. Op het eerste gezicht leek dit voldoende om te beoordelen hoe de proxy omgaat met verkeer.
Daarnaast hebben we voor gebruik een stresstest uitgevoerd. De resultaten zijn hieronder:
wrk -t50 -c1000 -d360s 'https://esni-rev-proxy.npw:443' --timeout 15s
Running 6m test @ https://esni-rev-proxy.npw:443
50 threads en 1000 verbindingen
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 gelezen
Requests/sec: 573.07
Transfer/sec: 17.28MB De stresstest was puur kwalitatief, om de scenario's met en zonder de ESNI reverse proxy te vergelijken. We 'injecteerden' verkeer lokaal om eventuele 'interferentie' in tussenliggende componenten uit te sluiten.
Dus, met ondersteuning voor ESNI en upstream proxied via HTTP, kregen we ongeveer ~ 550 rps van één instantie, met een gemiddeld CPU/RAM verbruik van de ESNI reverse proxy:
- 80% CPU-gebruik (4 vCPU, 4 GB RAM hosts, Linux)
- 130 MB Mem RSS

Ter vergelijking, RPS voor dezelfde upstream nginx zonder TLS-terminatie (HTTP-protocol) ~ 1100:
wrk -t50 -c1000 -d360s 'http://lb.npw:80' --timeout 15s
Running 6m test @ http://lb.npw:80
50 threads en 1000 verbindingen
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 gelezen
Socketfouten: connect 0, read 0, write 0, timeout 9555
Niet-2xx of 3xx responses: 8111
Requests/sec: 1091.62
Transfer/sec: 32.27MB De aanwezigheid van timeouts duidt op een tekort aan middelen (we hebben 4 vCPU, 4 GB RAM hosts, Linux) en in werkelijkheid ligt het potentieel RPS hoger (we haalden cijfers tot 2700 RPS op krachtigere middelen).
Tot slot wil ik opmerken, dat de ESNI-technologie veelbelovend lijkt. Er zijn nog veel open vragen, zoals de opslag van de publieke ESNI-sleutel in DNS en het roteren van ESNI-sleutels - deze kwesties worden actief besproken, en de laatste versie van de draft (ten tijde van schrijven) van ESNI is al .
Bron: habr.com
