การส่งต่อไบต์ที่เข้ารหัสโดยไม่เคยถือใบรับรองคือสิ่งที่รีเลย์ TLS passthrough ทำ และการจัดวางนี้ทั้งสะดวกและสับสนเล็กน้อยในเวลาเดียวกัน เนื่องจากไม่มีอะไรถูกถอดรหัส จึงไม่มีสิ่งใดในรูปแบบ HTTP ให้กรอง ดังนั้นบล็อก location ปกติและกฎการเข้าถึงของมันจึงไม่มีประโยชน์ที่นี่ การตัดสินใจที่ NGINX มีนั้นเกี่ยวกับการเชื่อมต่อเอง: peer เครือข่ายใดที่เคาะเข้ามา และควรส่งต่อ peer นั้นหรือไม่

การตัดสินใจอยู่ใน stream ไม่ใช่ HTTP

ในบริบท stream NGINX ทำงานกับเซสชัน TCP มันเห็น peer address ของการเชื่อมต่อและสามารถใช้กฎ allow และ deny ตามลำดับกับมันก่อนที่ไบต์ใดจะถึง backend นี่เป็นการตัดสินใจรับเข้าที่สมบูรณ์และไม่ต้องใช้คีย์ส่วนตัว ใบรับรอง หรือการแยกวิเคราะห์ HTTP

MechanismWhat it decidesWhere it lives
stream allow/denyWhether this TCP peer is relayedstream server block
ssl_preread mapWhich backend a named connection goes tostream, with the preread module
HTTP location ruleWhether an HTTP request is servedhttp server block

สามบรรทัดนั้นไม่เคยแทนที่กัน การคัดลอกคำสั่งเข้าถึง HTTP ไปยังไฟล์ใดก็ตามที่กล่าวถึงพอร์ต 443 ไม่เปลี่ยนแปลงอะไร เพราะเซิร์ฟเวอร์ stream ไม่เคยอ่านบริบท http

ยอมรับการเชื่อมต่อก่อนส่งต่อ

สมมติว่ามีการติดตั้งที่มีโมดูล stream และ stream-access พร้อมใช้งาน ไคลเอนต์ VPN เก็บต้นทางแอดเดรสไว้ใน 10.90.0.0/24 และมี backend ที่ 192.168.50.20:443 ซึ่งให้บริการตัวตน HTTPS ที่ต้องการอยู่แล้ว

stream {
    server {
        listen 443;

        allow 10.90.0.0/24;
        deny all;

        proxy_pass 192.168.50.20:443;
    }
}

อ่านสิ่งนี้เป็นส่วนหนึ่งของ listener ใหม่ ไม่ใช่การแทนที่การกำหนดค่าที่ทำงานอยู่โดยตรง ตรวจสอบ listener และ include ที่มีอยู่ก่อน เพราะ listener ที่สองบนพอร์ตที่ถูกใช้อยู่แล้วจะล้มเหลวตอน reload ไม่ใช่ตอนร้องขอ

สังเกตสิ่งที่ขาดหาย: ไม่มี listen ... ssl ไม่มีคำสั่งใบรับรอง NGINX ส่งต่อสตรีมที่เข้ารหัส และ backend ยุติ TLS และนำเสนอใบรับรองที่ไคลเอนต์ตรวจสอบ ไคลเอนต์ที่ไม่ผ่าน allowlist จะถูกปฏิเสธเซสชัน TCP ไม่ใช่หน้า HTTP 403 และเครื่องมือใดที่คาดหวังรหัสสถานะจะรายงานสิ่งที่ไม่เป็นประโยชน์

allowlist ครอบคลุมอะไรและไม่ครอบคลุมอะไร

หากรีเลย์เลือก backend ตามชื่อเซิร์ฟเวอร์อยู่แล้ว ssl_preread และ map สามารถทำงานนั้นต่อไปได้หากโมดูลพร้อมใช้งาน ชื่อยังคงถูกจัดหาโดยไคลเอนต์ และไม่มีสิ่งใดในการนำเสนอชื่อที่พิสูจน์ว่าไคลเอนต์เข้าร่วม VPN

กฎตัวอย่างครอบคลุม listener stream ทั้งหมด สิ่งนี้ทำให้ไม่เหมาะกับจุดเข้าออกเดียวที่ให้บริการเว็บไซต์สาธารณะและเว็บไซต์เฉพาะ VPN ผสมกัน ซึ่งจะต้องมีข้อยกเว้นต่อชื่อ listener แยกมักจะชัดเจนกว่านโยบายที่ขึ้นกับชื่อซึ่งไม่มีใครอ่านได้

ควรกล่าวถึงช่องว่างอีกสองประการ การจับคู่ prefix ไม่ได้บอกว่าแพ็กเก็ตเข้ามาทางอินเทอร์เฟซใด และกฎ HTTP ที่อื่นไม่สามารถจำกัด listener นี้ได้ ทั้งสองเป็นเหตุผลที่ควรเก็บนโยบาย ingress ระดับโฮสต์ไว้ในภาพแทนที่จะถือว่า allow บรรทัดเดียวเป็นขอบเขตทั้งหมด เขียนขอบเขตเป็นสองการควบคุมที่ต้องสอดคล้องกัน เพื่อให้การเปลี่ยนแปลงในสิ่งใดสิ่งหนึ่งได้รับการทบทวน

NGINX เห็นแอดเดรสของใครจริง ๆ

ตัวกระจายโหลด พร็อกซีอื่น หรือ source NAT สามารถวางตัวกลางไว้หน้า NGINX ได้ ดังนั้น peer ที่เห็นจึงไม่ใช่แอดเดรสภายในของไคลเอนต์ VPN ตรวจสอบเส้นทางจริงก่อนเลือก prefix เพราะ allowlist ที่เขียนสำหรับแอดเดรสผิดจะอนุญาตทุกคนที่อยู่หลังตัวแปลหรือไม่ให้ใครเลย

โปรโตคอล PROXY และการจัดการ IP จริง เป็นคำตอบที่ชอบด้วยกฎหมาย และควรกำหนดค่าเฉพาะสำหรับผู้ส่งที่เข้ากันได้และได้รับความไว้วางใจอย่างชัดเจน กลไกทั้งสองเชื่อสิ่งที่ผู้ส่งพูด และนั่นคือเหตุผลที่รายชื่อผู้ส่งต้องสั้น การไว้วางใจส่วนหัวระบุตัวตนจากไคลเอนต์อินเทอร์เน็ตใด ๆ ไม่ได้ทำให้ข้อมูลต้นทางน่าเชื่อถืออีกครั้ง แต่เปลี่ยนการปฏิเสธที่ถูกต้องเป็นการยอมรับที่ปลอมแปลง

ทดสอบทั้งสองผลลัพธ์ก่อน reload

ตรวจสอบความถูกต้องของการกำหนดค่า ทำ reload แล้วทดสอบสองครั้ง: เซสชันใหม่ที่ได้รับอนุญาตซึ่งต้องทำงานได้ และเซสชันที่ถูกปฏิเสธซึ่งไม่ควรทำงาน เก็บการตรวจสอบใบรับรอง backend ตามปกติไว้ในการทดสอบที่ได้รับอนุญาต เพราะรีเลย์ที่เชื่อมต่อได้แต่ตรวจสอบ backend ไม่ได้แก้ปัญหาหนึ่งและสร้างอีกปัญหาหนึ่ง

nginx -t
ss -tnp | grep ':443'

ตรวจสอบเส้นทางไปยัง backend และการตอบกลับแยกกันด้วย การผ่าน allowlist พิสูจน์เพียงว่า NGINX ยอมรับเซสชัน ไม่สามารถบังคับ backend ที่เข้าถึงไม่ได้ให้ตอบกลับ และไม่สามารถซ่อมเส้นทางกลับที่ขาดหายก่อนคุณเริ่มได้

บทความที่เกี่ยวข้อง