SaaS רב־דיירים הוא קודם כול מערכת הרשאות — ורק אחר כך תבנית מסד נתונים

SaaS רב־דיירים הוא קודם כול מערכת הרשאות — ורק אחר כך תבנית מסד נתונים

רב־דיירות מאפשרת למוצר SaaS לחלוק תשתית בלי לערבב אחריות, נתונים או השפעה תפעולית בין לקוחות. לכן השאלה החשובה אינה רק היכן נשמרת כל שורה, אלא כיצד המערכת יודעת בכל רגע מי פועל, עבור איזה ארגון, באיזו סמכות, על איזה משאב ובאילו גבולות. ארכיטקטורה טובה הופכת את התשובות האלה לחוזה שחוצה API, מסד נתונים, תורים, קבצים, cache, כלי תמיכה ומערכות תצפית.

1. דייר הוא גבול עסקי, לא עמודה טכנית

במוצר B2B, tenant עשוי להיות חברה, יחידה עסקית, סביבת עבודה או ישות משפטית. אותו משתמש יכול להשתייך לכמה דיירים, להחזיק תפקיד שונה בכל אחד מהם ולעבור ביניהם במהלך אותה כניסה. לכן tenant_id אינו רק שדה שמוסיפים לטבלה. הוא חלק מהקשר החלטת ההרשאה: זהות האדם או השירות, החברות הפעילה שלו, תפקידו, סביבת העבודה, הפעולה המבוקשת והמשאב המסוים. אם הצוות מתייחס לדייר כאל פילטר נתונים בלבד, הוא עלול להגן על שאילתת SELECT אך לשכוח export, קובץ, webhook, תוצאת חיפוש או משימה אסינכרונית. AWS מגדירה בידוד דיירים כנושא יסוד ב־SaaS ומדגישה שאין אסטרטגיה אוניברסלית: תחום הפעילות, ציות, מודל הפריסה והשירותים הטכנולוגיים משפיעים על הגבול הנכון. מכאן נובעת החלטה מוצרית: להגדיר במפורש מהו דייר, אילו משאבים שייכים לו, אילו פעולות יכולות להיות חוצות דיירים ומי מוסמך לאשר אותן.

2. Authentication אינה Authorization

כניסה מוצלחת מוכיחה זהות ברמה מסוימת; היא אינה מוכיחה שהמשתמש רשאי לראות חשבונית, לשנות חבר צוות או לייצא מידע של ארגון מסוים. OWASP מנסחת את ההפרדה בפשטות: Authorization היא בדיקה שהפעולה המבוקשת מאושרת עבור ישות מסוימת, והיא שונה מאימות זהות. במערכת רב־דיירית יש שני ממדים של הרשאה: אנכי—למשל מנהל לעומת צופה; ואופקי—ארגון א׳ לעומת ארגון ב׳. כשל אופקי מסוכן במיוחד משום שמשתמש לגיטימי עלול להגיע למשאב לגיטימי של לקוח אחר. לכן מזהה אובייקט שקשה לנחש אינו בקרת גישה, ו־JWT שמכיל tenant_id אינו מספיק אם המערכת אינה מאמתת שהחברות עדיין פעילה. לכל בקשה נדרש מסלול החלטה עקבי: מי המבקש, מה הדייר שנבחר, האם הקשר ביניהם תקף עכשיו, איזו פעולה מתבקשת והאם המשאב שייך לאותו תחום הרשאה.

3. הקשר דייר צריך להיווצר מוקדם ולהישאר אמין

הקשר הדייר צריך להיקבע בשכבה מרכזית בתחילת הבקשה—middleware, gateway או interceptor—מתוך זהות שאומתה בצד השרת וחברות עדכנית. מזהה שמגיע ב־URL או בכותרת יכול לבחור דייר, אך אסור לו להעניק סמכות. לאחר האימות, המערכת צריכה להעביר הקשר פנימי שאינו ניתן להחלפה על ידי רכיב downstream. ברירת המחדל כאשר ההקשר חסר או לא תקף היא עצירה, לא שאילתה בלתי מסוננת. עיקרון זה חשוב גם לחוויית המשתמש: מעבר בין ארגונים צריך להיות ברור; מסך חייב להראות באיזו סביבת עבודה פועלים; וקישור עמוק לדייר אחר צריך להפעיל בדיקה מחדש ולא לשנות הקשר בשקט. כך הממשק, ה־API והלוגים מספרים אותה אמת. תכנון כזה מצמצם גם תקלות תמיכה, שבהן משתמש חושב שהוא מעדכן סביבת בדיקה אך למעשה פועל בייצור.

4. בוחרים מודל בידוד לפי סיכון ותפעול

שלושה דפוסים נפוצים הם מסד נפרד לכל דייר, schema נפרד, או טבלאות משותפות עם בעלות ברמת שורה; מערכות רבות משלבות ביניהם לפי סוג הנתון או שכבת השירות. מסד נפרד מעניק גבולות credentials, backup ולעיתים network חזקים יותר, אך מגדיל עלות של migrations, תצפית ותפעול. schema נפרד מצמצם חלק מהשיתוף אך דורש משמעת ב־grants, roles ו־search_path. טבלאות משותפות יעילות וגמישות, אך כל נתיב גישה חייב לאכוף בעלות ולהיבדק במקרי כשל. זו אינה תחרות על המודל “המאובטח ביותר” באופן מופשט. יש להתאים את הגבול לרגישות המידע, לדרישות לקוח ורגולציה, להיקף, ליכולת הצוות ולתוכנית התאוששות. מוצר יכול להציע tier ייעודי ללקוחות מסוימים, אבל עליו להסביר מה באמת מבודד: נתונים, חישוב, מפתחות, גיבויים, ביצועים או רק namespace.

5. RLS היא שכבת הגנה, לא קיצור דרך

PostgreSQL Row-Level Security מאפשרת למדיניות לקבוע אילו שורות תפקיד מסוים יכול לראות או לשנות. כאשר RLS מופעלת ואין מדיניות מתאימה, ההתנהגות היא deny by default—בסיס טוב להגנה בעומק. עם זאת, לתכונה יש גבולות: superuser ותפקיד עם BYPASSRLS עוקפים אותה; בעלי טבלה עשויים לעקוף מדיניות אלא אם מופעל FORCE ROW LEVEL SECURITY; אילוצי referential integrity יכולים לעקוף סינון לצורך שלמות נתונים; ומדיניות מורכבת עלולה ליצור race conditions. גם connection pool הוא סיכון אם הקשר דייר נשמר ברמת session ומחלחל לבקשה הבאה. OWASP ממליצה לקבוע הקשר transaction-local, לשחזר אותו בכל transaction ולוודא שבקשות רגילות משתמשות בתפקיד שאינו יכול לעקוף RLS. המשמעות המוצרית: RLS אינה מחליפה הרשאה ב־API, סיווג נתונים או בדיקות. היא חגורת בטיחות נוספת שחייבים לבדוק בתצורת הייצור האמיתית.

6. הדייר חייב לנוע עם העבודה האסינכרונית

תור משותף אינו גבול בידוד. כאשר בקשה יוצרת job, message, webhook או workflow ארוך, הקשר הדייר צריך להיות קשור לזהות המפיק ולפעולה שהורשתה—לא לשדה חופשי שהצרכן מאמין לו. הצרכן מאמת את נתיב ההפקה, משחזר את ההקשר ובודק שוב הרשאה על המשאב. בדיקה חוזרת חשובה כאשר העבודה מתעכבת: משתמש יכול לעזוב ארגון, מפתח API יכול להישלל או הרשאה יכולה להשתנות בין יצירת המשימה לביצועה. גם idempotency keys, retries, dead-letter queues וסדר הודעות צריכים להיות scoped לדייר כאשר התוצאה משתנה לפי דייר. אחרת retry של לקוח אחד עלול לחסום אחר, או מפתח כפילות משותף עלול למחוק פעולה תקפה. global job אמיתי—למשל חישוב מדד מערכת—צריך זהות והרשאה מפורשות, לא tenant מזויף שמקבל חריגים בכל הקוד.

7. Cache, חיפוש וקבצים הם חלק מגבול ההרשאה

דליפת רב־דיירות אינה חייבת לעבור דרך SQL. cache key שאינו כולל tenant, אינדקס חיפוש עם filter חסר, נתיב קובץ צפוי, CDN שמשרת response פרטי או signed URL שחי יותר מדי זמן—כולם יכולים לעקוף מסד מוגן היטב. לכן כל תוצאה מסווגת כ־global, tenant-scoped או user-scoped. אם ערך או הרשאה משתנים לפי דייר, זהות הדייר היא חלק מהמפתח, אך הפרדת key אינה מחליפה Authorization לפני הקריאה. אחסון אובייקטים צריך prefix או bucket policy שניתנים לאכיפה, ו־export צריך להיבנות מתוך snapshot מוגדר עם בעלות מתועדת. במנועי חיפוש ובאנליטיקה חשוב לבדוק reindex, replicas ו־preview tools, לא רק שאילתה ראשית. כלל שימושי הוא לעקוב אחר הנתון לאורך כל מחזור חייו: יצירה, cache, העתקה, export, שיתוף, גיבוי, שחזור ומחיקה.

8. כלי תמיכה הם הממשק המסוכן ביותר

צוות תמיכה צריך לעיתים לצפות בחשבון לקוח, לשחזר תקלה או לבצע פעולה חריגה. פתרון מהיר הוא “מצב אדמין” חוצה דיירים; פתרון אחראי הוא workflow הרשאה נפרד. גישת תמיכה צריכה לכלול סיבה, scope מצומצם, זמן תפוגה, אישור בהתאם לרגישות ורישום שאינו ניתן לשינוי. Impersonation חייב להיות בולט בממשק, לא פעולה שקטה שנראית כמו המשתמש; פעולות מסוימות, כמו העברת כספים או שינוי בעלות, עשויות להיות חסומות גם בזמן impersonation. הנתיב הזה נבדק ומנוטר בנפרד כי הוא עוקף הנחות רגילות של המוצר. גם כלי BI, SQL consoles וסקריפטים של הצלחת לקוח צריכים זהות שירות, הרשאות ו־audit. אם תמיכה תלויה בחיבור database בעל הרשאות מלאות, הבעיה אינה רק אבטחה—זה סימן שחסרה למוצר שכבת תפעול בטוחה.

9. בידוד כולל זמינות והוגנות, לא רק סודיות

לקוח אחד יכול לפגוע באחרים גם בלי לראות את המידע שלהם: job כבד ממלא תור, export צורך את pool החיבורים, webhook כושל יוצר retries אינסופיים או tenant גדול מנצל את כל מכסת החישוב. זהו noisy neighbor, והוא הופך ארכיטקטורת בידוד לשאלת SLO ומודל עסקי. מכסות וקצבי שימוש צריכים לשקלל דייר, משתמש, endpoint ומשאב גלובלי. לדיירים שונים יכולים להיות entitlements שונים, אך אסור ש־tier מסחרי יבטל מנגנוני הגנה מערכתיים. חשוב למדוד queue depth, concurrency, database connections, storage growth ועלות לפי דייר, לצד מגבלות שירות כוללות. מנגנון throttling צריך להסביר למשתמש מה קרה ומה אפשר לעשות. כך בקרת משאבים הופכת מחסימה מסתורית לחוזה מוצר מדיד בין הספק ללקוח.

10. Observability חייבת להיות מודעת לדייר וגם לפרטיות

כאשר כל log כולל tenant מאומת, אפשר לחקור מי ניסה לגשת למשאב, לזהות דפוסי denied cross-tenant ולתחם אירוע. אבל תצפית אינה הצדקה לרשום תוכן רגיש או secrets. יש להפריד בין מזהים תפעוליים לבין payload, לצמצם retention ולהגדיר מי רשאי לבצע שאילתה חוצת דיירים. trace צריך לקשר request, actor, tenant, resource, policy decision, job והפעולה שבוצעה, בלי להפוך את מערכת הלוגים לעותק נוסף של המידע העסקי. מדדים חשובים כוללים שיעור denial בלתי צפוי, כשלי policy, פעולות תמיכה, exports, שינויי חברות וחריגות מכסה. בהתראה אמיתית, הצוות צריך לענות במהירות על שלוש שאלות: אילו דיירים הושפעו, איזה גבול נכשל והאם הנתון נקרא, שונה או רק נחשף פוטנציאלית.

11. בודקים את השלילה, לא רק את ה־happy path

בדיקת “משתמש מארגון א׳ רואה את הפרויקט שלו” אינה מוכיחה בידוד. נדרש test matrix שבו אותה פעולה נבדקת עם משאב של דייר אחר, membership שפג תוקפה, role חסר, מזהה מנוחש, cache חם, connection ממוחזר, job שהתעכב, export גדול ונתיב admin. בדיקות property-based יכולות לייצר שילובים של actors ומשאבים; integration tests צריכות לפעול מול תפקידי database ותצורת RLS אמיתיים; ו־end-to-end בודק שגם UI, API ואחסון מסכימים. כדאי להחזיק שני דיירי בדיקה עם נתונים ייחודיים שקל לזהות ולבצע בדיקות canary לאחר deployment. כל table, index, bucket או queue חדש נכנס לרשימת נכסים מסווגת. שער השקה צריך לדרוש ראיות של denial, לא רק היעדר תקלות בסבב שימוש רגיל.

12. מחזור חיי הדייר הוא חלק מהארכיטקטורה

Onboarding יוצר יותר מחשבון: roles, namespaces, keys, quotas, storage, integrations ונתוני audit. שינוי תוכנית עשוי להזיז דייר בין מודלי בידוד. Offboarding חייב לבטל גישה, לעצור jobs, לסובב secrets, לטפל ב־webhooks, לייצא נתונים לפי חוזה ולמחוק עותקים בהתאם למדיניות retention. Restore הוא תרחיש רגיש במיוחד: גיבוי משותף עלול לכלול דיירים רבים, ולכן שחזור דייר יחיד דורש הרשאה ובדיקה משלו. תהליך lifecycle צריך להיות idempotent, ניתן לחידוש ומתועד. מוצר שמסוגל לצרף לקוח אך אינו יודע להוציאו בבטחה עדיין לא השלים את המודל הרב־דיירי שלו. זו גם נקודת אמת עסקית: היכולת להסביר מיקום, הפרדה, ייצוא ומחיקה של מידע היא חלק מאמון וממוכנות enterprise, לא רק סעיף security questionnaire.

13. שער החלטה לפני השקה

לפני production, הנהלה צריכה לקבל מסמך קצר שמחבר בין ארכיטקטורה לסיכון. הוא מגדיר מהו דייר; אילו נתונים ומשאבים שייכים לו; איזה מודל בידוד חל על כל data class; היכן נוצרת ומועברת זהות הדייר; אילו פעולות חוצות דיירים קיימות; כיצד תמיכה מקבלת גישה; מהן המכסות; ואיך בודקים, מנטרים, משחזרים ומוחקים. לכל גבול יש owner וראיה: policy, test, dashboard או runbook. NIST Zero Trust מחזקת את העיקרון שאין אמון משתמע בגלל מיקום רשת או בעלות, ושההגנה ממוקדת במשאבים. ב־SaaS, תרגום מעשי שלו הוא לאמת הקשר והרשאה בכל נתיב משמעותי. הארכיטקטורה הטובה ביותר אינה זו שמבטיחה שאין שיתוף; היא זו שמגדירה בדיוק מה משותף, מה מבודד ואיך המערכת מוכיחה את ההבדל שוב ושוב.

חזרה לכל המאמרים ←