סריקת תמונות Docker לאיתור פגיעויות (Trivy/Snyk/Grype)

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

היכולות שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1365
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    776
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1022
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113

מה נשבר בלי סריקת Docker images

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

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

נקודת הדעיכה השנייה — ה-build עצמו. מנהל החבילות ב-Dockerfile מושך גרסאות בלי הצמדה, וה-image הסופי מקבל חבילות מתוך הרכב ה-build וכלי עזר שאינם משתתפים בהרצת ה-container. כל חבילה מיותרת היא משטח תקיפה נוסף, אבל ה-build שותק על כך: image שנבנה בהצלחה נראה עובד.

סיכון הניצול מופיע במקום שבו תלות פגיעה נגישה מבחוץ. ה-container עולה כברירת מחדל בהרשאות root, הספרייה מנתחת נתונים חיצוניים — ותלות אחת שלא נבדקה הופכת לנקודת כניסה, בלתי נראית לא בלוגים של האפליקציה ולא ב-monitoring.

משם התרחיש אחד: הפגיעות חיה ב-prod חודשים וצפה רק באירוע אחרי הפריסה. סגירתה דורשת בנייה מחדש של כל השירותים על שכבת הבסיס המשותפת, בדיקה חוזרת של ה-images ב-registry, rollback של ה-release ורוטציית סודות. נפרט איך זה נסגר בסריקה בכל שלב.

מה נותן גילוי מוקדם של פגיעויות ב-build

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

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

האפקט מדיד. מהירות סגירת הליקויים משתנה משבועות לשעות: עדכון קריטי נכנס ל-build באותו יום שבו הוא מופיע במאגר הפגיעויות, כי הבדיקה מוטמעת ב-pipeline ולא חיה כדוח נפרד. שיעור ה-rollback של release בגלל תלות שצפה שואף לאפס — עדכון לא תואם נחסם לפני הפריסה. היקף הניתוח הידני מצטמצם כשהמהנדס רואה לא את כל הרשימה, אלא רק ממצאים חדשים ביחס לקו הבסיס.

כך נעלם סוג שלם של כשלים: סביבת ייצור עם image בסיסי פגיע ש"התכוונו לעדכן", ואירועים בגלל חבילה נטושה שאיש בפרויקט כבר לא זכר. צמצום זמן ההשבתה הוא כאן תוצאה, לא סיסמה: את ה-release לא מפרקים ידנית, וה-rollback נעשה ל-image שנבדק קודם.

מבחינה טכנית זה סט של מדיניות: סף הקריטיות שבו ה-build נעצר; רשימת חריגים עם נימוק מחייב ותאריך בחינה מחדש; השוואה לקו בסיס כדי שממצאים ישנים לא יטבעו ברעש של התראות שווא.

אנחנו מריצים Trivy ב-pipeline, מאחסנים את התוצאה בדוח קריא למכונה ומקשרים אותו ל-artifact מסוים — ולכן השאלה "באיזה release הגיעה התלות הזו" נסגרת בשאילתה, ולא בחפירות.

מצבי בדיקה: הרצה ידנית או בקרה ב-build

המצבים נבדלים לא בסורק, אלא ברגע שבו פסק הדין הופך למחייב. ככל שהבדיקה פועלת מוקדם יותר, כך התיקון זול יותר — אבל גם ההקשר קטן יותר: מקומית אין מדיניות release, וב-pipeline אין נתונים על איך ה-image יתנהג בסביבת הייצור. לכן בפועל המצבים לא מחליפים זה את זה, אלא משלימים זה את זה.

אפשרות מתי מתאימה מגבלות
בדיקה ידנית חד-פעמית ניתוח אירוע, ביקורת של image שכבר נבנה, סיור מקדים לפני הטמעה סוגרת תמונת מצב אחת; התוצאה מתיישנת יחד עם עדכון מאגרי הפגיעויות
בקרה על מכונת המפתח משוב מהיר לפני שליחה ל-repository אין מדיניות אחידה, הגרסאות של המאגרים שונות אצל כל אחד, וקל לכבות את הבדיקה
בדיקה ב-pipeline של ה-build המחסום המרכזי: ה-build נופל על ממצאים קריטיים לפני פרסום נדרשים עדכון מאגרים מוגדר וסף סביר, אחרת ה-builds מתעכבים ומתחילים לעקוף אותם
בקרה בכניסה ל-registry רשת ביטחון ל-images של צד שלישי ול-builds שעוקפים את ה-pipeline חוסמת פרסום, אבל לא מודיעה למחבר מה בדיוק לתקן
סריקה תקופתית תופסת פגיעויות שנחשפו אחרי ה-release, ו-tags נטושים הממצאים מגיעים בדיעבד — צריך בעלים שיסגור אותם בבנייה מחדש

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

שלבי הטמעת בקרת פגיעויות בצוות

הטמעת בקרת פגיעויות היא לא התקנה של סורק, אלא שינוי בתהליך ה-build: הבדיקה צריכה לעמוד במקום שבו התיקון לוקח דקות ולא ימים. הולכים שלב-שלב — מניתוח ה-registry הקיים ועד מסירת התקנון לצוות.

  1. ביקורת של ה-build וה-registry הנוכחיים. מפרקים את ה-images הבסיסיים, את תדירות הבנייה מחדש, את מספר ה-tags ואת גיל השכבות. כבר כאן רואים מאיפה מגיע עיקר הממצאים.

  2. סכמת הבקרה. מגדירים את נקודות הבדיקה: בזמן ה-build, בזמן השליחה ל-registry, לפני הפריסה. הסורק המסוים (Trivy, Grype) הוא כאן משני — חשוב יותר היכן עומדת הבדיקה ואיזו דרגת קריטיות חוסמת את ה-release ואיזו רק מפיקה אזהרה.

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

  4. בדיקה על סביבת staging. מריצים על עותק של ה-registry, משווים את התוצאה לניתוח ידני של כמה images, ומודדים את שיעור התראות השווא לפני הכניסה למעגל העבודה — לפי חישוב אישי**

  5. בדיקה על הרצות build אמיתיות. כמה releases רצים במצב אזהרה: הצוות מקבל דוחות, וה-release לא נחסם. כך מכווננים חוקים, ולא מעכבים את העבודה.

  6. מסירת התקנון והדרכה. מוסרים מסמך: מה עושים כשמופיע ממצא, איך מתקנים אותו, איך מגישים חריגה, ומי אחראי על ה-images הבסיסיים.

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

מדיניות גישה, דוחות ותקנון עדכון

סריקת Docker images מביאה תועלת לא דרך דוח, אלא דרך סט של artifacts שחיים ב-repository ועובדים בלי מעורבות שלנו. להלן מה שנשאר אצל הצוות: קונפיגורציות, סכמת הרצה, כללי תגובה ומסמכים למפתחים.

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

  • קונפיגורציות בדיקה — קבצי מדיניות עם ספים: איזו דרגת קריטיות עוצרת את ה-build, ואיזו רק נרשמת בדוח.
  • סכמת הבקרה — היכן הבדיקה רצה: לפני merge, בזמן בניית ה-image, בלילה לפי לוח זמנים. ואיך ה-build מתנהג אם מאגרי הפגיעויות לא זמינים.
  • מדיניות תגובה — מועדי טיפול לפי דרגות קריטיות, מי מחליט אם לשחרר image עם ממצא פתוח, ואיך מתבצעת אסקלציה של חריגה בזמן.
  • רשימת חריגים — רשומה לכל סטייה: נימוק, אחראי, תאריך בחינה מחדש. ממצא בלי תיקון חי עם תאריך, ולא נשכח.
  • תקנון עדכון שכבת הבסיס — באיזו תדירות ה-image נבנה מחדש על בסיס מעודכן, איך נבדקת התאימות ואיך נראה ה-rollback.
  • דוחות לפי builds — מה נראה בכל build: חבילה חדשה, מחבר השינוי, היסטוריה לפי tags, בלי להריץ סורק ידנית.
  • הנחיות לצוות — תזכירים: מה עושים כשמופעל התראה, איך מגישים חריגה, ולאן להסתכל בממצא שנוי במחלוקת.

קייס: חלון הפגיעות קוצר משבועות ליממה

הלקוח — צוות מוצר עם releases יומיים: 25–30 builds בשבוע, וכמעט כולם נכנסו ל-prod בלי בדיקה. פגיעות קריטית ב-image הבסיסי חיה ב-prod משבוע עד שבועיים, וידעו עליה מהתראה חיצונית, ולא מהמעגל הפנימי. ניתוח האירוע בכל פעם התחיל בשאלה "איזה image בכלל נמצא עכשיו ב-prod".

הטמענו בדיקה ב-pipeline של ה-build: Trivy סורק את שכבות ה-image ואת מניפסטי התלויות בשלב שבו עדיין אפשר לעצור את הפריסה. את חוק החסימה הגדרנו לא לפי מספר הממצאים הכולל, אלא לפי דרגות — ה-build נופל רק על קריטי וגבוה עם נתיב ניצול מאומת, והשאר עובר לרשימת משימות עם תאריך טיפול.

מדדים לפני ואחרי: חלון הפגיעות הצטמצם מ-11–14 ימים ל-18–24 שעות, כי עדכון ה-image הבסיסי בונה מחדש אוטומטית את השירותים התלויים. שיעור התראות השווא ירד מ-40% ל-8% — הודות לקובץ החריגים שאנחנו מתחזקים יחד עם הצוות, ולא "הגדרנו פעם אחת ושכחנו".

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

מה עושים אם ה-registry של ה-images כבר פרוס?

אם ה-registry כבר פרוס, אין צורך להעביר או לבנות אותו מחדש: הסורק מתחבר אליו כמקור רגיל וקורא את המניפסטים ואת השכבות דרך אותו ממשק תוכנה שבו משתמשת הפריסה. ה-registry נשאר עובד, והבדיקה הופכת לעוד שלב ב-pipeline של ה-build — אחרי בניית ה-image ולפני פרסום ה-tag.

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

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

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

משם נשאר להרחיב את הכיסוי: לחבר לאותם כללים את שאר ה-repositories ולהסיר בהדרגה חריגים. ה-registry כבר פרוס — מה שמשתנה זה לא הוא, אלא הרגע שבו אנחנו לומדים על הפגיעות.

התראות שווא ו-build איטי: ספקות נפוצים

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

זמן ה-build נסגר באמצעות caching: התוצאה על שכבה שלא השתנתה נעשה בה שימוש חוזר, ומאגר הפגיעויות מתעדכן במשימה נפרדת. הבדיקה נתלית על בניית ה-image, ולא על כל commit, ועוצרת את ה-pipeline רק בדרגת קריטיות מוסכמת.

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

הסורק מחזיר מאות פגיעויות, ורק בודדות מסוכנות — איך מפרידים?

מסננים לפי שלושה סימנים: קריטיות, קיומה של גרסה מתוקנת ב-upstream, ונגישות של הקוד הפגיע מתוך האפליקציה. השארית עוברת לרשימת חריגים עם בעלים ותאריך בחינה מחדש — חריגה בלי תאריך הופכת לחור נשכח.

הבדיקה תאט את ה-build ואת הפריסה — בכמה?

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

נדבר על המשימה: מה מבררים בשיחה הראשונה

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

  • איך בנוי ה-pipeline של ה-build: היכן נבנים ה-images, כמה שירותים, מי הבעלים של שכבות הבסיס.
  • מה כבר יש ב-registry: כמה tags לכל שירות, כמה עמוק נשמרת ההיסטוריה, האם יש מדיניות ניקוי — מזה תלוי הזמן של הסריקה המלאה הראשונה.
  • אילו סורקים מיושמים כיום (Trivy, Grype, Snyk) ובאיזה שלב — מקומית, על שרת ה-build או כבר ב-cluster.
  • מה בדיוק אמור לעצור את ה-build: רק דרגות קריטיות, קיומה של גרסה מתוקנת, פגיעויות בשכבות הרצה.
  • דרישות לדוחות ולהרשאות: פורמט הייצוא (SBOM, SARIF), לאן הולכות התוצאות, גישת קריאה ל-registry בלי הרשאת כתיבה.
  • מי בצוות שלכם אחראי על עדכון ה-images הבסיסיים — בלי בעלים לתהליך, חוק החסימה מהר מאוד מכבים.

בתגובה אנחנו שולחים הערכת היקף עבודה, תוכנית הטמעה שלב-שלב עם נקודות rollback וקריטריוני קבלה: איזה שיעור פגיעויות סוגר המעבר הראשון, איך ה-pipeline מתנהג בכשל של הסורק, ומה עושים עם images שכבר נמצאים ב-registry. תארו את המשימה — נחזור עם הערכה ותוכנית.