实现行级别安全性
行级别安全性(RLS) 不使用加密,在数据库级别运行,以使用基于组成员身份或授权上下文的安全策略来限制对表的访问。 此功能等效于 WHERE 子句。
安全策略调用内联表值函数来保护对表中行的访问。
根据一个用户的属性,谓词确定该用户是否有权访问相关信息。 当你针对表运行查询时,安全策略会应用谓词函数。 根据业务需求,RLS 可以像 WHERE CustomerId = 29 一样简单,也可以根据需要变得复杂。
行级别安全性支持两种类型的安全策略:
筛选谓词 - 限制违反谓词的数据访问。
访问权限 定义 SELECT 无法查看已筛选的行。 UPDATE 无法更新已筛选的行。 DELETE 无法删除已筛选的行。 INSERT 不适用。 阻止谓词 - 限制违反谓词的数据更改。
访问权限 定义 AFTER INSERT 防止用户插入其值违反谓词的行。 AFTER UPDATE 防止用户将行更新为违反谓词的值。 BEFORE UPDATE 防止用户更新当前违反谓词的行。 BEFORE DELETE 如果行违反谓词,则阻止删除操作。
由于访问控制是在数据库级别配置和应用的,因此应用程序更改最少(如果有)。 此外,用户可以直接访问表,并且可以查询自己的数据。
行级别安全性是通过三个主要步骤实现的:
- 创建要隔离访问权限的用户或组。
- 创建内联表值函数,该函数根据定义的谓词筛选结果。
- 为表创建安全策略,并分配之前创建的函数。
以下 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;
接下来,创建用户并向其授予对 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 用户应看到所有行。 Tenant1、Tenant2、Tenant3 和 Tenant4 用户只能看到自己的行。
如果使用 WITH (STATE = OFF); 更改安全策略,你会注意到用户将看到所有行。
注意
如果攻击者使用特制的 WHERE 子句和例如一个除以 0 的错误来编写查询,以在 WHERE 条件为 true 时强制出现异常,则存在信息泄露的风险。 这称为旁道攻击。 使用行级别安全性时,最好限制用户运行计划外查询的能力。
用例
行级别安全性是许多场景下的理想选择,其中包括:
- 需要在行级别隔离部门访问时。
- 需要将客户的数据访问权限限制为仅访问与其公司相关的数据时。
- 需要出于符合性目的限制访问时。
最佳做法
在实现 RLS 时,要考虑下面的一些最佳做法:
- 为谓词函数和安全策略创建单独的架构。
- 避免谓词函数中的类型转换。
- 避免在谓词函数中使用过多的表联接和递归。