我有这个设置
- 我创建了两个数据库(源和目标)。
- 源数据库具有正在访问目标数据库中的表的存储过程。
- 存储过程作为服务帐户执行
- 服务帐户登录在两个数据库中都有用户帐户
- 服务帐户具有目标数据库的连接和身份验证权限
- 服务帐户对目标数据库架构具有读取权限
- 两个数据库都值得信赖
我仍然有一个错误
服务器主体“ServiceAccount”无法在当前安全上下文下访问数据库“TargetDatabase”。
SQL 脚本
-- Create logins
CREATE LOGIN SourceDatabaseOwner WITH PASSWORD = 'Pa$$w0rd'
CREATE LOGIN TargetDatabaseOwner WITH PASSWORD = 'Pa$$w0rd'
CREATE LOGIN ServiceAccount WITH PASSWORD = 'Pa$$w0rd'
-- Create databases
CREATE DATABASE SourceDatabase
CREATE DATABASE TargetDatabase
-- Setup trustworthy
ALTER DATABASE SourceDatabase SET TRUSTWORTHY ON;
ALTER DATABASE TargetDatabase SET TRUSTWORTHY ON;
-- Setup database owners
USE SourceDatabase
GO
EXEC dbo.sp_changedbowner @loginame = N'SourceDatabaseOwner'
USE TargetDatabase
GO
EXEC dbo.sp_changedbowner @loginame = N'TargetDatabaseOwner'
-- Add ServiceAccount to source database
USE SourceDatabase
GO
CREATE USER ServiceAccount FOR LOGIN ServiceAccount;
-- Add ServiceAccount to target database
USE TargetDatabase
GO
CREATE USER ServiceAccount FOR LOGIN ServiceAccount;
-- Enable ServiceAccount to authenticate to target database
USE TargetDatabase
GO
GRANT AUTHENTICATE TO ServiceAccount;
-- Grant permissions
USE TargetDatabase
GO
GRANT SELECT ON SCHEMA::dbo TO ServiceAccount
-- Create table with data
USE TargetDatabase
GO
create table dbo.InterestingData (Id int identity primary key, Content nvarchar(255))
insert into dbo.InterestingData (Content) values ('Foo'), ('Bar')
-- Create stored procedure executing under service account and accessing target database
USE SourceDatabase
GO
create or alter procedure dbo.GetData
with execute as 'ServiceAccount' as
begin
select id, content from TargetDatabase.dbo.InterestingData
end
-- Execution does not work under service account
-- The server principal "ServiceAccount" is not able to access the database "TargetDatabase" under the current security context.
exec dbo.GetData
所以我继续并尝试使用证书签署程序并在目标表上设置权限以登录该证书
-- create certificate in master
use master
go
CREATE CERTIFICATE SignProcedureCert
ENCRYPTION BY PASSWORD = 'Pa$$w0rd'
WITH SUBJECT = 'Certificate for signing stored procedures'
GO
-- backup certificate
BACKUP CERTIFICATE SignProcedureCert TO FILE ='C:\Certs\SignProcedureCert.cer'
WITH PRIVATE KEY
(
FILE = 'C:\Certs\SignProcedureCert.pvk',
DECRYPTION BY PASSWORD = 'Pa$$w0rd',
ENCRYPTION BY PASSWORD = 'Pa$$w0rd'
)
GO
-- create login from the certificate
create login SignProcedureLogin from certificate SignProcedureCert
-- Import certificate to the source database
CREATE CERTIFICATE SignProcedureCert
FROM FILE = 'C:\Certs\SignProcedureCert.cer'
WITH PRIVATE KEY (FILE = 'C:\Certs\SignProcedureCert.pvk',
ENCRYPTION BY PASSWORD = 'Pa$$w0rd',
DECRYPTION BY PASSWORD = 'Pa$$w0rd')
-- Sign the procedure
ADD SIGNATURE TO dbo.GetData
BY CERTIFICATE SignProcedureCert
WITH PASSWORD = 'Pa$$w0rd';
GO
-- Setup permission for SignProcedureLogin to target database
USE TargetDatabase
GO
CREATE USER SignProcedureLogin FOR LOGIN SignProcedureLogin;
GRANT SELECT ON SCHEMA::dbo TO SignProcedureLogin
所以现在我应该有这个设置
但是当我执行
USE SourceDatabase
GO
exec dbo.GetData
我得到了同样的错误
服务器主体“ServiceAccount”无法在当前安全上下文下访问数据库“TargetDatabase”。
我错过了什么?
更新
当我删除该with execute as
条款时,它确实有效
-- Setup trustworthy
ALTER DATABASE SourceDatabase SET TRUSTWORTHY OFF;
ALTER DATABASE TargetDatabase SET TRUSTWORTHY OFF;
USE SourceDatabase
GO
create or alter procedure dbo.GetData
--with execute as 'ServiceAccount'
as
begin
select id, content from TargetDatabase.dbo.InterestingData
end
GRANT EXECUTE ON SCHEMA::dbo TO ServiceAccount
execute as login = 'ServiceAccount'
exec dbo.GetData
revert
但是仍然可以使用该with execute as
子句以某种方式执行该过程吗?
您实际上与初始设置非常接近。你只是错过了 1 个步骤(并且有 2 个无关的步骤)。但是,在我解释如何使原始场景工作之前,我需要提及/警告原始场景并不理想,即使已修复也不应该使用,因为有一种更好更安全的方法:模块签名。我在对您的相关问题的回答中解释了原始设置解决的整体问题的首选解决方案:应该禁用可信赖选项,但有什么替代方案?.
正如您所发现的(基于您在问题中的更新),问题是由于
WITH EXECUTE AS ...
在 proc 定义中使用了子句(即模拟)。这会将进程隔离到当前数据库。是的,启用TRUSTWORTHY
(正如您最初尝试的那样)可以帮助克服这一点,但规则并不总是直截了当的。首先,TRUSTWORTHY
仅影响启动进程的位置,因此没有理由TRUSTWORTHY
在目标数据库中启用(这是两个无关步骤之一)。其次,当启用时TRUSTWORTHY
,安全的焦点变成了当前数据库的所有者(即进程被启动的地方)。因此,问题在于是否SourceDatabaseOwner
有权在 上充当身份验证器[TargetDatabase]
,而不是ServiceAccount
(意思是,缺少的步骤是授予AUTHENTICATE
-SourceDatabaseOwner
这将需要SourceDatabaseOwner
作为用户添加[TargetDatabase]
- 而第二个无关步骤是授予AUTHENTICATE
)ServiceAccount
。这是一个工作演示,基于问题中的示例代码:
最初设定
测试
使固定 ...
允许访问单个数据库
— ??? —
允许访问所有数据库
再次测试
如果出于某种原因,您真的需要/想要使用
WITH EXECUTE AS
(尽管通常没有理由这样做,因为模块签名允许 proc/function/trigger 始终如一地工作,而不管谁执行它),那么您仍然可以完成此操作无需TRUSTWORTHY
通过模块签名启用。与您的原始设置一样,您再次完成了额外的模块签名步骤,但又缺少一个步骤和一个无关的步骤。模块签名将安全重点转移到证书(以及基于证书的登录和/或用户)。因此,缺少的步骤是不授予AUTHENTICATE
基于证书的用户[TargetDatabase]
. 而且,额外的步骤是为证书创建登录名,因为在这种情况下没有授予服务器级别的权限。这是一个工作演示,从上面继续演示(即不是独立的):
从上面的演示中撤消使事情正常的额外步骤(以表明模块签名也修复了这种情况)
再次测试(验证 proc 不再有效)
设置模块签名
再次测试(验证 proc 仍然不起作用)
在相关问题(链接到顶部)中有效的模块签名设置在此处不起作用。
问题是
WITH EXECUTE AS
proc 定义中的子句。使固定
再次测试
有关模块签名的更多信息,请访问我的网站:
https ://ModuleSigning.Info/