สวัสดี Habr ฉันชื่อ Ilya ฉันทำงานในทีมแพลตฟอร์มที่ Exness เราพัฒนาและใช้งานส่วนประกอบโครงสร้างพื้นฐานหลักที่ทีมพัฒนาผลิตภัณฑ์ของเราใช้
ในบทความนี้ ฉันต้องการแบ่งปันประสบการณ์ของฉันในการใช้เทคโนโลยี SNI ที่เข้ารหัส (ESNI) ในโครงสร้างพื้นฐานของเว็บไซต์สาธารณะ
การใช้เทคโนโลยีนี้จะช่วยเพิ่มระดับความปลอดภัยเมื่อทำงานกับเว็บไซต์สาธารณะ และสอดคล้องกับมาตรฐานความปลอดภัยภายในที่บริษัทนำมาใช้
ก่อนอื่นฉันอยากจะชี้ให้เห็นว่าเทคโนโลยีไม่ได้มาตรฐานและยังอยู่ในร่าง แต่ CloudFlare และ Mozilla รองรับแล้ว (ใน
ทฤษฎีเล็กน้อย
เอสนี่ เป็นส่วนขยายของโปรโตคอล TLS 1.3 ที่อนุญาตการเข้ารหัส SNI ในข้อความ TLS handshake "Client Hello" นี่คือลักษณะของ Client Hello พร้อมการสนับสนุน ESNI (แทนที่จะเป็น SNI ปกติที่เราเห็น ESNI):
หากต้องการใช้ ESNI คุณต้องมีสามองค์ประกอบ:
- ดีเอ็นเอส;
- การสนับสนุนลูกค้า
- การสนับสนุนฝั่งเซิร์ฟเวอร์
DNS
คุณต้องเพิ่มระเบียน DNS สองรายการ – Aและ TXT (บันทึก TXT มีคีย์สาธารณะซึ่งไคลเอ็นต์สามารถเข้ารหัส SNI ได้) - ดูด้านล่าง นอกจากนี้ยังต้องมีการสนับสนุน กระทรวงกลาโหม (DNS ผ่าน 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) และเพิ่มสองรายการ
สนับสนุนลูกค้า
หากเรากำลังพูดถึงเบราว์เซอร์อยู่ในขณะนี้
แน่นอนว่าต้องใช้ TLS 1.3 เพื่อรองรับ ESNI เนื่องจาก ESNI เป็นส่วนเสริมของ TLS 1.3
เพื่อวัตถุประสงค์ในการทดสอบแบ็กเอนด์ด้วยการรองรับ ESNI เราได้ใช้งานไคลเอนต์ go, แต่เพิ่มเติมเกี่ยวกับที่ในภายหลัง.
การสนับสนุนฝั่งเซิร์ฟเวอร์
ปัจจุบัน เว็บเซิร์ฟเวอร์ไม่รองรับ ESNI เช่น nginx/apache ฯลฯ เนื่องจากเว็บเซิร์ฟเวอร์เหล่านี้ทำงานร่วมกับ TLS ผ่าน OpenSSL/BoringSSL ซึ่งไม่รองรับ ESNI อย่างเป็นทางการ
ดังนั้นเราจึงตัดสินใจสร้างส่วนประกอบส่วนหน้าของเราเอง (พร็อกซีย้อนกลับ ESNI) ซึ่งจะสนับสนุนการยกเลิก TLS 1.3 ด้วย ESNI และการรับส่งข้อมูล HTTP(S) พร็อกซีไปยังอัปสตรีม ซึ่งไม่รองรับ ESNI ซึ่งช่วยให้สามารถใช้เทคโนโลยีในโครงสร้างพื้นฐานที่มีอยู่แล้ว โดยไม่ต้องเปลี่ยนส่วนประกอบหลัก นั่นคือ การใช้เว็บเซิร์ฟเวอร์ปัจจุบันที่ไม่รองรับ ESNI
เพื่อความชัดเจน นี่คือแผนภาพ:
ฉันทราบว่าพร็อกซีได้รับการออกแบบให้มีความสามารถในการยุติการเชื่อมต่อ TLS โดยไม่มี ESNI เพื่อรองรับไคลเอนต์ที่ไม่มี ESNI นอกจากนี้ โปรโตคอลการสื่อสารที่มีอัปสตรีมอาจเป็น HTTP หรือ HTTPS ที่มี TLS เวอร์ชันต่ำกว่า 1.3 (หากอัปสตรีมไม่รองรับ 1.3) โครงการนี้ให้ความยืดหยุ่นสูงสุด
การดำเนินการสนับสนุน ESNI บน go เรายืมมาจาก
เพื่อสร้างคีย์ ESNI ที่เราใช้
เราทดสอบบิลด์โดยใช้ go 1.13 บน Linux (Debian, Alpine) และ MacOS
คำไม่กี่คำเกี่ยวกับคุณสมบัติการดำเนินงาน
พร็อกซีย้อนกลับ ESNI จัดเตรียมตัววัดในรูปแบบ Prometheus เช่น rps เวลาแฝงอัปสตรีมและโค้ดตอบกลับ การแฮนด์เชค TLS ที่ล้มเหลว/สำเร็จ และระยะเวลาการแฮนด์เชค TLS เมื่อมองแวบแรก ดูเหมือนว่าจะเพียงพอที่จะประเมินว่าพร็อกซีจัดการกับการรับส่งข้อมูลอย่างไร
เรายังทำการทดสอบการรับน้ำหนักก่อนการใช้งานด้วย ผลลัพธ์ด้านล่าง:
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:
- การใช้งาน CPU 80% (4 vCPU, โฮสต์ RAM 4 GB, Linux)
- RSS หน่วยความจำ 130 MB
สำหรับการเปรียบเทียบ RPS สำหรับ nginx upstream เดียวกันโดยไม่มีการยกเลิก 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, โฮสต์ RAM 4 GB, Linux) และในความเป็นจริง RPS ที่เป็นไปได้นั้นสูงกว่า (เราได้รับตัวเลขสูงถึง 2700 RPS จากทรัพยากรที่มีประสิทธิภาพมากขึ้น)
โดยสรุปฉันทราบ ว่าเทคโนโลยี ESNI ดูมีแนวโน้มดีทีเดียว ยังคงมีคำถามเปิดอยู่มากมาย เช่น ปัญหาในการจัดเก็บคีย์ ESNI สาธารณะใน DNS และการหมุนเวียนคีย์ ESNI - ปัญหาเหล่านี้กำลังถูกพูดคุยกันอย่างกระตือรือร้นและฉบับร่าง ESNI เวอร์ชันล่าสุด (ณ เวลาที่เขียน) ก็มีอยู่แล้ว
ที่มา: will.com