クロスデータベース証明書( Erland Sommarskogが説明 )を使用して、環境内の特定のデータベース(SQL Server 2008 R2)へのアクセスを制御します。
私はデータベースBのテーブルを更新するデータベースAのストアドプロシージャを持っています。これは、これまで、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
ここでの問題は、証明書が2つのテーブルに対するINSERT
権限を持つDatabaseAのユーザーとDatabaseAのストアドプロシージャをリンクする一方で、ストアドプロシージャから直接挿入されるテーブルのトリガーが別のモジュールであることです。チェーン、および証明書から取得したアクセス許可は、チェーン内の他のモジュールに渡されません。つまり、証明書により、ストアドプロシージャはユーザーを介してテーブルに挿入し、トリガーを実行することさえできました。ただし、オブジェクトに関連するすべてを実行するためのアクセス許可はトリガーに与えられていません(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:トリガーは成功
onlyの変更は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