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

התחל ממה שהפותר מחזיר

לקוח מבקש vpn-a.example ומקבל כתובת; הכתובת הזו ועוד פורט הם כל מה שהוא מחייג אליו. השם נצרך מקומית, בזמן החיפוש, והוא לעולם אינו הופך לשדה על החוט, ולכן המאזין בצד השני אינו יכול לראות באיזו תווית נעשה שימוש. כוון את vpn-a.example ואת vpn-b.example למארח אחד ושתי החיפושים ייתנו את אותו יעד, ולכן הדמון סופג את שני החיבורים למופע אחד בלי לרשום הבדל. פרופילים מבוססי כתובת מתנהגים בדיוק כמו מבוססי שם, מאותה סיבה.

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

הבורר היחיד שנחשב נוסע בתוך החבילה

HTTP מטפל בזה עם כותרת Host, ו-TLS עם הרחבת שם שרת ב-ClientHello, וכך כתובת אחת משרתת אתרים רבים מאחורי מאזין אחד. שניהם עובדים משום שהפרוטוקול על החוט נושא בקשה לשירות מסוים. העובדה הרלוונטית לגבי OpenVPN היא שאף אחד מהשדות האלה אינו נוכח בשכבה שקופסה מתווכת בוחנת.

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

היכן מפצל פרוטוקולים נעצר

מרבב מהסוג שפרויקט sslh מתעד מאזין בפורט אחד, בוחן את הבייטים הראשונים ומוסר את החיבור לכל backend שמדבר את הפרוטוקול שזיהה. זה ערך אמיתי כאשר פורט אחד חייב לשרת SSH, HTTPS ו-OpenVPN יחד.

sslh מחזיק בדיקה נפרדת לכל פרוטוקול, ובדיקת ה-TLS שלו יכולה לשלוף שם שרת או ערך ALPN מ-ClientHello אמיתי. זיהוי OpenVPN מקורי הוא סיווג שונה מקריאת הרחבת TLS, והשניים אינם מתחברים, משום שלקוח שלעולם אינו שולח ClientHello לשקע ההאזנה משאיר את הבדיקה בלי דבר לקרוא. לכן כלל שמתאים ל-OpenVPN משיב “זהו לקוח OpenVPN” ואינו ממשיך הלאה. שני backends של OpenVPN מאחורי הכלל הזה אינם ניתנים להבחנה, לחזית אין בסיס לבחירה, וכל הפעלה נוחתת באותו מקום. ריבוב חולק פורט אחד בין שירותים שונים; הוא אינו עושה אחסון וירטואלי בתוך שירות אחד.

סידורים שבאמת מפרידים בין שני שרתים

ההפרדה ההוגנת הזולה ביותר היא כתובת ציבורית שנייה, אחת לכל מופע, והפרופילים הקיימים כבר נושאים את הנדרש משום שהם מצביעים לכתובות שונות. הבא בתור הוא פורט שונה באותה כתובת, שאינו עולה דבר מלבד מספר בכל פרופיל. TCP ו-UDP מחזיקים טבלאות פורטים נפרדות, ולכן 1194/tcp ו-1194/udp הם שקעים לא קשורים ואחד מכל אחד יכול להתקיים יחד על אותו מארח; זו הפרדת תעבורה ולא ניתוב לפי שם, ולעיתים קרובות טועים ביניהם. שתי האפשרויות שורדות סקירת חומת אש, משום שהכלל מתאים למשהו שהחבילה אכן מכילה.

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

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

הוכח את הבורר לפני שאתה בונה

תן שם לשדה שיזהה backend, ואז אשר שהלקוח אכן שולח אותו; אם התשובה היא כתובת התוכנית מחזיקה, ואם זה שם שרת בתוך לחיצת יד שלעולם אינה מגיעה למאזין, שום עבודת DNS לא תציל אותה. שלוש פקודות מכריעות את צד ההאזנה במהירות.

ss -lntup | grep -E ':443|:1194'
openssl s_client -connect vpn-a.example:443 -servername vpn-a.example </dev/null | head -n 20
journalctl -u openvpn-server@server-a -n 20 --no-pager

שורת openssl נותנת תעודה רק היכן שמשהו בכתובת הזו מסיים TLS; מול מאזין OpenVPN מקורי היא נתקעת או נכשלת, וזה כשלעצמו התשובה לגבי מה השכבה החיצונית חושפת. שורת journal, כשהיא נקראת בכל backend, מראה איזה מופע קיבל את ההפעלה. יומני החזית רושמים מה סווג ולעולם לא מה היה מכוון, ולכן ה-backend נשאר הסמכות היחידה לגבי היכן המשתמש נחת.

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

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