使用跨數據庫證書時觸發器中的權限
我使用跨數據庫證書(如 Erland Sommarskog 所解釋的)來控制對我的環境(SQL Server 2008 R2)中某個數據庫的訪問。
我在數據庫 A 中儲存了更新數據庫 B 中表的儲存過程。到目前為止,這一直適用於 db A 中的各種儲存過程和 db B 中的表。我正在嘗試更新 db B 中的表,但該表上有一個觸發器。此觸發器正在 db B 的另一個表中插入其他數據。我收到錯誤消息:
消息 916,級別 14,狀態 1,過程 table_trigger,第 11 行 伺服器主體“sql\login”無法在目前安全上下文下訪問數據庫“B”。
我嘗試為與證書綁定的數據庫 B 使用者授予插入權限以插入到另一個表中,但它沒有解決錯誤。除了更改觸發器以使其使用之外,我還有其他選擇
WITH EXECUTE AS OWNER
嗎?這是複制問題的 DDL:
CREATE LOGIN [GuggTest] WITH PASSWORD=N'abcd', DEFAULT_DATABASE=[master], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF CREATE DATABASE A; CREATE DATABASE B; USE A; CREATE TABLE dbo.SPtoUpdate ( ID INT , ILoveFishing VARCHAR(255) ); INSERT INTO dbo.SPtoUpdate ( ID , ILoveFishing ) VALUES ( 1,'Musky'),( 2,'Pike'),( 3,'Yellow Perch'); CREATE TABLE dbo.TriggerToInsert ( ID INT , ILoveFishing VARCHAR(255) , ChangeDate DATETIME2 ); GO CREATE TRIGGER dbo.SPtoUpdateTrigger ON dbo.SPtoUpdate FOR UPDATE AS DECLARE @datetime DATETIME2; SELECT @datetime = GETDATE() INSERT INTO dbo.TriggerToInsert ( ID , ILoveFishing , ChangeDate ) VALUES ( 1 , 'Yes' , @datetime ); GO CREATE CERTIFICATE BExecutor ENCRYPTION BY PASSWORD = 'Obfuscated' WITH SUBJECT = 'Execute sp from B to A', START_DATE = '20140101', EXPIRY_DATE = '20300101' GO BACKUP CERTIFICATE BExecutor TO FILE = 'C:\temp\crossdbcert.cer' WITH PRIVATE KEY (FILE = 'C:\temp\crossdbcert.pvk' , ENCRYPTION BY PASSWORD = 'Obfuscated', DECRYPTION BY PASSWORD = 'Obfuscated') GO CREATE USER BExecutor FROM CERTIFICATE BExecutor GRANT UPDATE ON dbo.SPtoUpdate TO BExecutor GRANT SELECT ON dbo.SPtoUpdate TO BExecutor --Also give insert on dbo.TriggerToInsert GRANT INSERT ON dbo.TriggerToInsert TO BExecutor USE B GO CREATE USER [GuggTest] FOR LOGIN [GuggTest]; EXEC sp_addrolemember N'db_owner', N'GuggTest' GO CREATE PROCEDURE dbo.UpdateTableInA AS BEGIN UPDATE A.dbo.SPtoUpdate SET ILoveFishing = 'Walleye' WHERE ID = 2; END GO CREATE CERTIFICATE BExecutor FROM FILE = 'C:\temp\crossdbcert.cer' WITH PRIVATE KEY (FILE = 'C:\temp\crossdbcert.pvk' , ENCRYPTION BY PASSWORD = 'Obfuscated', DECRYPTION BY PASSWORD = 'Obfuscated') GO EXEC master..xp_cmdshell 'DEL C:\temp\crossdbcert.*', 'no_output' GO ADD SIGNATURE TO dbo.UpdateTableInA BY CERTIFICATE BExecutor WITH PASSWORD = 'Obfuscated' GO --Log In or Change execution context to GuggTest, then EXEC dbo.UpdateTableInA
這裡的問題是,雖然證書將 DatabaseA 中的儲存過程與 DatabaseB 中
INSERT
對這兩個表具有權限的使用者連結起來,但從儲存過程直接插入的表上的觸發器是鏈中的另一個模組,並且獲得了權限from 證書不會傳遞給鏈中的其他模組。意思是,證書允許儲存過程通過使用者插入到表中,甚至執行觸發器。但是,沒有授予觸發器執行與對象相關的任何操作的權限(執行類似的操作SELECT 1;
)。在這種情況下,需要通過相同的證書向觸發器授予權限,以便它可以採取任何必要的操作。這至少可以通過對觸發器進行會籤來完成。你可以通過執行
ADD COUNTER SIGNATURE TO [TriggerSchema].[TriggerName] BY CERTIFICATE ...;
. 之後,它應該可以正常工作,即使INSERT
觸發器插入的表上的基於證書的使用者沒有直接許可。下面的範常式式碼重現了該問題,通過添加計數器簽名解決了該問題,但不授予
INSERT
觸發器填充表的權限。清理
USE [master]; GO IF EXISTS (SELECT 1 FROM [sys].[databases] WHERE [name] = N'DatabaseA') BEGIN PRINT 'Dropping [DatabaseA] DB...'; ALTER DATABASE [DatabaseA] SET OFFLINE WITH ROLLBACK IMMEDIATE; ALTER DATABASE [DatabaseA] SET ONLINE; DROP DATABASE [DatabaseA]; END; IF EXISTS (SELECT 1 FROM [sys].[databases] WHERE [name] = N'DatabaseB') BEGIN PRINT 'Dropping [DatabaseB] DB...'; ALTER DATABASE [DatabaseB] SET OFFLINE WITH ROLLBACK IMMEDIATE; ALTER DATABASE [DatabaseB] SET ONLINE; DROP DATABASE [DatabaseB]; END; IF (SUSER_ID(N'JohnnyLunchbucket') IS NOT NULL) BEGIN PRINT 'Dropping [JohnnyLunchbucket] Login...'; DROP LOGIN [JohnnyLunchbucket]; END; IF (OBJECT_ID(N'tempdb..#CertInfo') IS NOT NULL) BEGIN PRINT 'Dropping [#CertInfo] Temp Table...'; DROP TABLE #CertInfo; END;
設置
USE [master]; EXECUTE AS LOGIN = N'sa'; PRINT 'Creating databases...'; CREATE DATABASE [DatabaseA] COLLATE Latin1_General_100_CI_AS_SC; CREATE DATABASE [DatabaseB] COLLATE Latin1_General_100_CI_AS_SC; REVERT; GO -- Default for both options should be OFF, but just to be sure: ALTER DATABASE [DatabaseA] SET DB_CHAINING OFF; ALTER DATABASE [DatabaseA] SET TRUSTWORTHY OFF; ALTER DATABASE [DatabaseB] SET DB_CHAINING OFF; ALTER DATABASE [DatabaseB] SET TRUSTWORTHY OFF; GO CREATE LOGIN [JohnnyLunchbucket] WITH PASSWORD = 'OhSoSecure;)'; USE [DatabaseA]; CREATE USER [JohnnyLunchbucket] FOR LOGIN [JohnnyLunchbucket]; GO --DROP PROCEDURE dbo.InsertIntoTableWithoutTrigger; CREATE PROCEDURE dbo.InsertIntoTableWithoutTrigger ( @SomeValue NVARCHAR(50) ) AS SET NOCOUNT ON; INSERT INTO [DatabaseB].[dbo].[TableWithoutTrigger] (SomeValue) VALUES (@SomeValue); GO GRANT EXECUTE ON dbo.InsertIntoTableWithoutTrigger TO [JohnnyLunchbucket]; GO CREATE PROCEDURE dbo.InsertIntoTableWithTrigger AS SET NOCOUNT ON; INSERT INTO [DatabaseB].[dbo].[TableWithTrigger] (SomeOtherValue) VALUES (NEWID()); GO GRANT EXECUTE ON dbo.InsertIntoTableWithTrigger TO [JohnnyLunchbucket]; CREATE CERTIFICATE [PermissionsCert] AUTHORIZATION [dbo] ENCRYPTION BY PASSWORD = 'WeakPassword' WITH SUBJECT = 'Used to test granting permissions to code', EXPIRY_DATE = '2099-12-31'; ADD SIGNATURE TO [dbo].[InsertIntoTableWithoutTrigger] BY CERTIFICATE [PermissionsCert] WITH PASSWORD = 'WeakPassword'; ADD SIGNATURE TO [dbo].[InsertIntoTableWithTrigger] BY CERTIFICATE [PermissionsCert] WITH PASSWORD = 'WeakPassword'; -- Save Certificate info in temporary table so we can recreate in DatabaseB SELECT CERTENCODED(CERT_ID(N'PermissionsCert')) AS [PublicKey], CERTPRIVATEKEY(CERT_ID(N'PermissionsCert'), 'OtherPassword', 'WeakPassword') AS [PrivateKey] INTO #CertInfo; GO USE [DatabaseB]; DECLARE @SQL NVARCHAR(MAX); SELECT @SQL = N'CREATE CERTIFICATE [PermissionsCert] AUTHORIZATION [dbo] FROM BINARY = ' + CONVERT(NVARCHAR(MAX), [PublicKey], 1) + N' WITH PRIVATE KEY (BINARY = ' + CONVERT(NVARCHAR(MAX), [PrivateKey], 1) + N', DECRYPTION BY PASSWORD = N''OtherPassword''' + N', ENCRYPTION BY PASSWORD = ''WeakPassword'');' FROM #CertInfo; PRINT @SQL; EXEC (@SQL); CREATE USER [PermissionsUser] FROM CERTIFICATE [PermissionsCert]; --DROP TABLE dbo.[TableWithoutTrigger]; CREATE TABLE dbo.[TableWithoutTrigger] ( [TableWithoutTriggerID] INT NOT NULL IDENTITY(1, 1) CONSTRAINT [PK_TableWithoutTrigger] PRIMARY KEY, [SomeValue] NVARCHAR(50) ); GRANT INSERT ON [dbo].[TableWithoutTrigger] TO [PermissionsUser]; CREATE TABLE dbo.[TableWithTrigger] ( [TableWithTriggerID] INT NOT NULL IDENTITY(1, 1) CONSTRAINT [PK_TableWithTrigger] PRIMARY KEY, [SomeOtherValue] NVARCHAR(50) ); GRANT INSERT ON [dbo].[TableWithTrigger] TO [PermissionsUser]; CREATE TABLE dbo.[TablePopulatedByTrigger] ( [TablePopulatedByTriggerID] INT NOT NULL IDENTITY(1, 1) CONSTRAINT [PK_TablePopulatedByTrigger] PRIMARY KEY, [DuplicatedValue] NVARCHAR(50) ); GO CREATE TRIGGER dbo.CopySomeOtherValue ON dbo.[TableWithTrigger] AFTER INSERT AS BEGIN SET NOCOUNT ON; INSERT INTO dbo.[TablePopulatedByTrigger] ([DuplicatedValue]) SELECT ins.[SomeOtherValue] FROM inserted ins; END; GO
測試 1:觸發器失敗
USE [DatabaseA]; EXECUTE AS LOGIN = 'JohnnyLunchbucket'; SELECT SESSION_USER AS [User], ORIGINAL_LOGIN() AS [OriginalLogin]; GO SELECT * FROM [DatabaseB].[dbo].[TableWithoutTrigger]; SELECT * FROM [DatabaseB].[dbo].[TableWithTrigger]; SELECT * FROM [DatabaseB].[dbo].[TablePopulatedByTrigger]; INSERT INTO [DatabaseB].[dbo].[TableWithoutTrigger] ([SomeValue]) VALUES (N'test 0'); USE [DatabaseB]; /* -- All 5 statements above get the following error: Msg 916, Level 14, State 1, Line xxxxxx The server principal "JohnnyLunchbucket" is not able to access the database "DatabaseB" under the current security context. */ EXEC [dbo].[InsertIntoTableWithoutTrigger] @SomeValue = N'test A'; -- SUCCESS!!! EXEC [dbo].[InsertIntoTableWithTrigger]; -- ERROR: /* Msg 916, Level 14, State 1, Procedure CopySomeOtherValue, Line xxxxxx The server principal "JohnnyLunchbucket" is not able to access the database "DatabaseB" under the current security context. */ REVERT; SELECT SESSION_USER AS [User], ORIGINAL_LOGIN() AS [OriginalLogin]; -- Check to make sure that dbo.InsertIntoTableWithoutTrigger really did work: SELECT * FROM [DatabaseB].[dbo].[TableWithoutTrigger]; -- 1 test A
測試 2:觸發成功
請注意,所做的唯一更改是
ADD COUNTER SIGNATURE
;沒有GRANT INSERT ON dbo.TablePopulatedByTrigger TO [PermissionsUser];
。USE [DatabaseB]; ADD COUNTER SIGNATURE TO dbo.[CopySomeOtherValue] BY CERTIFICATE [PermissionsCert] WITH PASSWORD = 'WeakPassword'; GO USE [DatabaseA]; EXECUTE AS LOGIN = 'JohnnyLunchbucket'; SELECT SESSION_USER AS [User], ORIGINAL_LOGIN() AS [OriginalLogin]; GO INSERT INTO [DatabaseB].[dbo].[TableWithTrigger] ([SomeOtherValue]) VALUES (N'Test B'); INSERT INTO [DatabaseB].[dbo].[TablePopulatedByTrigger]([DuplicatedValue]) VALUES ('Test B') /* Msg 916, Level 14, State 1, Line xxxxxx The server principal "JohnnyLunchbucket" is not able to access the database "DatabaseB" under the current security context. */ EXEC [dbo].[InsertIntoTableWithTrigger]; -- SUCCESS!!! REVERT; SELECT SESSION_USER AS [User], ORIGINAL_LOGIN() AS [OriginalLogin]; SELECT * FROM [DatabaseB].[dbo].[TableWithTrigger]; SELECT * FROM [DatabaseB].[dbo].[TablePopulatedByTrigger]; -- 2 968DB092-C3DE-4E4B-92B9-E21CA551A5FA -- 1 968DB092-C3DE-4E4B-92B9-E21CA551A5FA