【问题标题】:Row Level Security delete block predicate and cascade delete constraint issue行级安全删除块谓词和级联删除约束问题
【发布时间】:2021-03-25 04:57:53
【问题描述】:

考虑有以下表格:

带有列的客户表:

  • client_guid(uniqueidentifier - 主键)
  • client_description

带列的票务表:

  • ticket_guid(uniqueidentifier - 主键)
  • ticket_client_guid(uniqueidentifier - 客户端表的外键)
  • ticket_description.

附列表格:

  • attachment_guid(唯一标识符 - 主键)
  • attachment_ticket_guid(uniqueidentifier - 票证表的外键带有 ON DELETE CASCADE 约束)
  • 附件说明

考虑具有以下删除谓词功能,该功能应该允许用户仅删除附件与特定描述匹配的票:

    CREATE FUNCTION [Security].[fn_ticket_delete](@ticket_guid AS SYSNAME) 
returns TABLE 
WITH schemabinding 
AS 
    RETURN 
      SELECT 1 AS fn_securitypredicate_result 
      WHERE  (SELECT attachment_description 
              FROM   dbo.attachment 
              WHERE  attachment_ticket_guid = @ticket_guid) = 'some specific attachment description' 

然后我们有应用谓词 fn 的安全策略:

CREATE SECURITY POLICY [Security].[ticket_security] 
ADD BLOCK PREDICATE [Security].[fn_ticket_delete]([ticket_guid]) ON [dbo].[ticket] BEFORE DELETE
WITH (STATE = ON, SCHEMABINDING = ON)

问题:即使它的附件描述与安全谓词条件匹配也无法删除票证,因为附件记录信息不可用,在行级安全检查时 DML 已经已按照文档执行,删除级联约束已被应用,记录已被删除,但尚未提交。

注意:在应用相同条件的更新 RLS 限制的情况下,安全性工作得非常好,并且符合预期。

问题:我怎样才能克服这个问题并使删除策略按预期工作?

【问题讨论】:

  • 这是对ticket表的附加谓词,以确保ticket_client_guid与用户GUID匹配以验证ticket的删除访问权限?这个谓词是在不删除ticket的情况下验证删除附件的访问权限?
  • 这只是一个例子。如果其链接的附件描述与特定字符串不匹配,此谓词是关于不允许删除票证的。不要介意不根据会话上下文检查用户 guid,这只是为了简单。
  • 此外,这个问题与我之前发布的关于将 ticket_client_guid 与用户 guid 匹配的问题完全不同。
  • 考虑将函数的事务隔离级别(或作为其中的表提示)更改为 READCOMMITTED 之类的东西,以避免这些脏读。
  • 如果您正在测试对附件表的更改,那么谓词应该在附件表上。此外,如果这是一致性检查而不是安全检查,那么触发器可能会更好。

标签: sql-server row-level-security


【解决方案1】:

估计的删除计划或不存在的工单的实际删除计划:

delete from ticket where ticket_guid = newid()
--or
delete from ticket where ticket_guid is null

表明安全功能“驻留在”两个“删除”之间: 最左边的部分是 T-SQL 删除(delete 语句) 右侧是工单的集群删除(和附件的级联删除)

安全功能是“执行和评估”之后 [嵌套循环的内部]行被删除/删除[嵌套循环的外部]但之前删除行的已提交 [t-sql delete]。

高级描述可以是:对于每个工单行,删除工单和相应的附件,输出已删除的外键:ticket_guid(序列运算符,由底部 TableSpool 提供)和每个已删除的 guid(嵌套循环,外部)执行安全函数(嵌套循环的内部)左半连接,因为安全函数不能返回任何结果,如果函数的结果为空或不为空,则评估/断言,如果不为空,票行是“已删除”并移至下一张票。

如果安全函数引用了任何行被删除的表,那么这些表将在行删除之后重新访问,并且如果函数的结果取决于已删除的行,那么安全将始终失败。

这可以使用一个函数来测试,该函数检查假定的“待删除”(实际上它之前已被删除)ticket_guid 是否存在:

create table dbo.ticketX
(
    ticket_guid uniqueidentifier default(newsequentialid()) primary key clustered,
    ticket_client_guid uniqueidentifier,
    ticket_description varchar(10)
); 
go
insert into dbo.ticketX(ticket_client_guid, ticket_description)
select v.guid, v.descr
from 
(values (newid(), 'ticketA'), (newid(), 'ticketB'), (newid(), 'ticketC')) as v(guid, descr)
go
insert into dbo.ticketX(ticket_client_guid, ticket_description)
select v.guid, v.descr
from 
(values (newid(), 'abc'), (newid(), 'ticketE'), (newid(), 'ticketF')) as v(guid, descr)
go    

create function dbo.fn_ticketX_delete(@ticket_guid uniqueidentifier)
returns table 
with schemabinding 
as 
return
( 
    select 1 as fn_securitypredicate_result 
    where exists(select 1 from dbo.ticketX where ticket_guid = @ticket_guid)
)
go  

create security policy dbo.ticketX_security
add block predicate dbo.fn_ticketX_delete(ticket_guid) on dbo.ticketX before delete
with (state=on, schemabinding=on);
go

由于在删除行后对函数求值,删除的ticket_guid不存在,函数什么也不返回,安全检查总是失败:

--The attempted operation failed because the target object 'xyz.dbo.ticketX' has a block predicate that conflicts with this operation.
delete from dbo.ticketX;

可以使用调整后的安全函数来测试某些行被删除(直到违反安全性并回滚整个语句)的事实:

drop security policy dbo.ticketX_security;
go
--allow deletion of tickets with description like ticket%
create or alter function dbo.fn_ticketX_delete(@ticket_description varchar(10))
returns table 
with schemabinding 
as 
return
( 
    select 1 as fn_securitypredicate_result 
    where @ticket_description like 'ticket%'
    --where exists(select 1 from dbo.ticketX where ticket_guid = @ticket_guid)
)
go  
create security policy dbo.ticketX_security
add block predicate dbo.fn_ticketX_delete(ticket_description) on dbo.ticketX before delete
with (state=on, schemabinding=on);
go


delete from dbo.ticketX
output deleted.* --some rows "get deleted"

问题:如何通过脏读来克服这个问题并制作 删除策略按预期工作?

没有脏读。事实上,如果脏读是可能的,安全功能可能会起作用:

RETURN 
  SELECT 1 AS fn_securitypredicate_result 
  WHERE  (SELECT attachment_description 
          FROM   dbo.attachment with(nolock) --if this were possible/enforced, it could work ??
          WHERE  attachment_ticket_guid = @ticket_guid) = 'some 

由于创建安全策略声明有点误导,因此预期被颠覆了。
更准确的声明是:

CREATE SECURITY POLICY xyz... ADD BLOCK PREDICATE... after row is deleted BEFORE DELETE commits;

一般来说,最好以确定性的方式处理 RLS,即:在不受 DML 操作影响的值上评估常量的安全功能。在某种程度上,安全性本质上是确定性的。如果安全功能依赖于其他表(或非固定/持久的列/值*),则它变得不确定(且不可预测),但最终结果,安全策略评估必须是确定性的。

*您可以尝试在票据表上创建一个计算列,该列检查附件描述 = 'some specific description' 的存在并在安全函数中使用该列,但仍然是该列(不持久,因为它访问数据)是在删除票证和附件行之后计算的,它对于按照您设想的方式执行策略没有任何用处。

让我们考虑一下,只有在存在 链接到它的附件,附件作者基本上是在 描述:“请删除我的票 {signature}”。

如果您考虑要求,它与安全性无关==谁可以删除数据,它与根据您的业务规则的数据完整性有关:何时/在什么条件下可以删除数据。如果您想将此逻辑封装在 dbschema 中,那么附件触发器是执行它的最佳位置。

【讨论】:

    猜你喜欢
    • 2011-02-24
    • 1970-01-01
    • 2014-01-17
    • 2012-05-08
    • 1970-01-01
    • 2019-12-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多