从存储过程调用 sp_start_job

从存储过程调用 sp_start_job

我们的开发人员需要能够从他们的 .Net 代码启动 SQL Server 代理作业。我知道我可以调用msdb..sp_start_job就是这样做的,但我不想让一般用户帐户直接访问运行作业。

我想要做的是使用 WITH EXECUTE AS 子句在应用程序的数据库中创建一个存储过程来模拟代理帐户。我们的程序如下:

CREATE PROCEDURE dbo.StartAgentJob 
    WITH EXECUTE AS 'agentProxy'
AS
BEGIN
    EXEC msdb.dbo.sp_start_job N'RunThisJob';
END

但是,当我们运行这个程序时,我们收到以下消息:

The EXECUTE permission was denied on the object 'sp_start_job', database 'msdb', schema 'dbo'.

有什么想法吗?这甚至是在 SQL2005 中执行此操作的最佳方法吗?

答案1

您是否已将 agentProxy 登录名放入 msdb 数据库并授予其运行 sp_start_job 的权限?如果没有,您需要为 msdb 数据库和用户数据库启用数据库权限链。

最好将登录名放入 msdb 数据库并授予其正确的权限。

答案2

我很高兴你解决了这个问题,但所有权链不是推荐的解决方案。由于你似乎确实关心安全性和所涉及的权利的适当粒度,我添加了这个回复,虽然有点晚,但作为正在发生的事情以及如何解决这个问题的参考。

EXECUTE AS 模拟范围

EXECUTE AS 子句有两种形式:EXECUTE AS LOGIN 和 EXECUTE AS USER。EXECUTE AS LOGIN 由服务器进行身份验证,并且是整个 SQL 实例(服务器范围)信任的模拟上下文:

使用 EXECUTE AS LOGIN 语句模拟主体时,或者在服务器范围的模块中使用 EXECUTE AS 子句模拟主体时,模拟的范围是服务器范围。这意味着在上下文切换之后,可以访问服务器中被模拟的登录名具有权限的任何资源。

EXECUTE AS USER 由数据库进行身份验证,并且是仅受该数据库信任的模拟上下文(数据库范围):

但是,当使用 EXECUTE AS USER 语句模拟主体时,或者在数据库范围的模块中使用 EXECUTE AS 子句模拟主体时,模拟范围默认限制在数据库中。这意味着对数据库范围之外的对象引用将返回错误。

具有 EXECUTE AS 子句的存储过程将创建数据库范围的模拟上下文,因此将无法引用数据库外部的对象,例如您将无法引用msdb.dbo.sp_start_job因为在msdb。 还有许多其他示例可用,例如尝试访问服务器范围 DMV、尝试使用链接服务器或尝试将 Service Broker 消息传递到另一个数据库。

启用数据库范围模拟来访问通常不允许的资源验证器模拟上下文必须可信。对于数据库范围的模拟,验证者是数据库 dbo。这可以通过两种可能的方式实现:

  • 通过打开验证模拟上下文的数据库(即发出 EXECUTE AS 子句的数据库)上的 TRUSTWORTHY 属性。
  • 通过使用代码签名。

MSDN 中描述了这些细节:使用 EXECUTE AS 扩展数据库模拟

当您通过跨数据库所有权链接解决问题时,您已在整个服务器级别启用了跨数据库链接,这被视为安全风险。实现所需结果的最受控、最细粒度的方法是使用代码签名:

  • 在应用程序数据库中创建自签名证书
  • dbo.StartAgentJob用此证书签署
  • 删除证书的私钥
  • 将证书导出到磁盘
  • 将证书导入msdb
  • 从导入的证书中创建派生用户msdb
  • 向派生用户授予 AUTHENTICATE 权限msdb

这些步骤确保过程的 EXECUTE AS 上下文dbo.StartAgentJob现在在 中是受信任的msdb,因为该上下文由在 中具有 AUTHENTICATE 权限的主体签名msdb。这解决了一半难题。另一半是实际将 EXECUTE 权限授予msdb.dbo.sp_start_job现在受信任的模拟上下文。有几种方法可以做到这一点:

  1. 将模拟用户agentProxyuser 映射到msdb并授予他执行权限msdb.dbo.sp_start_job
  2. msdb授予验证器证书派生用户执行权限
  3. 为该过程添加新的签名,为其派生一个用户msdb并授予该派生用户的执行权限

选项1.很简单,但是有一个很大的缺点:用户现在可以按照自己的意愿agentProxy执行,他真正被授予访问权限并拥有执行权限。msdb.dbo.sp_start_jobmsdb

选项 3 肯定是正确的,但是我觉得没有必要太过分。

msdb.dbo.sp_start_job因此我首选的是选项 2:向在中创建的证书派生用户授予 EXECUTE 权限msdb

以下是相应的 SQL:

use [<appdb>];
go

create certificate agentProxy 
    ENCRYPTION BY PASSWORD = 'pGFD4bb925DGvbd2439587y'
    with subject = 'agentProxy'
   , start_date='01/01/2009';
go

ADD SIGNATURE TO OBJECT::[StartAgentJob]
      BY CERTIFICATE [agentProxy]
        WITH PASSWORD = 'pGFD4bb925DGvbd2439587y';
go

alter certificate [agentProxy] 
  remove private key;
go

backup certificate [agentProxy] 
 to file='c:\temp\agentProxy.cer';
go

use msdb
go

create certificate [agentProxy] 
  from file='c:\temp\agentProxy.cer';
go

create user [agentProxyAuthenticator] 
 from certificate [agentProxy];
go

grant authenticate to [agentProxyAuthenticator];
grant execute on msdb.dbo.sp_start_job to [agentProxyAuthenticator];
go

use [<appdb>];
go

exec dbo.StartAgentJob;
go

我的博客有一些涉及该主题的文章,是在 Service Broker 激活过程的背景下编写的(因为它们需要 EXECUTE AS 子句):

顺便说一句,如果您想测试我的脚本并且您住在东半球,或者英国夏令时,请务必在测试之前阅读我链接的最后一篇文章。

答案3

由于您尝试从 .NET 代码启动 SQL Server Agent,这对于 StackOverflow 来说可能是更好的问题?

http://www.stackoverflow.com

答案4

无需授予额外权限即可实现此目的的一种方法是:不要让存储过程直接启动作业,而只允许存储过程在表(在应用程序数据库中)中翻转一个位;然后,让作业每分钟左右运行一次,检查位是否翻转,如果是,则执行工作并再次翻转位。如果作业发现位未翻转,则作业将退出。

如果您不介意延迟(以及作业频繁运行),那么它会非常有效。

相关内容