כשל client-connect ב-OpenVPN: האם ניקוי ב-disconnect מובטח?
לקוח שנדחה אינו מבטל חוק חומת אש או רשומה שנכתבה. השוו את חוזה ה-callback עם הנסיגה שה-hook שלכם חייב לבצע בעצמו.
כאשר סקריפט client-connect מסתיים בקוד שאינו אפס, OpenVPN דוחה את הלקוח, ומפעילים מניחים בדרך כלל שסקריפט client-disconnect המתאים יסדר הכול. לעיתים קרובות הוא כלל אינו רץ, מפני שעיבוד ה-disconnect תלוי במה ששלב ה-connect הספיק להשלים. התייחסות לדחייה ולנסיגה כמנגנון אחד היא מה שהופך התחברות כושלת לחוק חומת אש שדלף או לרשומה יתומה. אלו שתי התחייבויות נפרדות, ורק אחת מהן אוטומטית. קראו את נתיב היציאה של ה-hook עצמו לפני שאתם מאשימים את השרת בהשארת מצב.
יציאה שאינה אפס דוחה את הלקוח; היא אינה מבטלת את העבודה שלכם
סטטוס היציאה של client-connect הוא החלטת קבלה. אפס פירושו לקבל, כל דבר אחר פירושו לפרק את המופע לפני שהוא מבוסס לחלוטין. שום דבר בחוזה הזה אינו בודק מה הסקריפט כבר עשה לעולם החיצון. אם הסקריפט הוסיף חוק iptables, כתב שורה או העלה מונה, השינויים האלה שורדים את הדחייה.
אותה אסימטריה חלה על תוספים, שבהם כמה פונקציות connect יכולות לרוץ עבור מופע אחד. כשל מאוחר אינו מבטל בדיעבד הצלחה מוקדמת, ולפונקציה המוקדמת אין דרך לדעת שפונקציית אחות נכשלה.
הדחייה אוטומטית, הניקוי לא
המדריך של OpenVPN 2.6 קושר את עיבוד client-disconnect להצלחת עיבוד ה-connect הקודם. חריגה מתועדת מכסה שרשראות מדורגות: אם שלב connect אחד כבר החזיר הצלחה, שלבי ה-disconnect המתאימים עדיין רצים בעת פירוק המופע, גם אם שלב connect מאוחר נכשל. החריגה הזו צרה ותלויה במספר ה-callbacks המוגדרים, ולכן בדיקת סקריפט shell אחד אינה אומרת לכם דבר על שרשרת.
מפו את התצורה בפועל לפני שמתכננים סביבה: איזה סקריפט, איזה תוסף ובאיזה סדר הם רצים. הטבלה שלמטה מפרטת מה הגדרה חצי-גמורה משאירה מאחור.
| What the hook did before failing | What the client sees | What stays behind |
|---|---|---|
| Created a firewall entry | A rejection, with no tunnel | A rule matching an address nobody owns |
| Wrote a state row | A rejection, with no tunnel | A record of a session that never started |
| Allocated a tunnel address | A rejection, with no tunnel | An address marked busy until something releases it |
כל שורה מסתיימת באותו אופן עבור הלקוח ובדרך שונה עבור המארח. הלקוח אינו יכול לדעת איזו מהן התרחשה, ולכן ה-hook חייב לבטל את מה שעשה לפני שיחזיר את סטטוס הכשל.
פצו בתוך הסקריפט שנכשל
אמתו כל מה שאפשר לפני נגיעה במצב משותף: שדות תעודה, זמינות הכתובת, הקבוצה שאליה שייך הלקוח. הדחייה הזולה ביותר היא זו שמתרחשת לפני כל הקצאה. ברגע שההגדרה משתרעת על כמה פעולות חיצוניות, נהלו רשומה של אלו הושלמו ובטלו אותן בסדר הפוך לפני החזרת סטטוס הכשל.
מקרה קונקרטי: הסקריפט יוצר רשומת חומת אש לכל לקוח, ואז נכשל בכתיבת שורת המצב שמתארת אותה. הענף הכושל צריך להסיר את הרשומה שהוא זה עתה יצר ואז לצאת בקוד שאינו אפס. המתנה לאירוע disconnect שאולי לא יתוזמן לעולם היא הדרך שבה חוק שורד את הסשן שלו נועד.
הפכו את הפיצוי לאידמפוטנטי ולבעלים
הניקוי חייב להיות בטוח להרצה פעמיים ומדויק לגבי מה שהוא רשאי למחוק. אירוע מאוחר מניסיון קודם לא אמור להסיר הקצאה של סשן חדש יותר רק מפני ששניהם חולקים שם משותף או כתובת מנהרה ממוחזרת. תעדו את ה-handle או מזהה הפעולה הספציפי יחד עם המשאב, ומחקו לפי המפתח הזה ולא לפי שם. טרנזקציות מסד נתונים עוזרות רק בשינויים של מסד הנתונים עצמו; חוק חומת אש מחוץ לגבול הזה דורש שלב פיצוי מפורש.
הניחו שהתהליך יכול למות בין השינוי לניקוי, והוסיפו מתאם שמשווה את המצב החיצוני לרשימת הלקוחות שהתקבלו לפי לוח זמנים.
בדקו את שלושת הנתיבים שבאמת שונים
תרגלו שלושה מקרים: דחייה לפני שהוקצה משהו, דחייה לאחר הקצאה חלקית, וסשן שהתקבל באופן רגיל ושמסתיים מאוחר יותר. שלושת אלה מייצרים שורות יומן שונות ושאריות שונות. אמתו את המצב החיצוני אחרי כל אחד מהם, ולא רק את הודעת הכשל של הלקוח, מפני שכשל הלקוח הוא התוצאה הצפויה בשניים מהשלושה.
תעדו את שלב ה-hook, את סטטוס היציאה ומה הוקצה או שוחרר, בלי פרטי ההתחברות. יומן hook שמנקוב בשם המשאב שהוא יצר הוא מה שהופך חוק שדלף לניתן לאיתור שישה חודשים לאחר מכן.