Sql server 行级安全删除块谓词和级联删除约束问题

Sql server 行级安全删除块谓词和级联删除约束问题,sql-server,row-level-security,Sql Server,Row Level Security,考虑使用以下表格: 包含列的客户端表: 客户端guid(唯一标识符-主键) 客户描述 带列的票证表: 票证guid(唯一标识符-主键) 票证\客户端\ guid(uniqueidentifier-客户端表的外键) 车票描述 带列的附件表: 附件\u guid(唯一标识符-主键) 附件\u票证\u guid(uniqueidentifier-票证表的外键,带有ON DELETE级联约束) 附件说明 考虑使用以下删除谓词函数,该函数应允许用户仅删除附件与特定描述匹配的票证: CR

考虑使用以下表格:

包含列的客户端表:

  • 客户端guid(唯一标识符-主键)
  • 客户描述
带列的票证表:

  • 票证guid(唯一标识符-主键)
  • 票证\客户端\ guid(uniqueidentifier-客户端表的外键)
  • 车票描述
带列的附件表:

  • 附件\u guid(唯一标识符-主键)
  • 附件\u票证\u guid(uniqueidentifier-票证表的外键,带有ON DELETE级联约束
  • 附件说明
考虑使用以下删除谓词函数,该函数应允许用户仅删除附件与特定描述匹配的票证:

    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限制的情况下,安全性工作得非常好,正如预期的那样


问题:如何克服此问题并使删除策略按预期工作?

对于不存在的票证,估计的删除计划或实际的删除计划:

delete from ticket where ticket_guid = newid()
--or
delete from ticket where ticket_guid is null
显示安全功能“驻留”在两个“删除”之间: 最左边是T-SQL delete(delete语句) 右侧是票据的集群删除(&附件的级联删除)

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

高级描述可以是:对于每个票证行,删除票证和相应的附件,输出删除的外键:票证guid(序列运算符,由底部表池提供),对于每个删除的guid(嵌套循环,外部部分),执行安全功能(嵌套循环的内部部分)左半联接,因为安全函数无法返回任何结果,请评估/断言函数的结果是否为null或不为null,如果不为null,则票据行被“删除”并移动到下一票据

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

可以使用一个函数来测试这一点,该函数检查假定的“要删除”(实际上在之前已删除)票证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
由于函数是在行删除后计算的,因此删除的票证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。。。添加块谓词。。。删除提交前删除行后

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

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

让我们考虑只有在有一张票的情况下才能删除票。 附件链接到附件作者在 描述:'请删除我的票据{签名}'


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

这是对票证表上谓词的补充,以确保票证客户端guid与用户guid匹配,从而验证票证的删除访问权?此谓词用于验证在不删除票据的情况下删除附件的权限?这只是一个示例。如果票证链接的附件描述与特定字符串不匹配,则此谓词不允许删除票证。不要介意根据会话上下文检查用户guid,这只是为了简单起见。此外,这个问题与我之前发布的关于根据用户guid匹配票证\u客户端\u guid的问题完全不同。请考虑