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

שם משתמש בסביבה אינו מתג זהות

יוניקס מחליט למה תהליך רשאי לגעת לפי מזהה המשתמש האפקטיבי שלו, לא לפי המחרוזות בסביבה שלו. הגדרת USER=alice או ייצוא LOGNAME משנה מה תוכנה רשלנית מדפיסה בפלט שלה; היא לא משנה דבר בבדיקות הבעלות על קבצים. hook שרץ כ-root עם המשתנה הזה מוגדר עדיין פותח את הקבצים של אליס כ-root.

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

אז השאלה הראשונה אינה «איזה משתמש מחובר» אלא «איזה חשבון מקומי רשאי לבצע את הפעולה הזו». ענה על זה מבסיס הנתונים של החשבונות שלך, לא ממה שהלקוח שלח:

id -u alice
getent passwd alice | cut -d: -f1,3,6
stat -c '%U:%G %a %n' /home/alice/.local/state/vpn-status.json

הפקודה השלישית היא זו שאנשים מדלגים עליה. אם הקובץ המיועד בבעלות root משום שה-hook הקודם יצר אותו, פעולת משתמש מוגדרת כראוי תיכשל בשגיאת הרשאה שנראית כמו בעיית systemd ואינה כזו.

הדמון נשאר במקומו

שינוי ה-User= של שירות ה-VPN עצמו לאליס הוא המנוף הלא נכון. זה משנה איך הדמון קושר ממשקים, קורא מפתחות, כותב את קובץ המצב שלו ומנהל נתיבים, וזה מעביר את כל הסיכון הזה ליחידה שעבדה. הדמון צריך את ההרשאות שלו; הפעולה שלאחר מכן לא.

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

systemctl show -p User -p Group -p ExecStart openvpn-server@server
systemctl cat openvpn-server@server

בהתקנת חבילה רגילה הדמון הוא root וסקריפט ה---up יורש זאת. אשר זאת במקום להניח, כי הפצה מוקשחת אולי העבירה אותו לחשבון ייעודי, וקובץ drop-in שנכתב ביד עשוי להריץ את ה-hook דרך עטיפה עם כללים אחרים.

שלושה חלקים בעלי שם, טריגר צר אחד

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

הפעולה עצמה היא יחידת שירות רגילה, והזהות היא הנחיה אחת בתוכה:

[Service]
Type=oneshot
User=alice
Group=alice
ExecStart=/usr/local/bin/refresh-vpn-status --slot %i

שימו לב ש-User=alice חל על כל היחידה, ו-systemd מתעד חריג אחד שכדאי להכיר: פקודה עם הקידומת + רצה בהרשאות מלאות ללא תלות בכך. הקידומת הזו היא כלי לגיטימי לשלב מורשה אחד ודרך נוחה לבטל את ההפרדה שזה עתה בניתם, אז התייחסו לכל + ביחידה הזו כאל משהו שצריך להצדיק בסקירה.

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

אמתו את הזהות שבאמת רצה

סקירת תצורה אינה ראיה. אחרי חיבור בדיקה מאושר, אשרו את ה-UID שנגע בקובץ ואת ה-UID שהדמון עדיין משתמש בו:

stat -c '%U %G %y %n' /home/alice/.local/state/vpn-status.json
ps -o user=,pid=,cmd= -p "$(pgrep -o -f 'openvpn --daemon')"
systemd-run --uid=alice --pipe /usr/local/bin/refresh-vpn-status --dry-run

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

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

מה שאירוע חיבור עדיין לא מוכיח

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

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

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

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