ติดตามคำขอหนึ่งรายการจากโทรศัพท์บนรถไฟไปยังแบ็กเอนด์ที่มีอยู่เฉพาะภายในเครือข่ายส่วนตัว โทรศัพท์แปลงชื่อสาธารณะ เชื่อมต่อกับที่อยู่สาธารณะบนพอร์ต 443 และทำ TLS แฮนด์เชกตามปกติให้เสร็จ Nginx ยอมรับการเชื่อมต่อนั้นแล้วเปิดการเชื่อมต่อที่สองที่แยกจากกันโดยสิ้นเชิงไปยังที่อยู่ซึ่งเข้าถึงได้ผ่านอินเทอร์เฟซ VPN เท่านั้น ไม่มีอะไรเปลี่ยนที่โทรศัพท์ และโทรศัพท์ไม่เคยรู้ว่ามีอุโมงค์เข้ามาเกี่ยวข้อง

ติดตามคำขอหนึ่งรายการผ่านการเชื่อมต่อสองทอด

ทอดขาเข้าเป็นการทำธุรกรรม HTTP สาธารณะ มีชื่อสาธารณะ ใบรับรองสาธารณะ และเส้นทางตอบกลับที่ต้องทำงานกับที่อยู่ใดก็ตามที่ผู้เยี่ยมชมมีอยู่ในขณะนั้น การเปลี่ยนแปลงใดที่รบกวนเส้นทางนั้นจะทำให้ผู้เยี่ยมชมทุกคนพังพร้อมกัน นั่นคือเหตุผลที่ทอดสาธารณะเป็นทอดที่คุณอยากทดลองน้อยที่สุด

ทอดขาออกเป็นทอดส่วนตัว Nginx กลายเป็นไคลเอนต์ แปลงชื่อภายในหรือใช้ที่อยู่คงที่ และเข้าถึงบริการที่จะปฏิเสธการเชื่อมต่อจากอินเทอร์เน็ต

Public client  -> frontend public address:443   (TLS, public name)
Frontend       -> VPN private backend 10.40.0.20:8443  (TLS, private name)

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

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

ชื่อเกตเวย์ต้องรอดผ่านอุโมงค์ไปให้ได้

มีกับดักที่ละเอียดอ่อนอยู่ในการแปลงชื่อ หาก DNS ส่วนตัวเขียนทับชื่อเดียวกับที่โปรไฟล์ VPN ใช้สำหรับเกตเวย์ของตัวเอง การเชื่อมต่อใหม่ครั้งหนึ่งอาจลงเอยด้วยการพยายามเข้าถึงเกตเวย์นั้นผ่านอุโมงค์ที่มันควรจะสร้างขึ้นเอง ไคลเอนต์ต้องการให้ชื่อนั้นถูกแปลงก่อนที่อุโมงค์จะมีอยู่ เมื่อชื่อนั้นถูกแปลงอยู่ภายในอุโมงค์แล้ว การเชื่อมต่อก็ไม่อาจสร้างได้ตั้งแต่แรก

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

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

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

เขียนบล็อกเซิร์ฟเวอร์ให้ทั้งสองทอดตรวจสอบ

สมมติว่าแบ็กเอนด์นำเสนอใบรับรองสำหรับชื่อนี้: backend.corp.example และตอบบนที่อยู่ส่วนตัว การเชื่อมต่อกับที่อยู่ขณะตรวจสอบชื่อคือสิ่งที่ตัวเลือก SNI และชื่อของโมดูลพร็อกซีมีไว้เพื่อการนี้พอดี

upstream private_app {
    server 10.40.0.20:8443;
}

server {
    listen 443 ssl;
    server_name app.example.com;

    ssl_certificate     /etc/nginx/tls/app-fullchain.pem;
    ssl_certificate_key /etc/nginx/tls/app-key.pem;

    location / {
        proxy_pass https://private_app;
        proxy_ssl_server_name on;
        proxy_ssl_name backend.corp.example;
        proxy_ssl_trusted_certificate /etc/nginx/trust/backend-ca.pem;
        proxy_ssl_verify on;
        proxy_ssl_verify_depth 3;

        proxy_set_header Host app.example.com;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-For $remote_addr;
    }
}

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

proxy_set_header Host app.example.com เป็นความตั้งใจ แบ็กเอนด์ควรได้รับการกำหนดค่าให้ยอมรับต้นกำเนิดสาธารณะนั้น และควรเชื่อถือส่วนหัวที่ส่งต่อมาเฉพาะจากส่วนหน้านี้เท่านั้น การเขียนทับ X-Forwarded-For ด้วยที่อยู่ไคลเอนต์ถูกต้องสำหรับพร็อกซีตัวเดียวและผิดสำหรับห่วงโซ่ ดังนั้นการติดตั้งที่มีพร็อกซีหลายตัวจึงต้องมีระบบจัดการที่อยู่ที่เชื่อถือได้ของตัวเอง

ทำให้ต้นกำเนิดสาธารณะประพฤติตัวเหมือนต้นกำเนิดสาธารณะ

แอปพลิเคชันควรสร้าง URL แบบสัมบูรณ์ การเปลี่ยนเส้นทาง และคุกกี้สำหรับต้นกำเนิดสาธารณะ หากมันเปลี่ยนเส้นทางผู้เยี่ยมชมไปยัง backend.corp.example ผู้เยี่ยมชมคนนั้นจะถูกส่งไปยังชื่อที่แปลงไม่ได้

แก้ไขเรื่องนี้ผ่านการตั้งค่า URL ภายนอกที่แอปพลิเคชันรองรับและนโยบายการเปลี่ยนเส้นทางที่ผ่านการทบทวน ไม่ใช่ด้วยการเขียนชื่อโฮสต์ใหม่ในเนื้อหาการตอบกลับ การแทนที่สตริงในเนื้อหาตามอำเภอใจจะทำให้เพย์โหลดที่ลงนามแล้วเสียหายและสร้างบั๊กที่ตามรอยได้ยาก

คุกกี้สมควรได้รับการดูแลแบบเดียวกัน คุกกี้ที่จำกัดขอบเขตไว้ที่ชื่อส่วนตัวจะไม่ถูกส่งโดยเบราว์เซอร์ที่รู้จักเฉพาะชื่อสาธารณะ

ตรวจสอบด้วยว่า Nginx สามารถเป็นเจ้าของซ็อกเก็ตสาธารณะได้จริง บริการเว็บที่มีอยู่บน 443 ต้องย้ายที่หรือส่วนหน้าต้องอยู่ที่อื่น เพราะสองโปรเซสไม่สามารถผูกที่อยู่และพอร์ตเดียวกันได้

ตรวจสอบแต่ละทอดในที่ที่มันทำงานจริง

ตรวจสอบความถูกต้องของการกำหนดค่าด้วยตัวแยกวิเคราะห์ที่ติดตั้งไว้ก่อนการโหลดใหม่ทุกครั้ง เพื่อให้การพิมพ์ผิดไม่ทำให้ตัวรับฟังที่ให้บริการอยู่ล่ม

จากนั้นทดสอบทอดแบ็กเอนด์จากโฮสต์ส่วนหน้าเอง ในบริบทเครือข่ายเดียวกับที่ Nginx ใช้ การ ping ผ่านอุโมงค์พิสูจน์การเข้าถึงได้ และไม่ได้พิสูจน์อะไรเกี่ยวกับ TLS การอนุญาต หรือแอปพลิเคชัน

สุดท้ายส่งคำขอสาธารณะใหม่ไปยังชื่อโฮสต์จริงพร้อมการตรวจสอบใบรับรองตามปกติ และสังเกตเส้นทางตอบกลับ ไม่ใช่แค่เนื้อหาการตอบกลับ

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

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