OpenVPN หลัง stunnel: ส่งต่อตัวห่อ ไม่ใช่พอร์ต 1194
ผู้ฟังสองตัว พอร์ตสามพอร์ต ตรวจว่า การส่งต่อไปลงเอยที่ไหน ตัวห่อผูกที่ไหน เชื่อมต่อไปที่ไหน การทดสอบในเครื่องข้ามอะไรไป และทั้งสองอัตลักษณ์
วางตัวห่อ TLS ไว้หน้า OpenVPN แล้วการติดตั้งจะมีผู้ฟังตัวที่สอง ซึ่งมีเพียงตัวเดียวเท่านั้นที่ควรหันสู่อินเทอร์เน็ต เมื่ออุโมงค์ทำงานจากเครื่องเดียวกันแต่ล้มเหลวจากภายนอก การส่งต่อมักเล็งไปที่ตัวผิดในสองตัวนั้น สิ่งที่ต้องเรียงให้ตรงกันคือห่วงโซ่ กฎสาธารณะไปถึงตัวห่อ และการเชื่อมต่อแบ็กเอนด์ของตัวห่อเองไปถึงซ็อกเก็ต VPN จริง มีการกระโดดสี่ครั้งที่พาห่วงโซ่นี้ และครั้งใดครั้งหนึ่งอาจยุติการสอบสวนก่อนเวลา
สองซ็อกเก็ต สองเจ้าของ หนึ่งในนั้นเป็นสาธารณะ
ตัวห่อปิด TLS สำหรับการเชื่อมต่อภายนอก และส่งกระแสไบต์ที่ถอดรหัสแล้วไปยังผู้ฟัง OpenVPN TCP ธรรมดา สิ่งเหล่านี้คือ ซ็อกเก็ตแยกกันที่มีไฟล์กำหนดค่าแยกกัน ดังนั้นการแก้ไขอันหนึ่งจึงทิ้งอีกอันไว้ไม่ถูกแตะต้องและยังดูเหมือนสุขภาพดี นอกจากนี้ยังหมายความว่าทั้งสองอาจแยกจากกันทุกครั้งที่มีการเปลี่ยนอันหนึ่งโดยไม่อีกอัน
การทำงานจากเครื่องเดียวกันจึงพิสูจน์ได้น้อยมากเกี่ยวกับเส้นทางสาธารณะ การเชื่อมต่อตรงไปยังพอร์ตดั้งเดิมข้ามตัวห่อ การเชื่อมต่อกับตัวห่อที่ที่อยู่ LAN ของมันข้ามการแปล และมีเพียงการจัดวางฝั่งไคลเอนต์เต็มรูปแบบเท่านั้นที่ฝึกสิ่งที่ผู้ใช้ภายนอกทำ ชื่อก็ต้องอ่านอย่างระมัดระวังเช่นกัน เพราะ localhost หมายถึงโฮสต์ที่รันคอมโพเนนต์นั้นโดยเฉพาะ ไม่ใช่โฮสต์ที่คุณบังเอิญพิมพ์อยู่ เก็บความแตกต่างนี้ไว้ในบันทึกของคุณ เพราะการทดสอบที่บันทึกโดยไม่ระบุการกระโดดที่มันครอบคลุมเชิญชวนให้เปรียบเทียบสองสิ่งที่ไม่เหมือนกัน
การส่งต่อไปลงเอยที่ไหนจริง ๆ
ห่วงโซ่นี้จับไว้เป็นสี่การกระโดดได้ง่ายกว่าการจับเป็นหมายเลขพอร์ตสามหมายเลข เพราะหมายเลขเดียวกันอาจหมายถึงสิ่งต่างกันในแต่ละปลาย อ่านตารางทีละแถว แต่ละแถวคือซ็อกเก็ตหนึ่งตัวที่ต้องรับการเชื่อมต่อแล้วเปิดตัวถัดไป เมื่อแถวหนึ่งเล็งไปที่คู่ผิด อาการในคอลัมน์สุดท้ายคือสิ่งที่ผู้ใช้รายงาน
| Hop | Listens on | Must reach | Symptom when it points elsewhere |
|---|---|---|---|
| Client machine | A local plaintext port | The wrapper's public TLS address | Plain text offered to a TLS endpoint |
| Router forward | Public TCP 443 | The wrapper host on its translated port | Wrapper records no connection at all |
| Wrapper, server mode | Its accepted port, 8443 here | The native VPN socket on loopback | Handshake completes, then silence |
| Native VPN listener | Loopback TCP port | Its peer, over TCP only | A UDP socket behind a byte-stream relay |
กฎที่เล็งไปที่พอร์ต VPN ดั้งเดิมแทนที่จะเป็นตัวห่อจะข้ามการปิด TLS ไปทั้งหมด และนี่คือรูปแบบที่พบบ่อยที่สุดของความผิดพลาดนี้ ไคลเอนต์เปิดการจับมือกับผู้ฟังที่คาดหวังการใส่กรอบแบบ OpenVPN การเชื่อมต่อจึงอาจเปิดขึ้นแล้วหยุดนิ่ง ตรวจสอบพอร์ตที่แปลแล้วกับไฟล์กำหนดค่าของตัวห่อไม่ใช่กับความจำ เพราะคนต่อไปที่แก้ไฟล์นั้นอาจเลือกหมายเลขอื่น สองคำสั่งด้านล่างแสดงทั้งสองด้านของคำถามนี้พร้อมกัน
grep -nE '^[[:space:]]*(accept|connect)' /etc/stunnel/stunnel.conf
ss -lntp | grep -E ':443|:8443|:1194'
ที่อยู่ผูกที่ปฏิเสธคุณ
บรรทัด accept ที่ผูกกับ 127.0.0.1 ตอบเฉพาะโฮสต์เอง ดังนั้นการส่งต่อที่มาถึงที่อยู่ LAN จึงเชื่อมต่อไม่ได้และไคลเอนต์เห็นการปฏิเสธที่ฟังดูเหมือนปัญหาตัวกรองแพ็กเก็ต มองที่ซ็อกเก็ตก่อนแตะไฟร์วอลล์ เพราะซ็อกเก็ตคือคำตอบที่ถูกกว่า จะผูกตัวห่อกับอินเทอร์เฟซที่การส่งต่อเล็ง หรือเก็บไว้บน loopback แล้วทำให้การส่งต่อไปถึง loopback ผ่านสิ่งที่ทำงานได้จริง สองอย่างนี้เป็นสองการออกแบบ และแต่ละแบบต้องมีกฎของตัวเอง
การผสมทั้งสองอย่างเข้าด้วยกันคือวิธีที่ตัวห่อที่ทำงานอยู่กลายเป็นสิ่งที่เข้าถึงไม่ได้จากภายนอกในขณะที่ทุกไฟล์ดูถูกต้อง การตรวจสอบด้านล่างเป็นคำสั่งเดียว และมันตอบคำถามที่การทบทวนกฎไฟร์วอลล์ตอบไม่ได้ หากผลลัพธ์แสดงที่อยู่ loopback ในขณะที่การส่งต่อเล็งที่อยู่ LAN การแก้ไขเป็นของบรรทัด accept ไม่ใช่ตัวกรองแพ็กเก็ต
ss -lntp '( sport = :8443 )'
การทดสอบในเครื่องพิสูจน์อะไรจริง ๆ
ความสำเร็จที่บันทึกไว้โดยไม่ระบุการกระโดดที่มันครอบคลุมไม่ใช่หลักฐานเกี่ยวกับการกระโดดอื่น จดว่าการทดสอบใช้ที่อยู่ใดและโปรเซสใดตอบกลับ จากนั้นทำซ้ำจากภายนอกเครือข่ายไม่ใช่จากโทรศัพท์บน Wi-Fi เดียวกัน เพราะพฤติกรรมการวกกลับต่างกันระหว่างเราเตอร์ และการเชื่อมต่อที่ไม่เคยออกจาก LAN ก็สามารถทำให้ไคลเอนต์พอใจได้โดยที่การส่งต่อไม่เกี่ยวข้องเลย มีเพียงความพยายามครั้งที่สองนั้นเท่านั้นที่ฝึกเส้นทางที่คนนอกใช้
เมื่อความพยายามล้มเหลว ให้ติดตามการเชื่อมต่อครั้งเดียวในทั้งสองล็อก คือบรรทัดที่รับและเชื่อมต่อของตัวห่อ แล้วตามด้วยล็อกเซสชันของดีมอน VPN เอง เส้นแบ่งแรกที่ไม่บันทึกอะไรเลยคือที่ที่การเปลี่ยนแปลงครั้งต่อไปควรอยู่ เมื่อ OpenVPN เริ่มเปรียบเทียบตัวเลือกของคู่สัญญา ให้ตัดสินข้อความเหล่านั้นตามกฎของ implementation นั้น คำเตือนเรื่อง link-mtu ไม่สอดคล้องไม่ได้ถึงตายโดยอัตโนมัติ การตรึง link-mtu ไว้ที่ 1500 ไม่ได้พิสูจน์ว่าการแบ่งส่วนหยุดแล้ว และการเจรจาไซเฟอร์เป็นของกำหนดค่าภายใน OpenVPN ไม่ใช่การส่งต่อของเราเตอร์
nc -vz 127.0.0.1 1194
journalctl -u stunnel -n 50 --no-pager
การตรวจเหล่านั้นครอบคลุมซ็อกเก็ตแบ็กเอนด์และล็อกของตัวห่อ ซึ่งแยกแบ็กเอนด์ที่ถูกปฏิเสธออกจากการจับมือที่ถูกปฏิเสธ รันระหว่างที่มีความพยายามครั้งเดียวกำลังดำเนินอยู่ ไม่ใช่หลังจากนั้น เวลาประทับจากการพยายามครั้งเดียวคือสิ่งที่ทำให้การเปรียบเทียบน่าเชื่อถือ
สองอัตลักษณ์ สองลำดับความล้มเหลว
ใบรับรองภายนอกเป็นของตัวห่อและอัตลักษณ์ภายในเป็นของ OpenVPN และอาจมาจากหน่วยงานต่างกัน การปิดการตรวจสอบใดตัวหนึ่งเพื่อให้เส้นทางดูสุขภาพดีคือการแลกการป้องกันจริงกับผลลัพธ์ด้านภาพ เก็บการตรวจสอบทั้งสองไว้และดีบักตามลำดับ เพราะลำดับของข้อความบ่งชี้ชั้น ปัญหาใบรับรองของตัวห่อปรากฏก่อนการเจรจาตัวเลือก VPN ใด ๆ ส่วนความล้มเหลวของอัตลักษณ์ภายในจะปรากฏเฉพาะเมื่อกระแสถูกสถาปนาแล้ว
คู่มือ stunnel กำหนด accept, connect และโหมดไคลเอนต์อย่างแม่นยำ และส่วนเหล่านั้นตอบคำถามเรื่องซ็อกเก็ตได้เร็วกว่าการเดา เมื่อยังบอกไม่ได้ว่าชั้นใดตอบ ให้อ่านใบรับรองที่พอร์ตสาธารณะนำเสนอด้วยคำสั่งด้านล่าง การมีใบรับรองของตัวห่อในผลลัพธ์หมายความว่าการส่งต่อไปถึงตัวห่อ และความเงียบหรือข้อผิดพลาดของโปรโตคอลหมายความว่าไม่ถึง ความแตกต่างเดียวนี้ประหยัดเวลากว่าการตรวจอื่นใดที่นี่
openssl s_client -connect vpn.example:443 -servername vpn.example </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer