יישום אבטחה ברמת השורה

הושלם

Row-Level Security (RLS) מאפשרת לך לשלוט בגישה לשורות בטבלת מסד הנתונים בהתבסס על מאפייני המשתמש שמבצע שאילתה. בניגוד להרשאות ברמת הטבלה שמעניקות או מונעות גישה לטבלאות שלמות, RLS מסנן שורות באופן דינמי כך שהמשתמשים רואים רק את הנתונים שהם מורשים לגשת אליהם.

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

הבנת רכיבי RLS

Row-Level אבטחה משתמשת בשני רכיבים שעובדים יחד: פרדיפיקטים של אבטחה ומדיניות אבטחה.

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

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

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

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

יצירת פרדיקטים מסננים

נתחיל ביצירת פונקציית פרדיקט שמעריכה גישה לשורות. הפונקציה מקבלת פרמטרים המייצגים את ערכי העמודה לבדיקה ומחזירה טבלה עם שורה אחת כאשר יש גישה מותרת.

הנה תרחיש נפוץ: יישום רב-שוכרים שבו לכל שורה יש עמודה TenantID :

CREATE SCHEMA Security;
GO

CREATE FUNCTION Security.fn_TenantAccessPredicate(@TenantID int)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS fn_TenantAccessPredicate_Result
    WHERE @TenantID = CAST(SESSION_CONTEXT(N'TenantID') AS int);

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

EXEC sp_set_session_context @key = N'TenantID', @value = 42;

בתרחישים המבוססים על משתמשי מסד נתונים ולא על הקשר סשן, הנשואה יכולה להתייחס למשתמש הנוכחי:

CREATE FUNCTION Security.fn_SalesRepPredicate(@SalesRepID int)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS fn_SalesRepPredicate_Result
    WHERE @SalesRepID = DATABASE_PRINCIPAL_ID()
       OR IS_MEMBER('SalesManagers') = 1;

תנאי זה מאפשר לנציגי מכירות לראות את הרשומות שלהם בעוד שמנהלים בתפקיד SalesManagers יכולים לראות את כל הרשומות.

יצירת מדיניות אבטחה

לאחר שהגדרת את פונקציות הניבוי שלך, צור מדיניות אבטחה שמיישמת אותן על טבלאות:

CREATE SECURITY POLICY TenantSecurityPolicy
ADD FILTER PREDICATE Security.fn_TenantAccessPredicate(TenantID)
    ON dbo.Orders,
ADD FILTER PREDICATE Security.fn_TenantAccessPredicate(TenantID)
    ON dbo.OrderDetails
WITH (STATE = ON);

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

אפשר לשלב בין מסננים לחסימה במדיניות אחת:

CREATE SECURITY POLICY SalesSecurityPolicy
ADD FILTER PREDICATE Security.fn_SalesRepPredicate(SalesRepID)
    ON dbo.CustomerAccounts,
ADD BLOCK PREDICATE Security.fn_SalesRepPredicate(SalesRepID)
    ON dbo.CustomerAccounts AFTER INSERT,
ADD BLOCK PREDICATE Security.fn_SalesRepPredicate(SalesRepID)
    ON dbo.CustomerAccounts AFTER UPDATE
WITH (STATE = ON);

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

מימוש דפוסי גישה היררכיים

ארגונים רבים זקוקים לגישה היררכית לנתונים — מנהלים צריכים לראות את הנתונים של כפיפיהם. אפשר ליישם זאת על ידי שילוב RLS עם טבלת היררכיית ניהול.

הנה פונקציה שעוברת את ההיררכיה:

CREATE FUNCTION Security.fn_HierarchyPredicate(@OwnerID int)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
    WITH EmployeeHierarchy AS (
        SELECT EmployeeID, ManagerID
        FROM dbo.Employees
        WHERE EmployeeID = DATABASE_PRINCIPAL_ID()
        
        UNION ALL
        
        SELECT e.EmployeeID, e.ManagerID
        FROM dbo.Employees e
        INNER JOIN EmployeeHierarchy h ON e.ManagerID = h.EmployeeID
    )
    SELECT 1 AS fn_HierarchyPredicate_Result
    WHERE @OwnerID IN (SELECT EmployeeID FROM EmployeeHierarchy);

ביטוי הטבלה המשותפת הרקורסיבי (CTE) בונה את שרשרת העובדים המלאה המדווחת למשתמש הנוכחי. הפרדיקט מאפשר גישה לכל שורה שבבעלות מישהו בהיררכיה זו.

הערה

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

ניהול מדיניות אבטחה

מדיניות אבטחה תומכת במספר פעולות ניהול לתחזוקה שוטפת. אתה יכול להשבית פוליסה זמנית מבלי להסיר אותה:

ALTER SECURITY POLICY TenantSecurityPolicy
WITH (STATE = OFF);

ניתן להוסיף פרדיפיקטים חדשים למדיניות קיימת:

ALTER SECURITY POLICY TenantSecurityPolicy
ADD FILTER PREDICATE Security.fn_TenantAccessPredicate(TenantID)
    ON dbo.Shipments;

או להסיר פרדיקטים כאשר טבלאות כבר לא זקוקות לסינון:

ALTER SECURITY POLICY TenantSecurityPolicy
DROP FILTER PREDICATE ON dbo.Orders;

לצפייה במדיניות אבטחה קיימת ובבסיסיה:

SELECT p.name AS PolicyName,
       p.is_enabled,
       o.name AS TableName,
       pred.predicate_definition
FROM sys.security_policies p
INNER JOIN sys.security_predicates pred ON p.object_id = pred.object_id
INNER JOIN sys.objects o ON pred.target_object_id = o.object_id;

עצה

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

Row-Level Security פועלת בשקיפות עם תצוגות, פרוצדורות מאוחסנות ואובייקטים אחרים של מסד הנתונים שמבצעים שאילתות בטבלאות המוגנות. משתמשים שניגשים לנתונים בכל נתיב מקבלים תוצאות מסוננות עקביות בהתאם להקשר האבטחה שלהם.