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

לבסוף שלח בקשה ציבורית חדשה לשם המארח האמיתי עם אימות אישור רגיל, וצפה בנתיב התגובה ולא רק בגוף התגובה.

שמור את שתי התצפיות יחד עם התצורה הפרוסה, ותעד מה קורה כשהמנהרה אינה זמינה. הפרונט צריך להחזיר שגיאת upstream מבוקרת, ולא ליפול בשקט לבק־אנד אחר שאיש לא תיעד. 502 עם שורת לוג ברורה הוא תוצאה טובה. דף שמור ממטמון שמקורו לא מכוון אינו כזה.

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