ה-VPN פעיל, לוח הבקרה הפנימי נפתח בדפדפן, וסקריפט ה-Python עדיין זורק שגיאת פרוקסי. בתמונה הזו אין שום דבר לא תקין במנהרה: המפרש קורא כתובת פרוקסי מהסביבה שלו ושולח לשם את הבקשה. ספר זה קורא את הסביבה שהתהליך רואה בפועל, מיישם את ההחרגה הצרה ביותר ורק לאחר מכן שוקל מדיניות הפעלה מפורשת. כל שלב מסתיים בפקודה שהפלט שלה מחליט אם להמשיך.

קראו את הסביבה שהתהליך רואה בפועל

הגדרות הפרוקסי של שולחן העבודה, הייצואים ב-shell ושורות ה-Environment= של יחידת systemd הם שלושה מקורות שונים, ו-Requests קורא רק את האחרון שהוא יורש. הריצו את האבחון שלמטה באותו shell, מכולה או יחידה שמריצים את הסקריפט הכושל, ולא במסוף האינטראקטיבי שלכם. משתנה שיוצא ב-shell ההתחברות שלכם אינו נראה לתהליך במכולה, והפער הזה מסביר דיווחים רבים על פרוקסי שמופיע משום מקום. שתי הפקודות מדפיסות את אותם נתונים ברמות פירוט שונות.

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 עבור הכתובת האמיתית, לא עבור שם מארח שהקלדתם מהזיכרון.

הפרידו בין שתי השכבות בראש. עקיפת פרוקסי ה-HTTP אינה עוקפת את ה-VPN, כי החלטת הפרוקסי מתקבלת בספרייה והחלטת המנהרה בטבלת הניתוב של הליבה.

כבו את trust_env רק כמדיניות הפעלה מכוונת

אם כל המשימה אמורה להתעלם מתצורת הסביבה, בנו את ההפעלה באופן מפורש ותעדו את ההחלטה בקוד. זו מדיניות, לא עקיפה, והיא שייכת ל-diff שמבקר יכול לראות. הדוגמה שלמטה שומרת על פסק זמן, נתיב 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= ברמת היחידה על פני ייצוא שחי רק בהיסטוריית המסוף שלכם. שני השינויים נראים בסביבת התהליך, ולכן הפעלה מחדש מחזירה אתכם בדרך כלל להתנהגות הישנה.

סיימו באימות ארבעה דברים: ההגדרות הממוזגות לכתובת הפרטית לא מציגות פרוקסי, התעודה מאומתת מול ה-CA המיועד, הבקשה מחזירה את הסטטוס שאתם מצפים לו, והשינוי נשמר במקום שניתן לשחזר. סקריפט ירוק עם סביבה בלתי מוסברת הוא תיקון שיישבר שוב במכונה הבאה. הפקודות בספר הזה הן אלה להדביק בכרטיס.

מאמרים קשורים