בלוג
כשל האמון: כיצד הגדרת Docker מרובת הדיירים שלנו דלפה מידע (ואיך תיקנו את זה)
למד כיצד הגדרת Docker נאיבית של צוות אחד הובילה לדליפת מידע בין דיירים ואסטרטגיית הבידוד השכבתית שמנעה אותה.
סיכום
קונטיינרים של Docker אינם מבודדים כברירת מחדל—הם חולקים את גרעין המארח, וללא תצורה מכוונת, דיירים יכולים להפריע זה לזה. מאמר זה מתאר תרחיש אמיתי שבו ספק אירוח מרובה-דיירים גילה שקונטיינרים של לקוחות יכולים לגשת למסדי הנתונים זה של זה עקב רשת משותפת והגדרות ברירת מחדל חלשות. אנו מציגים את השינויים שלב אחר שלב שתיקנו את הפרצה: רשתות מוגדרות על-ידי המשתמש לכל דייר, משתמשים שאינם root, יכולות שהוסרו, מערכות קבצים לקריאה בלבד ופרופילי seccomp. הנחה נפוצה היא שקונטיינרים מספקים בידוד חזק מטבעם; אנו מאתגרים זאת על ידי הסבר מדוע VM עדיין מציעים גבול קשה יותר ומתי לשקול גישה היברידית. המסקנה מחזקת שבידוד הוא תרגיל שכבתי, לא תיבת סימון אחת.
התקרית: כשקונטיינרים מדברים יותר מדי
הקמת Docker על מארח יחיד כדי להריץ אתרי אינטרנט של מספר לקוחות. לכל לקוח יש קונטיינר משלו—סביבה מסודרת ומבודדת, נכון? זה מה שחשבנו. עד שביקורת אבטחה שגרתית גילתה שקונטיינר של לקוח א' קורא את Socket ה-MySQL של קונטיינר של לקוח ב' באותו מארח. הם חלקו את רשת bridge ברירת המחדל. גרוע מכך, הקונטיינרים רצו כ-root, כך שתוקף שפרץ לאחד יכול להתעסק עם Socket ה-Docker של המארח או מערכת הקבצים של קונטיינר אחר. הפרצה לא הייתה ניצול מתוחכם; היא הייתה תצורה שגויה בסיסית. מידע דלף. אמון התפוגג.
תרחיש הכשל אינו נדיר. צוותים רבים מניחים ש-namespaces ו-cgroups של Docker אוטמים אוטומטית דיירים, אבל הם מזלזלים בכמה פתחי מילוט נשארים פתוחים כברירת מחדל. רשתות bridge ברירת מחדל אינן מציעות בידוד רשת בין קונטיינרים. ריצה כ-root נותנת לקונטיינר יותר כוח מהנדרש. ובלי מגבלות משאבים מפורשות, שכן רועש אחד יכול להרעיב אחרים מ-CPU או זיכרון.
שלב 1: להפסיק לשתף רשת אחת
התיקון הראשון שלנו היה לתת לכל דייר רשת Docker משלו המוגדרת על-ידי המשתמש. זה מונע מקונטיינרים להגיע זה לזה אלא אם מחברים אותם במפורש. יצרנו סקריפט שלכל דייר מפעיל רשת ייעודית ומחבר אליה את קונטיינר האפליקציה שלו. קונטיינר מסד הנתונים חי באותה רשת דייר, אבל הוספנו גם רשת פנימית לתקשורת בתוך הדייר בלבד. אין יותר חטטנות בין דיירים.
בידדנו גם את מסדי הנתונים על ידי הרצתם בקונטיינרים נפרדים באותה רשת דייר, תוך שימוש בנפחי נתונים נפרדים. זה הבטיח שגם אם תוקף פרץ לקונטיינר האפליקציה, הוא לא יוכל לרחרח תעבורת מסד נתונים מדייר אחר.
לצלילה עמוקה יותר לאסטרטגיות בידוד רשת, ראה את רשימת תיוג מעשית לבידוד Docker לאחסון מרובה-דיירים.
שלב 2: הסר הרשאות מיותרות
כברירת מחדל, קונטיינרים של Docker פועלים עם קבוצה מוגבלת של יכולות לינוקס, אך עדיין יש להם יותר ממה שרוב האפליקציות צריכות. הקונטיינרים שלנו רצו כ-root, מה שאפשר לתהליכים בפנים לבצע פעולות כמו הרכבת מערכות קבצים או שינוי פרמטרי גרעין. עברנו להפעלת האפליקציה כמשתמש שאינו root בתוך הקונטיינר (באמצעות ההנחיה USER ב-Dockerfile) והסרנו את כל היכולות מלבד אלו הנחוצות לחלוטין. עבור אפליקציית אינטרנט טיפוסית, אלה עשויות להיות רק NET_BIND_SERVICE (לקשירה לפורטים מתחת ל-1024) ו-CHOWN (לכתיבה לספריות). הוספנו גם --security-opt no-new-privileges כדי למנוע הסלמת הרשאות.
שלב זה לבדו חיסל וקטורי בריחה נפוצים רבים מקונטיינרים. תוקף שמשתלט על שרת האינטרנט לא יכול להתקין חבילות, לשנות קבצי מערכת בינאריים או לגשת ל-Socket ה-Docker של המארח מכיוון שלתהליך חסרות היכולות CAP_SYS_ADMIN או CAP_DAC_OVERRIDE.
שלב 3: נעל את מערכת הקבצים
מערכות קבצים ניתנות לכתיבה הן משטח תקיפה נפוץ. הפכנו את מערכת הקבצים הבסיסית לקריאה בלבד (--read-only) עבור כל הקונטיינרים, ואז הרכבנו מערכות קבצים זמניות (tmpfs) עבור ספריות שזקוקות לגישת כתיבה, כמו /tmp וספריית המטמון של האפליקציה. זה מונע מתוקף לשנות קוד אפליקציה או להתמיד קבצים בינאריים זדוניים.
בנוסף, השתמשנו באפשרות --mount של Docker כדי להרכיב ספריות רגישות כמו Socket ה-Docker רק כאשר הכרחי—ולעולם לא בקונטיינרים בייצור. העיקרון: אם הקונטיינר לא צריך לכתוב לנתיב, הפוך אותו לקריאה בלבד.
שלב 4: החל פרופילי Seccomp ו-AppArmor
פרופילי seccomp ברירת המחדל כבר חוסמים קריאות מערכת מסוכנות רבות, אבל התאמנו אותם אישית עוד יותר כדי להתיר רק את קריאות המערכת שהאפליקציה שלנו באמת צריכה. זוהי פשרה מכיוון שהיא דורשת פרופילציה של האפליקציה. גישה פשוטה יותר היא להשתמש בפרופיל seccomp ברירת המחדל של Docker ואז להוסיף --security-opt seccomp=path/to/profile.json אם אתה צריך חוקים מחמירים יותר. באופן דומה, פרופילי AppArmor יכולים להגביל תהליכי קונטיינר לנתיבי קבצים ויכולות ספציפיים. הפעלנו AppArmor והשתמשנו בפרופיל מותאם אישית שהגביל גישה רק לספריות הנתונים של האפליקציה.
למדריך מקיף על צעדי ההקשחה האלה, עיין בהקשחת קונטיינרים של Docker לאחסון מרובה-דיירים: מדריך בידוד שלב אחר שלב.
הדעה הנגדית: לפעמים צריך VM
לא משנה כמה מוקשחים, קונטיינרים חולקים את גרעין המארח. פגיעות גרעין יכולה לשבור את כל הבידוד בבת אחת. לכן פלטפורמות רבות שמודעות לאבטחה מריצות קונטיינרים בתוך VM קלים—כל דייר מקבל גרעין משלו. זה מוסיף תקורה אבל מספק גבול חומרתי שקונטיינרים לבדם אינם יכולים. אם הדיירים שלך מטפלים בנתוני כרטיסי אשראי או רשומות רפואיות, גישה היברידית (קונטיינרים בתוך VM) עשויה להיות הבחירה הנכונה. אל תניח שבידוד קונטיינרים מספיק למודל האיום שלך; הערך את רגישות הנתונים ואת הדרישות הרגולטוריות.
להשוואה מעמיקה יותר של רמות בידוד, קרא תכנון ארכיטקטורת Docker מרובת-דיירים: בחירת רמת הבידוד הנכונה.
מסקנה: בידוד הוא ערימה, לא מתג
התיקון לא היה שינוי בודד—זה היה שכבות: בידוד רשת, הרשאות מוגבלות, מערכות קבצים לקריאה בלבד וסינון קריאות מערכת. אפילו אז, קיבלנו שבידוד מושלם בלתי אפשרי עם קונטיינרים חולקי גרעין. עבור הדיירים בעלי האבטחה הגבוהה ביותר שלנו, העברנו אותם למארחים ייעודיים. הלקח: אל תסמוך על ברירת מחדל. בצע ביקורת על הגדרת ה-Docker שלך כאילו כבר התרחשה פריצה. הזמן לנעול הוא לפני הדליפה, לא אחריה.


