איך להריץ שירות כמשתמש ללא הרשאות
הרצת שירות כ-root מעניקה לתוקף שליטה מלאה בשרת. למדו איך להגביל את ה-blast radius באמצעות unprivileged user או שימוש ב-DynamicUser של systemd.
למה לא פשוט להריץ הכל כ-root
root יכול לבצע כל פעולה במכונה: לקרוא כל קובץ, לשנות כל הגדרה, או למחוק את המערכת כולה. כאשר מריצים שירות כ-root, מעניקים את כל הכוח הזה לאותו שירות. אם בשירות יש באג שניתן לנצל, התוקף לא מקבל רק את השירות, הוא מקבל את ה-root, ו-root הוא השרת כולו. הרצה תחת משתמש ללא הרשאות (unprivileged user) מגבילה את הנזק. באג בשירות שרץ תחת חשבון מוגבל מעניק לתוקף רק את מה שהחשבון הזה יכול לגעת בו, מה שאמור להיות כמעט כלום.
זהו עיקרון ההרשאה המינימלית (least privilege): תנו לכל חלק במערכת בדיוק את הגישה שהוא זקוק לה כדי לבצע את תפקידו, ולא יותר. זוהי ההרגל היעיל ביותר להגבלת רדיוס הפגיעה (blast radius) במקרה של פריצה, ובשרת מודרני העלות של יישום זה היא כמעט אפס.
חשבון ייעודי לכל שירות
הגישה הקלאסית היא ליצור משתמש מערכת נפרד עבור כל שירות, כזה שבבעלותו רק הקבצים של אותו שירות ואין לו אפשרות להתחבר (login). חשבון מערכת עבור אפליקציית Web עשוי להיראות כך:
sudo useradd --system --no-create-home --shell /usr/sbin/nologin appsvcלכל דגל (flag) יש חשיבות. --system הופך אותו לחשבון שירות, ולא למשתמש אנושי. --no-create-home מדלג על ספריית הבית (home directory) שאינה נחוצה. --shell /usr/sbin/nologin אומר שאפילו אם תוקף ישיג בדרך כלයක් גישה לחשבון, הוא לא יוכל לפתוח איתו shell. החשבון קיים רק כדי להחזיק תהליך וקבצים.
לאחר מכן, תנו למשתמש הזה רק את הקבצים שהוא זקוק להם, ולא יותר:
sudo chown -R appsvc:appsvc /opt/myappכעת השירות קורא וכותב בתוך הדירקטורי שלו בלבד ואין לו שום עניין בשום מקום אחר בדיסק. אם הוא יושפע מפריצה, הקבצים שהתוקף יכול לשנות מוגבלים ל-/opt/myapp; החשבון עדיין יכול לקרוא כל מה שמוגדר כ-world-readable, אך הוא לא יכול לשנות את שאר המערכת.
תנו ל-systemd להריץ אותו כמשתמש זה
ברגע שהחשבון קיים, הגדירו ל-systemd להריץ את השירות תחתיו. בקובץ ה-unit, שורה אחת עושה זאת:
[Service]
ExecStart=/opt/myapp/bin/server
User=appsvc
Group=appsvcUser=appsvc אומר שהתהליך מתחיל עם ההרשאות המוגבלות של החשבון הזה במקום עם הרשאות root. זוהי הדרך הרגילה והמומלצת להריץ אפליקציה תחת systemd, וכדאי לעשות זאת עבור כל שירות שאתם כותבים לו unit.
או דלגו לגמרי על החשבון באמצעות DynamicUser
systemd יכול ללכת צעד אחד קדימה וליצור עבורכם משתמש זמני, כזה שקיים רק כל עוד השירות רץ. הגדירו את DynamicUser=yes ולא תצטרכו לנהל חשבון בכלל:
[Service]
ExecStart=/opt/myapp/bin/server
DynamicUser=yes
StateDirectory=myappבתחילת הריצה, systemd מקצה מזהה משתמש (user ID) שאינו בשימוש; בסיום הריצה, הוא משחרר אותו. השירות מקבל גם /tmp פרטי, תצוגת קריאה בלבד של רוב מערכת הקבצים, וספריית מצב (state directory) ניתנת לכתיבה תחת /var/lib/myapp ש-StateDirectory= מקימה וממסרת לו. עבור שירות עצמאי שזקוק רק לספריית מצב משלו, DynamicUser=yes היא הדרך הקלה ביותר להשיג בידוד חזק, מכיוון שאין חשבון קבוע שתוקף יוכל לTarget.
כתיבת units באופן ידני היא עדינה, וההגדרות של אבטחה (hardening) הן עיקר הערך. הגנרטור ב-the systemd service and guide יכול למלא את האפשרויות הללו עבורכם כדי שה-unit יהיה תקין כבר בפעם הראשונה.
איך זה מתחבר לשאר
הרשאה מינימלית היא שכבה אחת, והיא עובדת בשילוב עם השכבות האחרות ולא מחליפה אותן. default-deny firewall שולט במה יכול להגיע לשירות; הרצה תחת משתמש ללא הרשאות שולטת במה השירות יכול לעשות אם הוא נפרץ; ו-hardened SSH שומר על התוקפים מחוץ למכונה מלכתחילה. אף אחת מהן לבדה אינה מספיקה, ויחד הן מבטיחות שבאג בשירות אחד לא יהפוך לפריצה של השרת כולו.
לפני שתמשיכו, עברו על רשימת בדיקה (checklist) לאבטחה של המכונה כולה וייצרו עותק אישי לעבודה:
FAQ
Why should I not run a service as root?
מכיוון ש-root יכול לבצע כל פעולה במכונה, שירות שרץ כ-root ונתקל בפריצה מעניק לתוקף את השרת כולו, לא רק את השירות. הרצת השירות כחשבון מוגבל ולא מורשה מגבילה את הנזק לכל מה שהחשבון הזה יכול לגשת אליו. שמרו את ה-root למשימות ניהול, והריצו כל שירות שרץ לאורך זמן תחת משתמש מוגבל.
How do I create a user that cannot log in?
הריצו את sudo useradd --system --no-create-home --shell /usr/sbin/nologin NAME. ה-shell מסוג nologin אומר שהחשבון לא יכול לפתוח סשן אינטראקטיבי גם אם פרטיו נגנבו, --system מסמן אותו כחשבון שירות, ו---no-create-home מדלג על ספריית בית שאינה נחוצה. תנו לו בעלות רק על הקבצים שלו באמצעות chown.
What is systemd DynamicUser?
DynamicUser=yes אומר ל-systemd ליצור משתמש זמני עבור השירות שקיים רק כל עוד הוא רץ, כך שלא תצטרכו לנהל חשבון קבוע. זה גם נותן לשירות /tmp פרטי, תצוגת מערכת קבצים שהיא בעיקר לקריאה בלבד, וספריית מצב מנוהלת. זו הדרך הקלה ביותר להריץ שירות עצמאי תחת זהות זמנית עם הרשאות נמוכות.
Does running as a non-root user replace a firewall?
לא. הם מגנים על דברים שונים. הרצה תחת משתמש ללא הרשאות מגבילה את מה שהשירות יכול לעשות אם הוא נפרץ, בעוד ש-firewall מגביל את מה שיכול להגיע לשירות מלכתחילה. השתמשו בשניהם, יחד עם SSH מאובטח, כדי שכל שכבה תכסה את מה שהשכבות האחרות אינן יכולות.
What files should the service user own?
רק את הקבצים שהשירות באמת זקוק להם, ולא יותר. תנו לחשבון בעלות על ספריית העבודה שלו ועל הנתונים שלו, והשאירו את כל השאר בבעלות root. תבנית טובה היא sudo chown -R svc-app:svc-app /opt/svc-app עבור ספריית האפליקציה, בעוד שהגדרות תחת /etc נשארות בבעלות root וניתנות לקריאה על ידי השירות בלבד. המטרה היא שאם התהליך ייפרץ, הקבצים שהוא יכול לשנות יהיו מוגבלים לנתונים שלו, ולא לשאר המערכת.