OpenVPN auth-user-pass: ไฟล์, stdin หรือพรอมต์? จะเลือกอย่างไร
เลือกระหว่างพรอมต์บนคอนโซล ไฟล์ข้อมูลรับรอง สตรีม และอินเทอร์เฟซการจัดการ จากนั้นแก้ไขข้อผิดพลาดที่ดูเหมือนรหัสผ่านผิดเท่านั้น
สามสิ่งกำหนดว่าควรใช้แหล่งข้อมูลรับรองใด: มีคนนั่งอยู่ที่คอนโซลหรือไม่ ความลับอยู่บนดิสก์ได้หรือไม่ และโปรเซสอื่นต้องตอบพรอมต์หรือไม่ หากเลือกผิด ไคลเอนต์จะรายงานปัญหา stdin ทั้งที่รหัสผ่านถูกต้องสมบูรณ์ และนั่นทำให้คนส่วนใหญ่ไปที่เซิร์ฟเวอร์โดยไม่มีเหตุผล เลือกเส้นทางก่อน ข้อความแสดงข้อผิดพลาดจะมีความหมายก็ต่อเมื่อคุณรู้ว่าเส้นทางใดสร้างมันขึ้นมา
สามเงื่อนไขที่กำหนด
พรอมต์บนคอนโซลเป็นเส้นทางเดียวที่ต้องใช้คน จึงเป็นเส้นทางเดียวที่ล้มเหลวอย่างเงียบ ๆ ในยูนิต systemd หรือคอนเทนเนอร์ หากมีคนเริ่มไคลเอนต์และพิมพ์รหัสผ่าน จะไม่มีไฟล์เกี่ยวข้องและไม่มีอะไรละเอียดอ่อนลงบนดิสก์ หากไคลเอนต์เริ่มตอนบูต พรอมต์เดียวกันจะกลายเป็นการค้าง และการหมดเวลามักปรากฏเป็นความล้มเหลวในการเชื่อมต่อทั่วไป ไม่ใช่ข้อผิดพลาดข้อมูลรับรอง
ไฟล์ข้อมูลรับรองเป็นคำตอบที่ถูกต้องเมื่อโปรเซสเริ่มโดยไม่มีผู้ดูแลและความลับอยู่บนดิสก์ได้ภายใต้ชุดสิทธิ์ที่ควบคุมไว้ เมื่อความลับไม่ควรถูกเขียนเลย - นโยบายเฉพาะตู้เซฟ โทเค็นอายุสั้น - คุณต้องมีสตรีมที่ไคลเอนต์เปิดได้ ซึ่งในทางปฏิบัติหมายถึงการแทนที่โปรเซสหรือตัวช่วยที่เขียนลงไฟล์ descriptor อินเทอร์เฟซการจัดการครอบคลุมกรณีที่สี่: ตัวควบคุมที่จัดการวงจรชีวิตของอุโมงค์อยู่แล้วและตอบคำถามผ่านซ็อกเก็ตได้
- มีคนที่คอนโซล ไม่มีระบบอัตโนมัติ: พรอมต์แบบโต้ตอบ
- เริ่มโดยไม่มีผู้ดูแล เก็บความลับได้: ไฟล์ข้อมูลรับรองโหมด 0600
- เริ่มโดยไม่มีผู้ดูแล เก็บความลับไม่ได้: การแทนที่โปรเซสหรือ fd ที่เอเจนต์ความลับเขียนลงไป
- โปรเซสอื่นเป็นเจ้าของไคลเอนต์: อินเทอร์เฟซการจัดการ
เมื่อมีสองคำตอบที่ใช้ได้ ให้เลือกอันที่เก็บความลับไว้นอก argv และนอกประวัติ shell รหัสผ่านที่ส่งเป็นอาร์กิวเมนต์ของโปรแกรมนั้นผู้ใช้ท้องถิ่นคนใดก็อ่านได้ผ่านรายการโปรเซส และการเปิดเผยนั้นย้อนกลับได้ยาก
แต่ละเส้นทางอินพุตทำอะไรจริง ๆ
--auth-user-pass ตามลำพังจะขอชื่อผู้ใช้และรหัสผ่านที่คอนโซล เมื่อให้ชื่อไฟล์ ไคลเอนต์จะใช้บรรทัดแรกเป็นชื่อผู้ใช้และบรรทัดที่สองเป็นรหัสผ่าน ไม่มีอะไรรองรับเครื่องหมายคำพูด ความคิดเห็น หรือไวยากรณ์คีย์-ค่า นั่นคือรูปแบบทั้งหมด ดังนั้นใครก็ตามที่แก้ไขโดยคาดว่าเป็นไฟล์ INI จะทำให้มันเสีย บล็อก <auth-user-pass> แบบอินไลน์เก็บสองบรรทัดเดียวกันไว้ในโปรไฟล์ ซึ่งทำให้โปรไฟล์เองกลายเป็นความลับและจัดอยู่ในคลาสการจัดการเดียวกับคีย์ส่วนตัว
อีกสองเส้นทางคือสตรีมและซ็อกเก็ต OpenVPN ต้องการเพียงไฟล์ descriptor ที่อ่านได้ จึงรับการแทนที่โปรเซส รายการ /dev/fd ที่รองรับไปป์ หรือชื่อไฟล์ที่เป็น FIFO จริง ๆ อินเทอร์เฟซการจัดการต่างกันโดยธรรมชาติ: ไคลเอนต์ถามโปรเซสผู้ดูแลค่าต่าง ๆ แทนที่จะอ่านเอง ความแตกต่างนั้นสำคัญเมื่อเชื่อมต่อใหม่: ไฟล์ข้อมูลรับรอง FIFO ที่ตัวช่วยความลับเขียนซ้ำได้ และอินเทอร์เฟซการจัดการสามารถจัดหาใหม่ได้โดยไม่มีคน ส่วนการแทนที่โปรเซสทำไม่ได้เพราะ descriptor ถูกใช้หมดในการอ่านครั้งแรก
ไฟล์ต้องมีสองบรรทัดพอดี
รูปแบบความล้มเหลวสองแบบครอบคลุมไฟล์ข้อมูลรับรองที่เสียส่วนใหญ่ ตัวแก้ไขของ Windows ทิ้งการลงท้ายแบบ CRLF ตัวอักษรขึ้นบรรทัดใหม่กลายเป็นส่วนหนึ่งของรหัสผ่าน และเซิร์ฟเวอร์ปฏิเสธค่าที่ดูถูกต้องบนหน้าจอ บรรทัดที่สองว่าง - บรรทัดว่างตรงที่รหัสผ่านควรอยู่ - ทำให้ไคลเอนต์มีรหัสผ่านว่าง ซึ่งเซิร์ฟเวอร์ปฏิเสธแม้ค่าบนหน้าจอดูถูกต้อง บรรทัดใหม่เกินมาหลังรหัสผ่านไม่เป็นอันตราย เขียนไฟล์จาก shell ไม่ใช่จากตัวแก้ไข และตรวจสอบไบต์:
install -m 600 /dev/null /etc/openvpn/client.creds
printf '%s\n%s\n' "$VPN_USER" "$VPN_PASS" > /etc/openvpn/client.creds
od -c /etc/openvpn/client.creds | head -3เส้นทางสำคัญพอ ๆ กับเนื้อหา เส้นทางแบบสัมพัทธ์ถูกแก้เทียบกับไดเรกทอรีทำงานของโปรเซส ซึ่งสำหรับบริการแล้วแทบไม่เคยเป็นไดเรกทอรีที่คุณทดสอบ ใช้เส้นทางแบบสัมบูรณ์ ทำให้ไฟล์เป็นเจ้าของของบัญชีที่รันไคลเอนต์ และคงโหมด 0600 เพื่อให้กลุ่มและผู้อื่นอ่านไม่ได้ คู่มือ OpenVPN 2.6 ระบุรูปแบบที่ยอมรับของไดเรกทีฟ ตรวจสอบก่อนคิดรูปแบบที่สามขึ้นมา
ทดสอบไฟล์ก่อนโทษเซิร์ฟเวอร์ การรัน openvpn --config client.ovpn --auth-user-pass /etc/openvpn/client.creds --verb 4 จะพิมพ์ว่าไฟล์ถูกเปิดหรือไม่ และต่อมาว่าข้อมูลรับรองถูกยอมรับหรือไม่ ซึ่งบอกคุณว่าควรเปลี่ยนชั้นใดต่อไป
สตรีม stdin และข้อผิดพลาดที่เกิดขึ้น
การส่งรหัสผ่านผ่านไปป์เป็นสิ่งแรกที่คนส่วนใหญ่ลองและเป็นเส้นทางที่มีความประหลาดใจมากที่สุด การแทนที่โปรเซสได้ผลเพราะไคลเอนต์ต้องการเพียง descriptor ที่อ่านได้: openvpn --config client.ovpn --verb 3 --auth-user-pass <(printf '%s\n%s\n' "$VPN_USER" "$VPN_PASS") การส่ง /dev/stdin ได้ผลเฉพาะเมื่อ stdin ยังเปิดและอ่านได้ในขณะที่ไคลเอนต์ถาม ซึ่งไม่จริงในไปป์ไลน์ที่ใช้มันไปแล้ว
ERROR: could not read Auth username from stdin มีสาเหตุที่แตกต่างกันสองอย่าง ในไคลเอนต์ 2.3.x บางตัว ข้อความมาจากข้อบกพร่องในการจัดการ stdin ซึ่งรุ่น 2.3 ต่อมาแก้ไข ดังนั้นไคลเอนต์ที่เก่าขนาดนั้นจะดีขึ้นได้ด้วยการอัปเกรดเท่านั้น ในไคลเอนต์ปัจจุบัน ถ้อยคำเดียวกันหมายความว่าไคลเอนต์ขออินพุตจากคอนโซลและ stdin ไม่ใช่เทอร์มินัล - ตรงกับสถานการณ์ในยูนิต systemd งาน cron หรือคอนเทนเนอร์ที่เริ่มโดยไม่มี -t
auth-nocache เพิ่มลูปที่สามที่ดูเหมือนรหัสผ่านเสีย มันทิ้งข้อมูลรับรองออกจากหน่วยความจำหลังใช้ ดังนั้นการเชื่อมต่อใหม่ที่เกิดจากการเปลี่ยนเครือข่ายจึงไม่มีอะไรให้เล่นซ้ำและพรอมต์กลับมา นั่นคือไดเรกทีฟที่ทำงานตามที่ออกแบบ auth-nocache เปลี่ยนสิ่งที่ไคลเอนต์เก็บในหน่วยความจำ ไม่ใช่สิ่งที่เซิร์ฟเวอร์หรือผู้ให้บริการตัวตนคิดเกี่ยวกับบัญชี ดังนั้นการแก้ไขทั้งหมดคือการจัดหาไฟล์เดิมใหม่ ซึ่งอ่านใหม่เมื่อรีสตาร์ต - และสำหรับการเชื่อมต่อที่มีการจัดการ การจัดหานั้นควรมาจากที่เก็บความลับที่ได้รับอนุมัติ ไม่ใช่รหัสผ่านในสคริปต์
อินเทอร์เฟซการจัดการเมื่อโปรเซสอื่นเป็นเจ้าของพรอมต์
ตัวควบคุมที่ดูแลอุโมงค์อยู่แล้วสามารถตอบคำถามของมันได้เช่นกัน เริ่มไคลเอนต์ด้วย --management 127.0.0.1 7505 อ่านบรรทัดสถานะ และเฝ้าดู >PASSWORD:Need 'Auth' username/password ตัวควบคุมตอบด้วย username "Auth" alice ตามด้วย password "Auth" ... ในบรรทัดคำสั่งแยกกัน โทเค็น 'Auth' ทำซ้ำตัวระบุจากคำขอและต้องตรงกัน มิฉะนั้นคำตอบจะถูกเพิกเฉยและไคลเอนต์รอ
ราคาคือเครื่องสถานะที่ตอนนี้คุณเป็นเจ้าของ: ลำดับการเชื่อมต่อ การรีสตาร์ตไคลเอนต์ และซ็อกเก็ตท้องถิ่นที่ผู้ใช้คนอื่นบนโฮสต์ไม่ควรเข้าถึง สิ่งที่คุณซื้อคือข้อมูลรับรองที่จัดหาในเวลาทำงาน โดยไม่มีไฟล์บนดิสก์และไม่มีพรอมต์ที่อาจค้าง หากไม่มีสิ่งอื่นในสแต็กของคุณพูดโปรโตคอลนี้อยู่แล้ว เส้นทางนี้เพิ่มองค์ประกอบที่ต้องบำรุงรักษา
ความล้มเหลวที่ดูเหมือนปัญหาข้อมูลรับรอง
การจับมือ TLS ที่ไม่มีวันเสร็จสิ้น หรือ VERIFY ERROR: depth=0, error=certificate signature failure เป็นปัญหาความเชื่อถือและเกิดขึ้นก่อนที่จะมีการอ่านชื่อผู้ใช้ใด ๆ ไคลเอนต์ proto tcp ที่ชี้ไปยังตัวรับฟัง UDP ก็ล้มเหลวที่จุดเดียวกัน: แพ็กเก็ตไม่เคยไปถึงขั้นตอนการรับรองความถูกต้อง ดังนั้นรหัสผ่านทุกตัวจึงดูผิดเพราะความสัมพันธ์ รักษาการตรวจสอบใบรับรองให้เปิดอยู่ก่อนส่งข้อมูลรับรอง พรอมต์ที่คุ้นเคยไม่ได้บอกอะไรเกี่ยวกับว่าใครได้รับรหัสผ่าน ตรวจสอบห่วงโซ่ใบรับรอง ชื่อสามัญ และการขนส่งก่อนแตะข้อมูลรับรอง
ข้อผิดพลาดของ shell ในสคริปต์ที่ประกอบโปรไฟล์เป็นร่องรอยเท็จอีกร่องรอยหนึ่ง การขาดช่องว่างภายใน [ ! -f "$file" ] ทำให้เกิด command not found สคริปต์หยุดแต่เนิ่น ๆ โปรไฟล์ไม่ถูกสร้างใหม่ และไคลเอนต์เริ่มด้วยอันก่อนหน้า ข้อผิดพลาดนั้นเป็นของ shell ไม่ใช่การรับรองความถูกต้อง และไม่ว่าคุณจะพิมพ์รหัสผ่านใหม่กี่ครั้งมันก็ไม่หายไป
ไม่ใช่ทุกพรอมต์ในโฟลว์นี้คือข้อมูลรับรองนี้ วลีรหัสผ่านของคีย์ส่วนตัว การเข้าสู่ระบบผ่านพร็อกซี HTTP และรหัสผ่านของบริการเป็นความลับสามอย่างที่ถามในระยะต่าง ๆ และวลีรหัสผ่านของคีย์มักจัดการโดยไคลเอนต์หรือเอเจนต์ไม่ใช่ auth-user-pass หากพรอมต์ปรากฏก่อนที่เซสชัน TLS จะตั้งขึ้น คุณกำลังตอบคำถามผิดและไม่มีการรีเซ็ตบัญชีใดช่วยได้ เมื่อตัดพรอมต์ที่ผิดออกแล้ว การตรวจสอบที่เหลือเริ่มจากชั้นที่เก่าที่สุด:
- อ่านข้อผิดพลาดโปรโตคอลแรก ไม่ใช่บรรทัดข้อมูลรับรองสุดท้าย: TLS จากนั้นการรับรองความถูกต้อง จากนั้น push
- ยืนยันว่าไฟล์มีสองบรรทัด ไม่มีการขึ้นบรรทัดใหม่แบบ carriage return และอ้างอิงด้วยเส้นทางแบบสัมบูรณ์
- ยืนยันว่าโปรไฟล์และบรรทัดคำสั่งไม่ขัดแย้งกันเกี่ยวกับไดเรกทีฟ
- แยก "ข้อมูลรับรองได้รับการยอมรับ" ออกจาก "ทราฟฟิกไหล" - การกำหนดเส้นทางและ DNS เป็นชั้นที่มาทีหลัง
เมื่อไคลเอนต์ถึงระยะ push ให้หยุดดูข้อมูลรับรองและเริ่มอ่านเส้นทางและเซิร์ฟเวอร์ DNS ที่เซิร์ฟเวอร์ส่งมา นั่นคือปัญหาถัดไป และไม่เกี่ยวข้องกับรหัสผ่านที่คุณเพิ่งแก้ไข