ใช้การรักษาความปลอดภัยระดับแถว
การรักษาความปลอดภัยระดับแถว (RLS) ไม่ได้ใช้การเข้ารหัสลับและดําเนินการในระดับฐานข้อมูลเพื่อจํากัดการเข้าถึงตารางโดยใช้นโยบายความปลอดภัยที่ยึดตามการเป็นสมาชิกกลุ่มหรือบริบทการอนุญาต ฟังก์ชันนี้จะเทียบเท่ากับส่วนคำสั่ง WHERE
นโยบายความปลอดภัยจะเรียกใช้ฟังก์ชันที่ให้ค่าตารางแบบอินไลน์เพื่อป้องกันการเข้าถึงแถวในตาราง
เพรดิเคตจะกําหนดว่าผู้ใช้รายนั้นมีสิทธิ์เข้าถึงข้อมูลที่เกี่ยวข้องหรือไม่ โดยขึ้นอยู่กับแอตทริบิวต์ของผู้ใช้ เมื่อคุณเรียกใช้คิวรีกับตาราง นโยบายความปลอดภัยจะใช้ฟังก์ชันเพรดิเคต RLS สามารถใช้งานได้ง่ายเท่า WHERE CustomerId = 29 หรือซับซ้อนตามความต้องการ ขึ้นอยู่กับข้อกําหนดทางธุรกิจ
มีนโยบายความปลอดภัยสองชนิดที่ได้รับการสนับสนุนโดยการรักษาความปลอดภัยระดับแถว:
เพรดิเคตตัวกรอง - จํากัดการเข้าถึงข้อมูลที่ละเมิดเพรดิเคต
เข้าถึง คำจำกัดความ เลือก ไม่สามารถดูแถวที่ถูกกรอง อัปเดต ไม่สามารถอัปเดตแถวที่ถูกกรองได้ ลบ ไม่สามารถลบแถวที่มีการกรองได้ แทรก N/A บล็อกเพรดิเคต - จํากัดการเปลี่ยนแปลงข้อมูลที่ละเมิดเพรดิเคต
เข้าถึง คำจำกัดความ หลังการแทรก ป้องกันไม่ให้ผู้ใช้แทรกแถวที่มีค่าที่ละเมิดเพรดิเคต หลังการอัปเดต ป้องกันไม่ให้ผู้ใช้อัปเดตแถวเป็นค่าที่ละเมิดเพรดิเคต ก่อนการอัปเดต ป้องกันไม่ให้ผู้ใช้อัปเดตแถวที่ละเมิดเพรดิเคตในขณะนี้ ก่อนลบ บล็อกการดําเนินการลบถ้าแถวละเมิดเพรดิเคต
เนื่องจากตัวควบคุมการเข้าถึงถูกกําหนดค่าและนําไปใช้ในระดับฐานข้อมูล แอปพลิเคชันจะเปลี่ยนน้อยที่สุด - ถ้ามี นอกจากนี้ ผู้ใช้สามารถเข้าถึงตารางได้โดยตรง และสามารถคิวรีข้อมูลของตนเองได้
การรักษาความปลอดภัยระดับแถวมีการใช้งานในสามขั้นตอนหลัก:
- สร้างผู้ใช้หรือกลุ่มที่คุณต้องการแยกการเข้าถึง
- สร้างฟังก์ชันค่าตารางแบบอินไลน์ที่กรองผลลัพธ์ตามเพรดิเคตที่กําหนด
- สร้างนโยบายความปลอดภัยสําหรับตาราง โดยกําหนดฟังก์ชันที่สร้างไว้ก่อนหน้านี้
คําสั่ง T-SQL ต่อไปนี้สาธิตวิธีใช้ RLS ในสถานการณ์ที่การเข้าถึงของผู้ใช้ถูกแยกจากผู้เช่า:
-- Create supporting objects for this example
CREATE TABLE [Sales] (SalesID INT,
ProductID INT,
TenantName NVARCHAR(10),
OrderQtd INT,
UnitPrice MONEY)
GO
INSERT INTO [Sales] VALUES (1, 3, 'Tenant1', 5, 10.00);
INSERT INTO [Sales] VALUES (2, 4, 'Tenant1', 2, 57.00);
INSERT INTO [Sales] VALUES (3, 7, 'Tenant1', 4, 23.00);
INSERT INTO [Sales] VALUES (4, 2, 'Tenant2', 2, 91.00);
INSERT INTO [Sales] VALUES (5, 9, 'Tenant3', 5, 80.00);
INSERT INTO [Sales] VALUES (6, 1, 'Tenant3', 5, 35.00);
INSERT INTO [Sales] VALUES (7, 3, 'Tenant4', 8, 11.00);
-- View all the rows in the table
SELECT * FROM Sales;
ถัดไป สร้างผู้ใช้ และอนุญาตให้พวกเขาเข้าถึงตาราง ยอดขาย ในตัวอย่างนี้ ผู้ใช้แต่ละรายเป็นผู้รับผิดชอบสําหรับผู้เช่าที่เฉพาะเจาะจง ผู้ใช้ TenantAdmin มีสิทธิ์เข้าถึงเพื่อดูข้อมูลจากผู้เช่าทั้งหมด
CREATE USER [TenantAdmin] WITH PASSWORD = '<strong password>'
GO
CREATE USER [Tenant1] WITH PASSWORD = '<strong password>'
GO
CREATE USER [Tenant2] WITH PASSWORD = '<strong password>'
GO
CREATE USER [Tenant3] WITH PASSWORD = '<strong password>'
GO
CREATE USER [Tenant4] WITH PASSWORD = '<strong password>'
GO
GRANT SELECT ON [Sales] TO [TenantAdmin]
GO
GRANT SELECT ON [Sales] TO [Tenant1]
GO
GRANT SELECT ON [Sales] TO [Tenant2]
GO
GRANT SELECT ON [Sales] TO [Tenant3]
GO
GRANT SELECT ON [Sales] TO [Tenant4]
GO
ถัดไป เราจะสร้างสคีมาใหม่ ฟังก์ชันที่ให้ค่าตารางแบบอินไลน์ และอนุญาตให้ผู้ใช้เข้าถึงฟังก์ชันใหม่ เพรดิเคต WHERE @TenantName = USER_NAME() OR USER_NAME() = 'TenantAdmin' จะประเมินถ้าชื่อผู้ใช้ที่ดําเนินการคิวรีตรงกับค่าคอลัมน์ TenantName
CREATE SCHEMA sec;
GO
--Create the filter predicate
CREATE FUNCTION sec.tvf_SecurityPredicatebyTenant(@TenantName AS NVARCHAR(10))
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS result
WHERE @TenantName = USER_NAME() OR USER_NAME() = 'TenantAdmin';
GO
--Grant users access to inline table-valued function
GRANT SELECT ON sec.tvf_SecurityPredicatebyTenant TO [TenantAdmin]
GO
GRANT SELECT ON sec.tvf_SecurityPredicatebyTenant TO [Tenant1]
GO
GRANT SELECT ON sec.tvf_SecurityPredicatebyTenant TO [Tenant2]
GO
GRANT SELECT ON sec.tvf_SecurityPredicatebyTenant TO [Tenant3]
GO
GRANT SELECT ON sec.tvf_SecurityPredicatebyTenant TO [Tenant4]
GO
--Create security policy and add the filter predicate
CREATE SECURITY POLICY sec.SalesPolicy
ADD FILTER PREDICATE sec.tvf_SecurityPredicatebyTenant(TenantName) ON [dbo].[Sales]
WITH (STATE = ON);
GO
ในตอนนี้ เราพร้อมที่จะทดสอบการเข้าถึง:
EXECUTE AS USER = 'TenantAdmin';
SELECT * FROM dbo.Sales;
REVERT;
EXECUTE AS USER = 'Tenant1';
SELECT * FROM dbo.Sales;
REVERT;
EXECUTE AS USER = 'Tenant2';
SELECT * FROM dbo.Sales;
REVERT;
EXECUTE AS USER = 'Tenant3';
SELECT * FROM dbo.Sales;
REVERT;
EXECUTE AS USER = 'Tenant4';
SELECT * FROM dbo.Sales;
REVERT;
ผู้ใช้ TenantAdmin ควรเห็นแถวทั้งหมด ผู้ใช้ ผู้เช่า 1, ผู้เช่า 2, ผู้เช่า 3 และ ผู้เช่า 4 ควรเห็นเฉพาะแถวของตนเองเท่านั้น
หากคุณเปลี่ยนนโยบายความปลอดภัยด้วย WITH (STATE = OFF);คุณจะสังเกตเห็นว่าผู้ใช้เห็นแถวทั้งหมด
หมายเหตุ
มีความเสี่ยงที่จะเกิดข้อมูลรั่วไหลหากผู้โจมตีเขียนคิวรีด้วยคําสั่งที่สร้างขึ้น WHERE เป็นพิเศษ และตัวอย่างเช่น ข้อผิดพลาดการหารด้วยศูนย์ เพื่อบังคับใช้ข้อยกเว้นหาก WHERE เงื่อนไขเป็นจริง ซึ่งเรียกว่าการโจมตีจากช่องทางด้านข้างของ ควรจํากัดความสามารถของผู้ใช้ในการเรียกใช้คิวรีที่ไม่ได้วางแผนไว้เมื่อใช้การรักษาความปลอดภัยระดับแถว
กรณีการใช้งาน
การรักษาความปลอดภัยระดับแถวเหมาะสําหรับหลายสถานการณ์ รวมถึง:
- เมื่อคุณต้องการแยกการเข้าถึงข้อมูลของแผนกในระดับแถว
- เมื่อคุณต้องการจํากัดการเข้าถึงข้อมูลของลูกค้าให้เหลือเพียงข้อมูลที่เกี่ยวข้องกับบริษัทของพวกเขาเท่านั้น
- เมื่อคุณต้องการจํากัดการเข้าถึงเพื่อวัตถุประสงค์ด้านการปฏิบัติตามกฎระเบียบ
แนวทางปฏิบัติ
ต่อไปนี้คือหลักปฏิบัติที่ดีที่สุดบางประการที่ควรพิจารณาเมื่อใช้ RLS:
- สร้าง Schema แยกต่างหากสําหรับฟังก์ชันเพรดิเคต และนโยบายความปลอดภัย
- หลีกเลี่ยงการแปลงชนิดในฟังก์ชันเพรดิเคต
- หลีกเลี่ยงการใช้การรวมตารางที่มากเกินไปและการเรียกใช้ซ้ําในฟังก์ชันเพรดิเคต