Docker secrets: היכן דולפים מפתחות בסביבת קונטיינרים
כשל מתחיל בדרך כלל בשלב הבנייה: סיסמה או טוקן מועברים לתמונה דרך משתני סביבה וארגומנטים של בנייה.
הארגומנט שוקע במטא-נתונים של השכבה, המשתנה — בתצורה הסופית, וכל מי שיש לו גישה לתמונה יכול לראות אותם: docker history מציג את פקודות הבנייה, וערך מתוך משתנה הסביבה נקרא ישירות מתוך הקונטיינר הפעיל.
סוד שנכנס לשכבה נשאר שם גם אחרי שהסירו אותו מההוראה — השכבות הקודמות אינן נעלמות, ואי אפשר לנקות ערך מתמונה שכבר פורסמה, אלא רק לבנות מחדש.
נקודת הדליפה השנייה — הסביבה בשלב הריצה. כל תהליך בתוך הקונטיינר קורא את המשתנים של מרחב השמות שלו: מספיק להציץ לקובץ הסביבה של התהליך הראשון או לקרוא לרשימת התהליכים עם הצגת המשתנים.
היישום מעביר את הסביבה לתהליכי בן, מוסר אותה לדוחות שגיאה ולטלמטריה, ואם לתוך הקונטיינר ממופה הסוקט של מנוע הקונטיינרים, הסביבה נעשית גלויה לכל השירותים השכנים בצומת.
כאן גם נמצאות הרשאות התהליך: קונטיינר שמופעל כמשתמש-על אינו מפריד גישות, והסוד נקרא באותו קוד שמקבל בקשות חיצוניות.
ההרגל השלישי — להדפיס את התצורה. לוגים של בנייה ושל ריצת היישום מכילים מחרוזות חיבור עם סיסמה בפנים, פלט שגיאות בגישה הראשונה למסד הנתונים, דאמפים של משתנים בעת האתחול.
משם הלוגים האלה עוברים לאינדקס משותף עם תקופת שמירה ארוכה והרשאות קריאה רחבות, והסוד מתפזר בין עותקים מהר יותר מקצב ההחלפה שלו.
בואו נפרק איך זה נסגר בכלים המובנים של docker secrets: הערך מועבר לא דרך משתנה סביבה, אלא כקובץ שנגיש רק לשירות מסוים.
גישה מבודדת ורוטציה של מפתחות: אפקט מדיד
סודות בקונטיינרים חיים לא במשתני הסביבה של התמונה, אלא במאגר נפרד של האורקסטרטור: Docker ממפה את הערך לתוך הקונטיינר כקובץ ב-tmpfs ומוסר אותו רק לשירותים שהורשו לכך במפורש בתצורה. את האפקט אפשר לבדוק לפי ארבעה מדדים מדידים.
הזמן להכנסת מפתח חדש לפעולה
הרוטציה מצטמצמת ליצירת גרסה חדשה של הסוד ולהפעלה מחדש רק של אותם שירותים שמפנים אליה. עדכון של שירות אחד מתבצע בדקות במקום חלון השבתה מתואם לכל המערכת; את הגרסה הקודמת מוחקים אחרי שהצרכן האחרון קרא מחדש את הערך.
שיעור השירותים עם גישה מופרדת
הסוד נקשר לשירות מסוים, ולכן מסד הנתונים לא רואה את המפתח של שער התשלומים, ואפליקציית הווב — את פרטי הגישה לתור. מביאים את המדד לכל השירותים: לכל אחד בדיוק הסודות שבלעדיהם הוא לא עולה, והשאר לא נגישים עבורו.
בידוד סביבות וסוג כשלים שנעלם
פיתוח, בדיקות ופרודקשן לוקחים ערכים ממאגרים שונים — החלפת סביבה כבר לא מובילה לכתיבה למסד נתונים זר. כשקונטיינר אחד נפרץ, נעלם סוג שלם של כשלים: דליפה דרך דאמפ סביבה, לוגים של בנייה והיסטוריית שכבות של התמונה נעשית בלתי אפשרית.
מה נשאר בשליטה
תוקף החיים של המפתח, רשימת שירותי הצרכנים ועקבות הגרסה הקודמת — הכול בתצורה וביומן השינויים. כך הרוטציה מפסיקה להיות אירוע חד-פעמי והופכת לפעולה שגרתית.
Docker secrets מול משתני סביבה: מצבים ומגבלות
Docker secrets מוסר את הערך כקובץ ב-tmpfs בתוך הקונטיינר: התהליך קורא /run/secrets/<שם>, ומשתנה סביבה לא נשמר בשום מקום. משתני סביבה פשוטים יותר, אבל נראים ב-inspect, עוברים בירושה לתהליכי בן ונכנסים לדאמפים וללוגים — כשהתמונה או הדאמפ דולפים, הסיסמה דולפת יחד איתם.
| אפשרות | מתי מתאים | מגבלות |
|---|---|---|
| Docker secrets | אורקסטרטור עם תמיכה ב-secrets, מיפוי ל-tmpfs | היישום קורא קובץ, ולא משתנה מוכן |
| משתני סביבה | סטנד מקומי, פרמטרים לא קריטיים | נראים ב-inspect ובלוגים, עוברים בירושה לתהליכים |
| קבצים ממופים | גרסאות ישנות של compose, ניהול ידני | הסוד מונח על המארח בגלוי, רוטציה ידנית |
| מאגר סודות חיצוני | כמה סביבות וקלאסטרים, אבחון גישה | נדרש סוכן וגישה לרשת בעת האתחול |
בחירה לפי סביבה
בצומת בודד ובסטנד מספיק קובץ ממופה או משתני סביבה — אין שם נתונים קריטיים. בקלאסטר הערך מופץ רק לשירותים שהוקצו, ועדכון הסוד מיושם בלי לבנות מחדש את התמונה.
בחירה לפי סוג העומס
שירות ארוך-טווח קורא את הסוד פעם אחת באתחול ושומר אותו בזיכרון — כל מצב יתאים. קונטיינרים ומשימות קצרי-טווח מופעלים מחדש לעיתים קרובות, ולכן הרוטציה חשובה: במאגר חיצוני היא אוטומטית, ובקבצים ממופים היא דורשת הפעלה מחדש. המגבלה של המצב היא שהיישום צריך לקבל נתיב לקובץ, ולא מחרוזת מוכנה.
הטמעה בשלבים: מאבחון ועד מסירת נהלים
את העבודה מתחילים בתיעוד המצב הקיים: היכן כרגע מונחות סיסמאות, מפתחות גישה ותעודות, מי מקבל אותם ואיך. השלבים הולכים ברצף — כל אחד נשען על התוצאה של הקודם, ולכן בכל שלב רואים מה כבר סגור ומה לא.
- ספירת מלאי של סודות. אוספים רשימה: איזה סוד, איזה שירות משתמש בו, היכן הוא נשמר, באיזו תדירות הוא מוחלף. בפלט — מרשם עם בעלים וקטגוריות קריטיות.
- תרשים זרימות של סודות. משרטטים את המסלול של הערך מהמאגר לתהליך: מיפוי לקונטיינר, גישה לפי תפקיד השירות, רוטציה. התרשים מיד מראה עותקים מיותרים במשתני סביבה ובתמונות — אותם מסירים ראשונים.
- הגדרת המאגר וההרשאות. יוצרים סודות דרך Docker Secrets, מפרידים גישה לפי שירותים וסביבות, כך שסביבת הפיתוח לא רואה ערכי פרודקשן. הרשאות ניתנות לפי עקרון המינימום ההכרחי.
- העברת שירותים. מחברים אחד-אחד, עם בדיקה אחרי כל שלב. כך שגיאה מתמקדת בשירות אחד ולא מפילה את כל הקונטור. העלות מחושבת לאחר אבחון.
- בדיקת כשלים. שוברים בעצמנו: מבטלים גישה, מחליפים ערך, עוצרים את המאגר — ובודקים איך השירות שורד את זה.
- מסירת נהלים. מוסרים את המרשם, התרשים, סדר הרוטציה והנחיות לתורנות.
בדיקה על סטנד וסדר החזרה לאחור
תרחישי עומס ותרחישי כשל מריצים קודם על עותק של קונטור העבודה — עם אותם תפקידים והרשאות, אבל עם ערכי בדיקה. חזרה לאחור צפויה בכל שלב: כל עוד השירות לא אישר קבלת הערך החדש, הישן נשאר תקף, ולכן החזרה היא מיתוג לרשומה הקודמת, ולא שחזור מגיבוי.
תצורות, תרשימים ונהלים: מה נשאר אצלכם
אחרי מסירת הפרויקט נשארת אצלכם לא ערימה של קבצים מפוזרים, אלא מערכת עובדת לניהול סודות: תצורות, תיאור זרימות וכללים שלפיהם הצוות פועל בלי מעורבות שלנו. להלן — מה בדיוק מתועד ומועבר.
- תצורות של סודות: הצהרות על סודות והקשרן לשירותים, דרכי האספקה לקונטיינר, ההתנהגות בהיעדר סוד — הוספת ערך חדש הופכת לעריכה של מקום אחד, ולא לפעולה ידנית על השרת.
- תרשים זרימות: מהיכן הסוד נלקח, באילו סביבות הוא עובר, היכן הוא נמצא בזמן ריצת השירות והיכן אסור לו להיות לעולם.
- מדיניות גישה: מטריצת «שירות — סוד — רמת גישה», כולל הפרדה בין סביבות ותפקידים.
- נוהל רוטציה: תדירות, סדר הפעולות, האחראי ונקודת התיעוד של עובדת ההחלפה.
- תיעוד: תיאור הארכיטקטורה, ההחלטות שהתקבלו והמגבלות המודעות — כדי שהצוות הבא לא יגלה אותן מחדש.
- הנחיות לצוות: תרחישים צעד-אחר-צעד «להוסיף סוד», «לבטל גישה», «לבדוק תצורה», «לחזור לערך הקודם».
- רשימת בדיקות לפני עלייה: מה נבדק אוטומטית ומה נשאר לסקירה.
מדיניות גישה
מתארים את ההרשאות כך שהקונטיינר יראה רק את הסודות שלו ושום דבר מיותר: הגישה ניתנת לשירות, ולא למארח, ומשתנה דרך התצורה, בלי להיכנס למכונה. בנפרד מפרטים מי מהאנשים יש לו גישה לערכים עצמם — בדרך כלל מדובר במעגל צר, והשאר עובדים עם שמות והפניות.
נוהל רוטציה
הרוטציה נבנית בלי השבתה: הערך החדש נוצר לצד הישן, השירות מקבל את שניהם, ורק לאחר אישור הערך הישן מבוטל. בנוהל מתועדים התדירות, האחראי וסדר הפעולות בעת פגיעה — כשצריך להחליף מיד ולפי ענף אחר של התרחיש.
מקרה בוחן: הגירת סודות של שירות בלי השבתה
שירות של כשלושים קונטיינרים חי על משתני סביבה: הרשאות גישה למסד הנתונים, טוקנים של שירותים חיצוניים ומפתחות חתימה הופצו בקובץ אחד, שהיה מונח במאגר הקוד לצד מניפסטים של פריסה. רוטציה של ערך אחד ארכה ערב שלם — עריכת הקובץ, בנייה מחדש של התמונות, עצירת קבוצת קונטיינרים והעלאה מחדש. במהלך רבעון דחו את הפעולה הזו שלוש פעמים: חלון ההשבתה לא הסתדר עם פעילות המוצר.
מצב התחלתי: ערך אחד — ארבעה עותקים
המפתח התקיים בו-זמנית במאגר הקוד, במשתני הסביבה של הקונטיינר, בפלט האבחון ובלוגים של היישום בעת חיבור שנכשל. את הערך קרא כל מי שהייתה לו גישה לבדיקת הקונטיינר על המארח, וביטול ההרשאות נעשה ידנית: קודם כיבו את השירות, אחר כך החליפו את הערך, ואז העלו אותו. מחזור רוטציה מלא — ארבע עד שש שעות, שליש מהערכים שימשו שוב, ובדיקות אוטומטיות לדליפה לא היו אף אחת.
התוצאה: רוטציה בדקות ובדיקה על קונטור הפרודקשן
הערכים הועברו ל-Docker secrets: המאגר מוסר אותם לקונטיינר כקובץ במקטע זיכרון נפרד, הנגיש רק לתהליך. תאימות לאחור נשמרה בקריאה כפולה — השירות מנסה קודם את הקובץ, ואחר כך את משתנה הסביבה; המיתוג נעשה בדגל אחד בתצורה, והחזרה לאחור — באותו דגל בחזרה, בלי לבנות מחדש את התמונות.
בדקנו בשלבים: עותק של התעבורה, אחר כך עשירית מהצמתים, ואז הכול. שיעור שגיאות החיבור לא עלה, זמן הרוטציה הצטמצם לדקות, ההשבתה — אפס, נותר עותק אחד של הערך, ושיעור השירותים עם בדיקה אוטומטית לדליפה הגיע למאה אחוזים.
מה עושים אם הסודות כבר מונחים במשתני סביבה
משתני סביבה אינם מבודדים: את הערך רואה כל תהליך בתוך הקונטיינר, הוא נכנס לאבחון, לתיאור השירות וללוגים כבר בקריסה הראשונה. לכן החלפת ערכים אינה עריכה אחת, אלא הגירה בשלבים, שבה המקור החדש והישן עובדים במקביל לזמן מה.
המעבר ל-docker secrets נעשה דרך קונטור כפול: הקוד קורא את הערך קודם מהקובץ הממופה, ובהיעדרו נופל בחזרה למשתנה הסביבה. הקובץ חי באזור זיכרון נפרד של הקונטיינר ואינו שוקע לא בתמונה ולא בהיסטוריית ההרצה. את הקונטור מסירים כשכל המופעים של השירות עבדו על המקור החדש מחזור שחרור מלא.
את סדר ההעברה קובעים לפי מחיר הטעות. ראשונים הולכים פרטי גישה יוצאים — טוקנים לשירותים חיצוניים, מפתחות תורים: ההחלפה שלהם הפיכה והיא מתבטאת רק בכשלים של אינטגרציות. אחר כך מפתחות הצפנה וחתימה, שבהם נדרש סינכרון בין הצרכנים. ובסוף — הרשאות גישה למסדי נתונים, כי שם שגיאה עוצרת את הכתיבה.
עם ערכים שדלפו נוהגים אחרת מאשר עם ערכים פעילים: כל מה שהיה במשתנה סביבה נחשב לחשוף, ולא מעבירים אותו — מחליפים אותו. את הרוטציה מנהלים בלי השבתה — קודם מפיקים ערך חדש, מעבירים את הצרכנים, ואחר כך מכבים את הישן, כדי שהחזרה לאחור תישאר אפשרית עד סוף ההעברה.
כשהצרכן האחרון הועבר, מסירים את המשתנה מתיאור השירות ושמים בדיקה שתופסת את חזרתו לאבחון וללוגים. משם פועל נוהל הרוטציה — לפי תוקף ולפי אירוע.
ספקות טכניים: רוטציה, חזרה לאחור, תאימות סביבות
הספקות בנוגע לסודות הם בדרך כלל אותם ספקות: האם יהיה צורך לעצור את השירות לשם רוטציה, מה לעשות אם הערך החדש יתברר כשגוי, והאם הערכים יצופו במקומות שבהם אסור להם להיות. להלן — תשובות מעשיות לשאלות שנשאלות בתדירות הגבוהה ביותר.
האם אפשר להחליף סוד בלי להפעיל מחדש את הקונטיינר?
Docker Secrets מזריק את הערך כקובץ בתוך הקונטיינר, ולכן תהליך שכבר רץ רואה את הגרסה הישנה עד להפעלה מחדש. את הרוטציה עושים בשני שלבים: היישום קורא את הסוד בכל פנייה, וההפצה נעשית משימה-משימה — בלי השבתת השירות.
איך מחזירים לאחור סוד שנפגם?
את הערך הקודם שומרים כסוד נפרד עם סיומת גרסה: החזרה לאחור היא החלפת ההפניה בתיאור השירות ופריסה מחדש. מחזיקים שתי גרסאות, ואת הערך הישן מוחקים לאחר שהעבודה עם החדש אושרה.
האם זה עובד אותו דבר בסביבות שונות?
ההיגיון אחד, המקור של הערך שונה: בסביבת הפרודקשן — סוד של האורקסטרטור, ובאופן מקומי — קובץ מחוץ למאגר הקוד. היישום לוקח את הערך דרך ממשק אחיד, ולכן משתנה רק דרך האספקה.
האם ערכי הסודות ייכנסו ללוגים?
הרשאות על הקובץ יש רק לחשבון השירות, והאבחון כותב שמות של סודות, אבל לא את התוכן. בדיקת דליפות של ערכים מוטמעת בבנייה, ולכן פלט מקרי ללוג נתפס עוד לפני השחרור.
בואו נדבר על המשימה: קונטור סודות לסטאק ולתהליכים שלכם
כדי שההערכה תהיה עניינית ולא «לפי תחושה», אנחנו צריכים מינימום נתוני פתיחה על הקונטור שלכם. שלחו את מה שיש בהישג יד — את השאר נדייק בשיחה ונחזור עם הערכה ותוכנית עבודה.
- תרשים שירותים: אילו קונטיינרים יש בקונטור, איך הם מקושרים, מי קורא את הסודות — היישום באתחול, הבנייה או משימות מתוזמנות.
- קונטורים: כמה סביבות (פיתוח, בדיקות, פרודקשן) ובמה הן נבדלות בהרכב הסודות ובהרשאות הגישה.
- סדר הרוטציה: מי יוזם אותה, באיזו תדירות, ומה צריך לקרות עם קונטיינרים שכבר רצים אחרי החלפת הערך.
- מצב נוכחי: היכן הסודות מונחים כעת — בקבצים בתוך התמונה, במשתני סביבה, במערכת לניהול תצורה או במאגר חיצוני.
- מגבלות: דרישות של מחלקת האבטחה והמבקרים, הפרדת גישה לפי תפקידים, איסורים על אחסון בגלוי במאגר הקוד ובלוגים.
- סדר החזרה לאחור: האם מותר להפעיל מחדש את השירות בעת עדכון סוד, ובאיזו מסגרת זמן זה צריך לקרות.
- תמיכה אחרי ההטמעה: האם נדרש נוהל רוטציה, הדרכת הצוות ותורנות בתקופת העדכונים הראשונים.
לפי נתוני הפתיחה האלה נחזור עם תוכנית: שלבים, הרכב השינויים במניפסטים ובהרשאות, סדר הרוטציה והחזרה לאחור, ומה בודקים בכל שלב. משם אנחנו נשארים בקשר — עוזרים לנהל את הנוהל ולנתח אירועים, עד שהצוות יתחיל לנהל את הסודות באופן עצמאי.
תארו את המשימה במייל או בשיחה קצרה — זה מספיק כדי להתחיל.







