הערה. תרגום: אנו מציגים לתשומת לבכם תרגום של מאמר מאת מהנדס אבטחת יישומים בכיר בחברה הבריטית ASOS.com. עם זה, הוא מתחיל סדרת פרסומים המוקדשים לשיפור האבטחה ב-Kubernetes באמצעות שימוש ב-seccomp. אם הקוראים יאהבו את ההקדמה, נעקוב אחר המחבר ונמשיך עם החומרים העתידיים שלו בנושא זה.

מאמר זה הוא הראשון בסדרת פוסטים על איך ליצור פרופילי seccomp ברוח SecDevOps, מבלי להזדקק לקסמים ולכישוף. בחלק XNUMX, אכסה את היסודות והפרטים הפנימיים של יישום seccomp ב-Kubernetes.
המערכת האקולוגית של Kubernetes מציעה מגוון רחב של דרכים לאבטח ולבודד מכולות. המאמר עוסק במצב מחשוב מאובטח, הידוע גם בשם secomp. המהות שלו היא לסנן את קריאות המערכת הזמינות לביצוע לפי קונטיינרים.
למה זה חשוב? מיכל הוא רק תהליך הפועל על מכונה מסוימת. והוא משתמש בקרנל בדיוק כמו יישומים אחרים. אם קונטיינרים יכלו לבצע קריאות מערכת כלשהן, בקרוב מאוד תוכנות זדוניות ינצלו זאת כדי לעקוף את בידוד הקונטיינרים ולהשפיע על יישומים אחרים: ליירט מידע, לשנות הגדרות מערכת וכו'.
פרופילי seccomp מגדירים אילו שיחות מערכת יש לאפשר או להשבית. זמן הריצה של מיכל מפעיל אותם כאשר הוא מתחיל כך שהקרנל יוכל לפקח על הביצוע שלהם. שימוש בפרופילים כאלה מאפשר לך להגביל את וקטור ההתקפה ולהפחית נזק אם תוכנית כלשהי בתוך הקונטיינר (כלומר, התלות שלך, או התלות שלהם) מתחילה לעשות משהו שאסור לה לעשות.
הבנת היסודות
פרופיל ה-secomp הבסיסי כולל שלושה אלמנטים: defaultAction, architectures (או archMap) ו syscalls:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"arch_prctl",
"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp"
],
"action": "SCMP_ACT_ALLOW"
}
]
}()
defaultAction קובע את גורל ברירת המחדל של כל קריאת מערכת שלא צוינה בסעיף syscalls. כדי להקל על הדברים, בואו נתמקד בשני הערכים העיקריים שישמשו:
-
SCMP_ACT_ERRNO- חוסם ביצוע של שיחת מערכת, -
SCMP_ACT_ALLOW- מאפשר.
בסעיף architectures ארכיטקטורות היעד מופיעות. זה חשוב מכיוון שהמסנן עצמו, המוחל ברמת הליבה, תלוי במזהי קריאת המערכת, ולא בשמותיהם המצוינים בפרופיל. זמן הריצה של המכולה יתאים אותם למזהים לפני השימוש. הרעיון הוא שלשיחות מערכת יכולות להיות מזהים שונים לחלוטין בהתאם לארכיטקטורת המערכת. למשל, שיחת מערכת recvfrom (משמש לקבלת מידע מהשקע) יש ID = 64 במערכות x64 ו-ID = 517 ב-x86. אתה יכול למצוא רשימה של כל קריאות המערכת עבור ארכיטקטורות x86-x64.
בקטע syscalls מפרט את כל קריאות המערכת ומציין מה לעשות איתן. לדוגמה, אתה יכול ליצור רשימת היתרים על ידי הגדרה defaultAction על SCMP_ACT_ERRNO, ושיחות במדור syscalls לְהַקְצוֹת SCMP_ACT_ALLOW. לפיכך, אתה מתיר רק שיחות שצוינו בסעיף syscalls, ואסור על כל השאר. עבור הרשימה השחורה עליך לשנות את הערכים defaultAction ופעולות להיפך.
כעת עלינו לומר כמה מילים על ניואנסים שאינם כה ברורים. שים לב שההמלצות שלהלן מניחות שאתה פורס שורה של יישומים עסקיים ב-Kubernetes ואתה רוצה שהם יפעלו עם כמות ההרשאות המינימלית האפשרית.
1. AllowPrivilegeEscalation=false
В securityContext למיכל יש פרמטר AllowPrivilegeEscalation. אם הוא מותקן ב false, מיכלים יתחילו עם (on) קצת . המשמעות של פרמטר זה ברורה מהשם: הוא מונע מהמיכל להשיק תהליכים חדשים עם יותר הרשאות ממה שיש לו עצמו.
תופעת לוואי של אפשרות זו מוגדרת true (ברירת המחדל) היא שזמן הריצה של המכולה מחיל את פרופיל seccomp ממש בתחילת תהליך האתחול. לפיכך, כל קריאות המערכת הנדרשות להפעלת תהליכי ריצה פנימיים (למשל הגדרת מזהי משתמש/קבוצה, ביטול יכולות מסוימות) חייבות להיות מופעלות בפרופיל.
למיכל שעושה דברים של מה בכך echo hi, יידרשו ההרשאות הבאות:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"arch_prctl",
"brk",
"capget",
"capset",
"chdir",
"close",
"execve",
"exit_group",
"fstat",
"fstatfs",
"futex",
"getdents64",
"getppid",
"lstat",
"mprotect",
"nanosleep",
"newfstatat",
"openat",
"prctl",
"read",
"rt_sigaction",
"statfs",
"setgid",
"setgroups",
"setuid",
"stat",
"uname",
"write"
],
"action": "SCMP_ACT_ALLOW"
}
]
}()
...במקום אלה:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"arch_prctl",
"brk",
"close",
"execve",
"exit_group",
"futex",
"mprotect",
"nanosleep",
"stat",
"write"
],
"action": "SCMP_ACT_ALLOW"
}
]
}()
אבל שוב, למה זו בעיה? באופן אישי, הייתי נמנע מהרשימה הלבנה של קריאות המערכת הבאות (אלא אם כן יש צורך אמיתי בהן): capset, set_tid_address, setgid, setgroups и setuid. עם זאת, האתגר האמיתי הוא שעל ידי מתן אפשרות לתהליכים שאין לך שליטה עליהם לחלוטין, אתה קושר פרופילים למימוש זמן הריצה של קונטיינר. במילים אחרות, יום אחד אתה עלול לגלות שאחרי עדכון סביבת הריצה של קונטיינר (או על ידך או, סביר יותר, על ידי ספק שירותי הענן), הקונטיינרים מפסיקים לפתע לפעול.
טיפ # 1: הפעל מכולות עם AllowPrivilegeEscaltion=false. זה יקטין את הגודל של פרופילי seccomp ויהפוך אותם לפחות רגישים לשינויים בסביבת זמן הריצה של מיכל.
2. הגדרת פרופילי seccomp ברמת המכולה
ניתן להגדיר את פרופיל seccomp ברמת הפוד:
annotations:
seccomp.security.alpha.kubernetes.io/pod: "localhost/profile.json"...או ברמת המיכל:
annotations:
container.security.alpha.kubernetes.io/<container-name>: "localhost/profile.json"שימו לב שהתחביר לעיל ישתנה כאשר Kubernetes seccomp (אירוע זה צפוי במהדורה הבאה של Kubernetes - 1.18 - בערך תרגום).
מעטים יודעים שלקוברנטס תמיד היה מה שגרם להחלת פרופילי seccomp . סביבת זמן הריצה מפצה חלקית על החיסרון הזה, אבל מיכל זה לא נעלם מהפודים, מכיוון שהוא משמש להגדרת התשתית שלהם.
הבעיה היא שהמיכל הזה תמיד מתחיל עם AllowPrivilegeEscalation=true, המוביל לבעיות שהושמעו בסעיף 1, ולא ניתן לשנות זאת.
על ידי שימוש בפרופילי seccomp ברמת המכולה, אתה נמנע מהמלכוד הזה ויכול ליצור פרופיל שמותאם למיכל ספציפי. זה יצטרך להיעשות עד שהמפתחים יתקנו את הבאג והגרסה החדשה (אולי 1.18?) תהיה זמינה לכולם.
טיפ # 2: הגדר פרופילי seccomp ברמת המכולה.
במובן המעשי, כלל זה משמש בדרך כלל כתשובה אוניברסלית לשאלה: "מדוע פרופיל ה-secomp שלי עובד עם docker runאבל לא עובד לאחר פריסה לאשכול Kubernetes?
3. השתמש בזמן ריצה/ברירת מחדל רק כמוצא אחרון
ל-Kubernetes שתי אפשרויות לפרופילים מובנים: runtime/default и docker/default. שניהם מיושמים על ידי זמן הריצה של המכולה, לא Kubernetes. לכן, הם עשויים להשתנות בהתאם לסביבת זמן הריצה המשמשת ולגרסה שלה.
במילים אחרות, כתוצאה משינוי זמן הריצה, ל-container עשויה להיות גישה לקבוצה אחרת של קריאות מערכת, שבהן הוא עשוי להשתמש או לא. רוב זמני הריצה משתמשים . אם ברצונך להשתמש בפרופיל זה, אנא ודא שהוא מתאים לך.
פרופיל docker/default הוצא משימוש מאז Kubernetes 1.11, אז הימנע משימוש בו.
לדעתי, פרופיל runtime/default מתאים באופן מושלם למטרה שלשמה הוא נוצר: הגנה על משתמשים מפני הסיכונים הכרוכים בביצוע פקודה docker run על המכוניות שלהם. עם זאת, כשמדובר באפליקציות עסקיות הפועלות על אשכולות Kubernetes, הייתי מעז לטעון שפרופיל כזה פתוח מדי ועל מפתחים להתמקד ביצירת פרופילים עבור האפליקציות (או סוגי האפליקציות) שלהם.
טיפ # 3: צור פרופילי seccomp עבור יישומים ספציפיים. אם זה לא אפשרי, צור פרופילים לסוגי יישומים, למשל, צור פרופיל מתקדם הכולל את כל ה-APIs האינטרנט של אפליקציית Golang. השתמש רק בזמן ריצה/ברירת מחדל כמוצא אחרון.
בפוסטים עתידיים, אספר כיצד ליצור פרופילי seccomp בהשראת SecDevOps, להפוך אותם לאוטומטיים ולבדוק אותם בצינורות. במילים אחרות, לא תהיה לך תירוץ לא לשדרג לפרופילים ספציפיים לאפליקציה.
4. Unconfined אינה אופציה.
של התברר כי כברירת מחדל . זה אומר שאם לא תגדיר PodSecurityPolicy, אשר יאפשר זאת באשכול, כל הפודים שעבורם לא מוגדר פרופיל seccomp יפעלו ב seccomp=unconfined.
הפעלה במצב זה גורמת לאיבוד שכבת בידוד שלמה המגנה על האשכול. גישה זו אינה מומלצת על ידי מומחי אבטחה.
טיפ # 4: אף מיכל באשכול לא אמור לפעול פנימה seccomp=unconfined, במיוחד בסביבות ייצור.
5. "מצב ביקורת"
נקודה זו אינה ייחודית ל-Kubernetes, אך עדיין נכנסת לקטגוריית "דברים שכדאי לדעת לפני שמתחילים".
כפי שזה קורה, יצירת פרופילי seccomp תמיד הייתה מאתגרת ונשענת במידה רבה על ניסוי וטעייה. העובדה היא שלמשתמשים פשוט אין את ההזדמנות לבדוק אותם בסביבות ייצור מבלי להסתכן "להפיל" את האפליקציה.
לאחר שהליבה הופיעה Linux גרסה 4.14 הציגה את היכולת להפעיל חלקים מפרופיל במצב ביקורת, תוך רישום מידע על כל קריאות המערכת ל-syslog מבלי לחסום אותן. ניתן להפעיל מצב זה באמצעות הפרמטר SCMT_ACT_LOG:
SCMP_ACT_LOG: seccomp לא ישפיע על השרשור המבצע את קריאת המערכת אם הוא לא תואם לאף כלל במסנן, אך מידע על קריאת המערכת יירשם.
הנה אסטרטגיה טיפוסית לשימוש בתכונה זו:
- אפשר שיחות מערכת הדרושות.
- חסום שיחות מהמערכת שאתה יודע שלא יועילו.
- הקלט מידע על כל שאר השיחות ביומן.
דוגמה פשוטה נראית כך:
{
"defaultAction": "SCMP_ACT_LOG",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"arch_prctl",
"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp"
],
"action": "SCMP_ACT_ALLOW"
},
{
"names": [
"add_key",
"keyctl",
"ptrace"
],
"action": "SCMP_ACT_ERRNO"
}
]
}()
אבל זכור שאתה צריך לחסום את כל השיחות שאתה יודע שלא ישמשו ושעשויות להזיק לאשכול. בסיס טוב לעריכת רשימה הוא הרשמי . הוא מסביר בפירוט אילו שיחות מערכת חסומות בפרופיל ברירת המחדל ומדוע.
עם זאת, יש מלכוד אחד. למרות ש SCMT_ACT_LOG נתמך על ידי הליבה Linux מאז סוף 2017, הוא נכנס רק לאחרונה למערכת האקולוגית של Kubernetes. לכן, כדי להשתמש בשיטה זו, תזדקקו לליבת קוד (core) Linux 4.14 וגרסת runC לא ישנה יותר .
טיפ # 5: ניתן ליצור פרופיל מצב ביקורת לבדיקה בייצור על ידי שילוב של רשימות שחורות ולבנות, וניתן לרשום את כל החריגים.
6. השתמש ברשימות הלבנים
רשימת היתרים דורשת מאמץ נוסף מכיוון שאתה צריך לזהות כל שיחה שהאפליקציה עשויה להזדקק לה, אך גישה זו משפרת מאוד את האבטחה:
מומלץ מאוד להשתמש בגישת הרשימה הלבנה מכיוון שהיא פשוטה ואמינה יותר. הרשימה השחורה תצטרך להתעדכן בכל פעם שמתווסף קריאת מערכת שעלולה להיות מסוכנת (או דגל/אפשרות מסוכנת אם היא ברשימה השחורה). בנוסף, לעיתים קרובות ניתן לשנות את הייצוג של פרמטר מבלי לשנות את מהותו ובכך לעקוף את מגבלות הרשימה השחורה.
עבור אפליקציות Go פיתחתי כלי מיוחד המלווה את האפליקציה ואוסף את כל השיחות שנעשות במהלך הביצוע. לדוגמה, עבור היישום הבא:
package main
import "fmt"
func main() {
fmt.Println("test")
} ... בואו נצא לדרך gosystract לכן you
go install https://github.com/pjbgf/gosystract
gosystract --template='{{- range . }}{{printf ""%s",n" .Name}}{{- end}}' application-path... ואנו מקבלים את התוצאה הבאה:
"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp",
"arch_prctl",לעת עתה, זו רק דוגמה - פרטים נוספים על הכלים יבואו בהמשך.
טיפ # 6: אפשר רק את השיחות שאתה באמת צריך וחסום את כל האחרות.
7. הניחו את היסודות הנכונים (או היכונו להתנהגות בלתי צפויה)
הקרנל יאכוף את הפרופיל ללא קשר למה שתכתוב בו. גם אם זה לא בדיוק מה שרצית. לדוגמה, אם אתה חוסם גישה לשיחות כמו exit או exit_group, המיכל לא יוכל להיסגר בצורה נכונה ואפילו פקודה פשוטה כמו echo hi o לתקופה בלתי מוגבלת. כתוצאה מכך, תקבל שימוש גבוה ב-CPU באשכול:

במקרים כאלה, כלי עזר יכול לבוא לעזרה strace - זה יראה מה יכולה להיות הבעיה:

sudo strace -c -p 9331
ודא שהפרופילים מכילים את כל קריאות המערכת שהאפליקציה צריכה בזמן ריצה.
טיפ # 7: שימו לב לפרטים וודאו שכל שיחות המערכת הדרושות נמצאות ברשימת ההיתרים.
בכך מסתיים החלק הראשון של סדרת מאמרים על שימוש ב-seccomp ב-Kubernetes ברוח SecDevOps. בחלקים הבאים נדבר על מדוע זה חשוב וכיצד להפוך את התהליך לאוטומטי.
נ.ב מהמתרגם
קרא גם בבלוג שלנו:
- «";
- «";
- «";
- «".
מקור: www.habr.com
