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

อ่านสภาพแวดล้อมที่โปรเซสมองเห็นจริง

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

env | grep -i -E 'proxy|requests_ca_bundle|curl_ca_bundle'
python3 -c "import os;[print(k,'=',v) for k,v in sorted(os.environ.items()) if 'proxy' in k.lower()]"

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

ถาม Requests ว่าจะทำอะไรกับ URL ที่ระบุ การเรียกนี้แสดงค่าพร็อกซี verify และ cert ที่รวมแล้วสำหรับคำขอนั้นโดยไม่ส่งแพ็กเก็ต

python3 -c "import requests;s=requests.Session();print(s.merge_environment_settings('https://api.corp.example/status',{},None,None,None))"

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

แยกโฮสต์ส่วนตัวออกก่อนที่คุณจะปิดสิ่งใด

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

export NO_PROXY="${NO_PROXY:+$NO_PROXY,}api.corp.example,.corp.example"
python3 -c "import requests;print(requests.utils.should_bypass_proxies('https://api.corp.example/status',None))"

การจับคู่เป็นไปตามส่วนต่อท้ายของโฮสต์ ดังนั้น .corp.example ครอบคลุมทั้งโดเมน ในขณะที่ api.corp.example ครอบคลุมชื่อเดียว ดาวแบบเปล่าเปลือยปิดพร็อกซีสำหรับทุกโฮสต์และแทบไม่เคยเป็นสิ่งที่งานโปรดักชันต้องการ การที่รายการ CIDR จะถูกนับถือหรือไม่ขึ้นอยู่กับเวอร์ชันของ Requests และ urllib3 ในอิมเมจ ดังนั้นทดสอบรายการที่แน่นอนแทนการสมมติว่ามันทำงาน ตรวจสอบผลลัพธ์ของ should_bypass_proxies กับ URL จริง ไม่ใช่ชื่อโฮสต์ที่พิมพ์จากความจำ

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

ปิด trust_env เฉพาะในฐานะนโยบายเซสชันที่ตั้งใจ

หากทั้งงานต้องเพิกเฉยต่อการกำหนดค่าสภาพแวดล้อม ให้สร้างเซสชันอย่างชัดเจนและบันทึกการตัดสินใจในโค้ด นั่นคือ นโยบาย ไม่ใช่ทางลัด และควรอยู่ใน diff ที่ผู้ตรวจสอบมองเห็น ตัวอย่างด้านล่างคงเวลา timeout พาธ CA ที่ชัดเจน และการตรวจสอบสถานะ

import requests

with requests.Session() as session:
    session.trust_env = False
    response = session.get(
        "https://api.corp.example/status",
        timeout=(5, 20),
        verify="/etc/ssl/certs/corp-ca.pem",
    )
    response.raise_for_status()

การปิด trust_env เปลี่ยนสามพฤติกรรมพร้อมกัน และมีเพียงหนึ่งเดียวที่เกี่ยวกับพร็อกซี Requests หยุดอ่านข้อมูลรับรองของ .netrc และตัวแปร REQUESTS_CA_BUNDLE กับ CURL_CA_BUNDLE หยุดมีผลกับ verify ซึ่งจะถอยกลับไปใช้ที่เก็บใบรับรองที่มาพร้อมกัน หากองค์กรของคุณสิ้นสุด TLS ด้วย CA ของตัวเอง การถอยกลับนั้นจะทำให้เกิดข้อผิดพลาดการตรวจสอบที่ไม่ดูเกี่ยวข้องกับการเปลี่ยนแปลงที่คุณเพิ่งทำ การส่ง verify อย่างชัดเจนคือสิ่งที่แยกผลกระทบสองอย่างออกจากกัน

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

ตรวจสอบชั้นที่ล้มเหลวและเก็บทางย้อนกลับ

ความล้มเหลวสามแบบดูเหมือนกันในบันทึกและไม่มีอะไรเหมือนกัน: พร็อกซีที่ไม่ถึงปลายทางโยน ProxyError หรือคืน 502 ปัญหาความเชื่อถือโยน SSLError และการปฏิเสธของแอปพลิเคชันคืน 401 หรือ 403 ทำซ้ำคำขอนอก Requests เพื่อดูว่าคุณมีแบบไหน คำสั่งด้านล่างจะลบพร็อกซี ทดสอบห่วงโซ่ใบรับรอง แล้วทำซ้ำการเรียกผ่านไลบรารี

curl -sv --noproxy '*' https://api.corp.example/status -o /dev/null
openssl s_client -connect api.corp.example:443 -servername api.corp.example </dev/null
python3 -c "import requests;r=requests.get('https://api.corp.example/status',timeout=10);print(r.status_code)"

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

การย้อนกลับคือหนึ่งบรรทัดในทั้งสองทิศทาง: ลบการยกเว้น หรือพลิก trust_env กลับ บันทึกค่า NO_PROXY ก่อนหน้าไว้ก่อนเพิ่ม และเลือกสคริปต์ห่อหรือรายการ Environment= ระดับยูนิตแทนการส่งออกที่อยู่ในประวัติเทอร์มินัลของคุณเท่านั้น การเปลี่ยนแปลงทั้งสองมองเห็นได้ในสภาพแวดล้อมของโปรเซส ดังนั้นการรีสตาร์ทมักทำให้คุณกลับไปสู่พฤติกรรมเดิม

ปิดท้ายด้วยการยืนยันสี่สิ่ง: การตั้งค่าที่รวมแล้วสำหรับ URL ส่วนตัวไม่แสดงพร็อกซี ใบรับรองตรวจสอบกับ CA ที่ตั้งใจ คำขอคืนสถานะที่คุณคาดหวัง และการเปลี่ยนแปลงถูก commit ไว้ในที่ที่ทำซ้ำได้ สคริปต์สีเขียวที่มีสภาพแวดล้อมอธิบายไม่ได้คือการแก้ไขที่จะพังอีกในเครื่องถัดไป คำสั่งในคู่มือนี้คือสิ่งที่ต้องวางในตั๋ว

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