למה וו OpenVPN לא מוצא את הקובץ שליד הסקריפט שלו
נתיבים יחסיים בוו נפתרים מול התהליך, לא מול הסקריפט. עגנו אותם עם __file__, ואז בדקו מרחב שמות וזהות לפני הרשאות.
ווים שרצים בלי שגיאה ובכל זאת מדווחים על קובץ חסר בדרך כלל מסתכלים במקום הלא נכון, ולא נכשלים בתפקידם. נתיבים יחסיים בסקריפט נפתרים מול תיקיית העבודה של התהליך, ושירות מתחיל עם תיקיית עבודה שלא בחרתם. הקובץ שליד הסקריפט והקובץ שהתהליך פותח הם אותו דבר רק כשמשהו מעגן אותם. הפער הזה מסביר חלק גדול מכשלי ווים שמפרשים אותם בטעות כבעיות הרשאה.
בדקו את הנתיב לפני ההרשאות, כי שגיאת הרשאה על הקובץ הלא נכון היא עדיין הקובץ הלא נכון.
נתיב יחסי נפתר מול התהליך, לא מול הקובץ
כשסקריפט קורא ל-open("policy.json"), הגרעין פותר את השם מול תיקיית העבודה הנוכחית של התהליך. שום דבר ממיקום הסקריפט עצמו לא משתתף בחיפוש הזה. OpenVPN מתחיל ווים מתיקייה שהוא בוחר בעצמו, והאפשרות cd יכולה לשנות אותה, ולכן סקריפט שנבדק ידנית מהתיקייה שלו מתנהג אחרת כשהדמון מריץ אותו. הבדיקה הידנית עוברת, הרצת השירות נכשלת, ושורת הלוג מזכירה רק את השם החסר.
פייתון חושפת את מיקום הסקריפט כ-__file__, וזה מה שמאפשר עיגון. Path(__file__).resolve().parent / "policy.json" בונה נתיב מהקובץ ולא מהתהליך. השורה האחת הזו מסירה את התלות בתיקיית העבודה בלי לשנות דבר בהרשאות.
עגנו את הנתיב בכוונה, ואז בדקו מה resolve עושה
resolve() עוקב אחרי קישורים סמליים, ולכן התקנה שמקשרת את הוו למקומו מעגנת ביעד הקישור, לא בתיקייה שמחזיקה את הקישור. זה בדרך כלל מה שרוצים בפריסה עם גרסאות, וזה שגוי בפריסה שמצפה לנתונים לצד הקישור הסמלי. החליטו מה הפריסה מתכוונת, וכתבו את הקוד בהתאם במקום לגלות את ההבדל בייצור. Path(__file__).parent בלי resolve() שומר על התיקייה של הקישור עצמו.
אם הנתונים שייכים למופע ולא לתוכנית, קחו נתיב מוחלט מהתצורה. קידוד קשיח של מיקום קושר את הוו לפריסת מארח אחת.
עברו ארבעה גבולות לפי הסדר
התחילו בתיקיית העבודה: השוו את WorkingDirectory, את האפשרות cd של OpenVPN וכל עטיפה שמחליפה תיקייה לפני exec. אחר כך אשרו את המיקום שנפתר, כי קובץ בעל שם דומה בפריסה אחרת יכול לענות לחיפוש ולהסתיר את הטעות. לאחר מכן מגיע מבט מערכת הקבצים: chroot או מרחב שמות mount של השירות יכולים פשוט להעלים נתיב מארח מוחלט. אחרון הוא זהות הגישה, שהיא שאלה נפרדת מנראות.
ProtectHome ו-PrivateTmp מסתירים מיקומים שונים, ואף אחד מהם לא מופיע ברשימת תיקייה שנלקחה מהמארח. נתיב שקיים ל-root במעטפת עשוי להיות בלתי נראה בתוך מרחב השמות של היחידה, ולכן בדיקת אותה פקודה מחוץ לשירות מוכיחה כל כך מעט.
תקנו את החיפוש בלי להרחיב את השירות
עיגון הנתיב פותר רק את הגבול הראשון. הוא לא חושף תיקייה שהשירות מסתיר, והוא לא נותן לחשבון חסר הרשאות גישה לקובץ שרק root רשאי לקרוא. העלאת script-security משנה אילו סקריפטים OpenVPN יריץ; אין לה שום השפעה על המקום שבו נפתר שם יחסי. שינוי תיקיית העבודה לכל השירות רחב יותר מהבעיה, כי כל נתיב יחסי אחר של תעודה, לוג או עזר עובר איתו.
העדיפו את התיקון הצר: נתיב דטרמיניסטי בסקריפט, ערך תצורה מפורש לנתוני המופע, ובעלות קבצים שמתאימה לחשבון שהוו באמת רץ תחתיו.
תעדו ראיות שממנות את הגבול
דוח כשל שימושי מכיל שלושה דברים: הנתיב המדויק שהוו ניסה, הסביבה שהפיקה אותו, ואיזה גבול סירב. הדפיסו את הנתיב שנפתר ואת החריגה בלי לשפוך את תוכן הקובץ. השלשייה הזו מפרידה עיגון שגוי מ-mount מוסתר ומסירוב הרשאה, ואלה שלושה תיקונים שונים.
הטענה ש-root יכול לקרוא את הקובץ ממסוף עונה על אחת מהשאלות האלה ואף לא על אחת מהאחרות. בדקו במקום זאת מההקשר של השירות עצמו.