Msdb.dbo.sp_send_dbmailへのデータベース間の呼び出しを行うプロシージャTheNotificationProcedure
があります。
Service Brokerキューから(間接的に)呼び出されます。
CREATE QUEUE [Blah].[TheQueue]
WITH ACTIVATION (STATUS = ON, PROCEDURE_NAME = [Blah].[TheQueueProcedure],
MAX_QUEUE_READERS = 1, EXECUTE AS N'TheUser');
TheQueueProcedure
は最終的にTheNotificationProcedure
を呼び出します
SSMSでTheUser
として接続し、TheNotificationProcedure
を実行すると、すべてが機能し、メールが送信されます。
ただし、キューに到着したメッセージの結果としてTheNotificationProcedure
が呼び出されると、msdbプロシージャにアクセスできないため失敗します。
私は、sp_send_dbmailをラップする独自のプロシージャをmsdbで作成し、dbmailラッパーとTheNotificationProcedure
の両方に同じ認証書で署名するなど、考えられるすべてのことを試しました。msdbの証明書ユーザーが " DatabaseMailUserRole "です。
最後に、さらに詳細なトレースを行った後、最終的に次のことに気付きました。
これは、Service BrokerがTheUser
のloginで実行されている場合でも、何らかの理由で、データベースuserで実行されているof guest
、これは私のパーミッションの問題を少なくとも部分的に説明していると思います。
loginTheUser
は、msdbでTheUser
と呼ばれるuserにもマップされます。確かにゲストにマッピングされていません。
では、Service Brokerを通過するときにmsdbでゲストとして実行されるのはなぜですか?
データベースにTrustworthy
のマークを付けないようにする必要があります。手順に署名することでそれを望んでいました(例: http://www.sommarskog.se/grantperm.html )データベース全体を転送するためのアクセス許可を取得できます-execute as
通常は証明書ユーザーを介して関連付けられる権限を無効にしますか?
サービスブローカーを通過するときに、上記のアクセス許可の問題なしでモジュール署名(同じ「ゲスト」トレースを与える)を行わないスクリプトを次に示します。
セットアップ:
--REPLACE EMAIL, and db_mail profile
--@profile_name = 'Test db mail profile',
--@recipients = '[email protected]',
use master;
GO
IF EXISTS(select * FROM sys.databases where name='http://dba.stackexchange.com/questions/166033')
BEGIN
ALTER DATABASE [http://dba.stackexchange.com/questions/166033] SET OFFLINE WITH ROLLBACK IMMEDIATE;
ALTER DATABASE [http://dba.stackexchange.com/questions/166033] SET ONLINE WITH ROLLBACK IMMEDIATE;
DROP DATABASE [http://dba.stackexchange.com/questions/166033];
END
CREATE DATABASE [http://dba.stackexchange.com/questions/166033];
GO
IF EXISTS(select * FROM sys.server_principals WHERE name = 'TheUser' AND type_desc='SQL_LOGIN') DROP LOGIN TheUser;
CREATE LOGIN [TheUser] WITH PASSWORD=N'jL839lIFKttcm3cNuk1WUazfk5lS76RKMscZ01UdFkI='
, DEFAULT_DATABASE=[http://dba.stackexchange.com/questions/166033]
, DEFAULT_LANGUAGE=[us_english], CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF;
use [msdb];
GO
IF (NOT EXISTS(select * FROM sys.database_principals WHERE name = 'TheUser')) CREATE USER [TheUser] FOR LOGIN [TheUser] WITH DEFAULT_SCHEMA=[dbo];
exec sp_addrolemember 'DatabaseMailUserRole', 'TheUser';
GO
use [http://dba.stackexchange.com/questions/166033];
GO
CREATE USER [TheUser] FOR LOGIN [TheUser] WITH DEFAULT_SCHEMA=[dbo]
GO
CREATE SCHEMA [Blah] AUTHORIZATION dbo;
GO
CREATE QUEUE [Blah].[SourceQueue];
GO
CREATE SERVICE [//FromService]
AUTHORIZATION [dbo]
ON QUEUE [Blah].[SourceQueue];
GO
CREATE MESSAGE TYPE [//TestMessage]
AUTHORIZATION [dbo]
VALIDATION = NONE;
GO
CREATE CONTRACT [//ServiceContract]
AUTHORIZATION [dbo]
([//TestMessage] SENT BY INITIATOR);
GO
CREATE PROCEDURE [Blah].[SendMessage]
AS
DECLARE @message varchar(50),
@conversationHandle UNIQUEIDENTIFIER
SET @message = 'Test Message Content';
-- Begin the dialog.
BEGIN DIALOG CONVERSATION @conversationHandle
FROM SERVICE [//FromService]
TO SERVICE '//ToService'
ON CONTRACT [//ServiceContract]
WITH ENCRYPTION = OFF;
-- Send the message on the dialog.
SEND ON CONVERSATION @conversationHandle
MESSAGE TYPE [//TestMessage]
(@message) ;
END CONVERSATION @conversationHandle ;
GO
CREATE PROCEDURE [dbo].[TheNotificationProcedure]
AS
PRINT 'DEBUG - Entering [dbo].[TheNotificationProcedure]'
-- Send notification
PRINT 'DEBUG - [dbo].[TheNotificationProcedure] - PRIOR TO msdb.dbo.sp_send_dbmail'
declare @log nvarchar(max) = '';
select @log = @log + 'name: ' + name + ' ' + 'type: ' + type + ' usage: ' + usage + ' || ' FROM sys.login_token
print @log
declare @mailitem_id int;
--exec [msdb].[dbo].[WRAP__sp_send_dbmail]
exec [msdb].[dbo].[sp_send_dbmail]
@profile_name = 'Test db mail profile',
@recipients = '[email protected]', --@Recipient,
@subject = 'Testing sp_send_dbmail', --@NotificationSubject,
@body = 'Testing sp_sdend_dbmail from service broker', --@NotificationBody,
@exclude_query_output = 1,
@mailitem_id = @mailitem_id OUTPUT
PRINT 'DEBUG - [dbo].[TheNotificationProcedure] - AFTER msdb.dbo.sp_send_dbmail'
GO
CREATE PROCEDURE [Blah].[TestMessageHandler]
AS
--has other logic that eventully calls notification
EXECUTE [dbo].[TheNotificationProcedure]
GO
CREATE PROCEDURE [Blah].[TheQueueProcedure]
AS
--Service Broker variables
DECLARE @conversation_handle UNIQUEIDENTIFIER,
@conversation_group_id UNIQUEIDENTIFIER,
@message_body varchar(255),
@message_type_name NVARCHAR(256),
@dialog UNIQUEIDENTIFIER,
@RowsReceived int
PRINT 'Start'
WHILE (1 = 1)
BEGIN
-- Get next conversation group.
WAITFOR(
GET CONVERSATION GROUP @conversation_group_id FROM [Blah].[TheQueue]),
TIMEOUT 500 ;
-- If there are no more conversation groups, roll back the
-- transaction and break out of the outermost WHILE loop.
IF @conversation_group_id IS NULL
BEGIN
BREAK ;
END ;
WHILE (1 = 1)
BEGIN
BEGIN TRANSACTION
PRINT 'Get Message'
; RECEIVE TOP (1)
@dialog = conversation_handle,
@message_type_name=message_type_name,
@message_body=message_body
FROM [Blah].[TheQueue]
WHERE conversation_group_id = @conversation_group_id ;
SET @RowsReceived = @@ROWCOUNT
PRINT 'Queue Read: ' + ISNULL(@message_body, '<NULL>')
PRINT '@RowsReceived: ' + CAST(@RowsReceived as varchar(200))
IF (@RowsReceived = 0)
BEGIN
BREAK ;
END ;
PRINT 'Deal with Message'
IF (@message_type_name = 'http://schemas.Microsoft.com/SQL/ServiceBroker/EndDialog')
BEGIN
PRINT 'End Dialog received for dialog # ' + cast(@dialog as nvarchar(40)) ;
END CONVERSATION @dialog ;
END ;
IF (@message_type_name = '//TestMessage')
BEGIN
print 'Have //TestMessage: ' + @message_body
exec [Blah].[TestMessageHandler];
END
COMMIT TRANSACTION;
END
END
RETURN
GO
CREATE QUEUE [Blah].[TheQueue]
WITH ACTIVATION (STATUS = ON, PROCEDURE_NAME = [Blah].[TheQueueProcedure], MAX_QUEUE_READERS = 1, EXECUTE AS N'TheUser');
GO
CREATE SERVICE [//ToService]
AUTHORIZATION [dbo]
ON QUEUE [Blah].[TheQueue]
([//ServiceContract]);
GO
GRANT EXECUTE ON [Blah].[TheQueueProcedure] TO [TheUser];
GO
次に、すべてを開始します。
--kick everything off
EXEC [Blah].[SendMessage];
GO
--read results from error log
--(might need to execute once or twice to get results - because service broker is asynchronous)
declare @sqlErrorLog table (LogDate datetime, ProcessInfo nvarchar(max), Text nvarchar(max));
INSERT INTO @sqlErrorLog EXEC xp_ReadErrorLog
SELECT * FROM @sqlErrorLog
WHERE LogDate >= DATEADD(SECOND, -15, GETDATE()) AND Text NOT LIKE 'CHECKDB%' AND Text NOT LIKE 'Starting up database ''upgrade%' AND Text NOT LIKE '%upgrade%information%' AND TEXT <> 'Error: 9001, Severity: 21, State: 1.'
ORDER BY LogDate
データベースに
Trustworthy
のマークを付けないようにする必要があります。
それは確かにTRUSTWORTHY
に対する正しい姿勢であり、そうです、それは可能です。
では、Service Brokerを通過するときに、msdbでゲストとして実行されるのはなぜですか?
私は当初、この問題が偽装を使用する際のデータベース間の問題の典型的な原因であると考えていました。デフォルトでは、データベースレベルのプリンシパル(ステートメントではなくEXECUTE AS
句でできること)を偽装します。ローカルデータベースに隔離されました。
ただし、追加のテストとO.P.との話し合いにより、このシナリオはわずかに異なることが判明しました。 Service Brokerを使用しているようです モジュール署名がすべてのセキュリティ問題を解決しない非常にまれな状況の1つです。 それが機能しなかったので、モジュール署名の典型的な実装をセットアップするときに私が持っていたと思ったのはそれです。したがって、私はいくつかのことを試みましたが、SQLCLRだけがこれを達成できることがわかりました。
次に、最近関連する質問を見つけました saにはService Brokerの同義語を通じて他のデータベースへのアクセス許可がありません 、それは @ Remus Rusanuによる投稿 を参照しましたこれは確かに可能です。 Remsusのサンプルコードが機能することを確認して、細かい部分をいくつか見落としているに違いないと結論付けました。また、詳細を確認したところ、直感に反するオプションが使用されていることがわかりました。
プロシージャを変更して、EXECUTE AS句を設定します(それ以外の場合、コード署名インフラストラクチャは機能しません)
通常、モジュール署名ではremoveEXECUTE AS
句とステートメントを使用できますが、ここでは必須です。また、Service BrokerがEXECUTE AS USER =
ステートメントを介してデータベースのみのセキュリティコンテキスト内で機能するために必要です。 EXECUTE AS
句をCREATE PROCEDURE
ステートメントに追加することで、新しいセキュリティコンテキストが作成され、サーバーレベルや他のデータベースにアクセスできます。これは、モジュール署名設定の残りの部分が処理するものです。 。
Sooooo、ええと、それを正しく理解して申し訳ありませんが、それを変更して実行可能なものに変更しますが、その1つのピースが欠落しているため、理想的ではありません;-)しかし、私は今それが最初はうまくいくはずだと私が言った方法でそれを動かしています:-)。以下のサンプルコードの最初のセットは、TRUSTWORTHY OFF
で機能する純粋なT-SQL、モジュール署名アプローチです(欠落しているWITH EXECUTE AS N'dbo'
を追加したため)。 SQLCLRアプローチは機能し、他のいくつかのシナリオにより適している可能性があるので、SQLCLRアプローチを下部に置いておきます。
手順に署名することでそれを望んでいました...データベース全体に転送する権限を取得できました
あなたはできる。モジュール署名の設定を見たことがないと思いますが、私が最初に見落としていた小さな、非定型のオプションを1つ見逃していた可能性があります(それが、この全体を機能させるための鍵です)。
execute as
は通常、証明書ユーザーを介して関連付けられる権限を無効にしますか?
EXECUTE AS USER
ステートメント(EXECUTE AS
ステートメントのCREATE object
句、またはEXECUTE AS LOGIN
ステートメントではない)の場合のみ。その1つのケースでは、セキュリティコンテキストはデータベースのみであり、データベースのみである可能性があり、サーバー署名または他のデータベースを表示できません。そして、幸運なことに、それ(つまり、EXECUTE AS USER
ステートメント)は、Service Brokerがアクティブ化手順を実行するために実行していることとまったく同じです。だから、はい、それがモジュール署名の最初の試みが機能しなかった理由です。さらに、それを修正するコツは、別のデータベースにアクセスするプロシージャのWITH EXECUTE AS N'dbo'
ステートメントにCREATE PROCEDURE
句を追加することです。どのユーザーを使用するかは問題ではありませんが、dbo
を使用すると、OWNER
を使用すると、所有者が変更された場合にストアドプロシージャに再署名する必要があるという警告が表示されます。もちろん、データベースの所有者を変更することも可能です。そのため、自分の選択についても警告が表示されることを期待していましたが、そうではなかったため、現時点ではその潜在的なニュアンスを無視することにしました;-)。
メインセットアップ
USE [master];
GO
IF (DB_ID(N'SendDbMailFromServiceBrokerQueue') IS NOT NULL)
BEGIN
RAISERROR(N'Dropping DB: [SendDbMailFromServiceBrokerQueue]...', 10, 1) WITH NOWAIT;
ALTER DATABASE [SendDbMailFromServiceBrokerQueue] SET OFFLINE WITH ROLLBACK IMMEDIATE;
ALTER DATABASE [SendDbMailFromServiceBrokerQueue] SET ONLINE WITH ROLLBACK IMMEDIATE;
DROP DATABASE [SendDbMailFromServiceBrokerQueue];
END
RAISERROR(N'Creating DB: [SendDbMailFromServiceBrokerQueue]...', 10, 1) WITH NOWAIT;
CREATE DATABASE [SendDbMailFromServiceBrokerQueue]
COLLATE Latin1_General_100_CI_AS_KS_SC
WITH DB_CHAINING OFF,
TRUSTWORTHY OFF;
ALTER DATABASE [SendDbMailFromServiceBrokerQueue]
SET RECOVERY SIMPLE,
PAGE_VERIFY CHECKSUM,
ENABLE_BROKER;
GO
-------------------------------------------------
USE [SendDbMailFromServiceBrokerQueue];
GO
CREATE SCHEMA [FunStuff] AUTHORIZATION [dbo];
GO
CREATE USER [BrokerUser] WITHOUT LOGIN WITH DEFAULT_SCHEMA=[dbo];
CREATE QUEUE [FunStuff].[SendingQueue];
CREATE SERVICE [//SendingService]
AUTHORIZATION [dbo]
ON QUEUE [FunStuff].[SendingQueue];
CREATE MESSAGE TYPE [//AuditMessage]
AUTHORIZATION [dbo]
VALIDATION = NONE;
CREATE CONTRACT [//AuditContract]
AUTHORIZATION [dbo]
([//AuditMessage] SENT BY INITIATOR);
GO
CREATE PROCEDURE [FunStuff].[SendMessage]
(
@Content NVARCHAR(MAX)
)
AS
SET NOCOUNT ON;
DECLARE @ConversationHandle UNIQUEIDENTIFIER;
BEGIN DIALOG CONVERSATION @ConversationHandle
FROM SERVICE [//SendingService]
TO SERVICE '//ReceivingService'
ON CONTRACT [//AuditContract]
WITH ENCRYPTION = OFF;
SEND ON CONVERSATION @ConversationHandle
MESSAGE TYPE [//AuditMessage]
(@Content) ;
END CONVERSATION @ConversationHandle ;
GO
---------------------------------------------------------------------------
GO
CREATE PROCEDURE [dbo].[EmailHandler]
(
@EmailSubject VARCHAR(255),
@EmailContent NVARCHAR(MAX)
)
WITH EXECUTE AS N'dbo' -- THIS IS REQUIRED (when used with Service Broker)!!!
AS
DECLARE @Recipients NVARCHAR(4000) = N'[email protected]';
EXEC [msdb].[dbo].[sp_send_dbmail]
@profile_name = N'{my_pofile_name}',
@recipients = @Recipients,
@subject = @EmailSubject,
@body = @EmailContent,
@exclude_query_output = 1;
GO
CREATE PROCEDURE [FunStuff].[AuditMessageHandler]
(
@EmailSubject VARCHAR(255),
@EmailContent NVARCHAR(MAX)
)
AS
EXECUTE [dbo].[EmailHandler] @EmailSubject, @EmailContent;
GO
CREATE PROCEDURE [FunStuff].[AuditActivation]
AS
SET XACT_ABORT ON;
DECLARE @ConversationHandle UNIQUEIDENTIFIER,
@ConversationGroupID UNIQUEIDENTIFIER,
@MessageBody NVARCHAR(MAX),
@MessageTypeName NVARCHAR(256),
@RowsReceived INT;
WHILE (1 = 1)
BEGIN
WAITFOR(
GET CONVERSATION GROUP @ConversationGroupID
FROM [FunStuff].[ReceivingQueue]
), TIMEOUT 500;
IF (@ConversationGroupID IS NULL)
BEGIN
BREAK;
END;
WHILE (2 = 2)
BEGIN
BEGIN TRANSACTION;
RECEIVE TOP (1)
@ConversationHandle = [conversation_handle],
@MessageTypeName = [message_type_name],
@MessageBody = [message_body]
FROM [FunStuff].[ReceivingQueue]
WHERE CONVERSATION_GROUP_ID = @ConversationGroupID;
SET @RowsReceived = @@ROWCOUNT;
IF (@RowsReceived = 0)
BEGIN
COMMIT;
BREAK;
END;
IF (@MessageTypeName = N'http://schemas.Microsoft.com/SQL/ServiceBroker/EndDialog')
BEGIN
END CONVERSATION @ConversationHandle;
END;
IF (@MessageTypeName = N'//AuditMessage')
BEGIN
EXEC [FunStuff].[AuditMessageHandler] N'Email From Broker test', @MessageBody;
END;
COMMIT TRANSACTION;
END; -- WHILE (2 = 2)
END; -- WHILE (1 = 1)
GO
GRANT EXECUTE ON [FunStuff].[AuditActivation] TO [BrokerUser];
GO
CREATE QUEUE [FunStuff].[ReceivingQueue]
WITH ACTIVATION (STATUS = ON,
PROCEDURE_NAME = [FunStuff].[AuditActivation],
MAX_QUEUE_READERS = 1,
EXECUTE AS N'BrokerUser'
);
CREATE SERVICE [//ReceivingService]
AUTHORIZATION [dbo]
ON QUEUE [FunStuff].[ReceivingQueue]
([//AuditContract]);
GO
これを機能させるためのモジュール署名手順
USE [SendDbMailFromServiceBrokerQueue];
CREATE CERTIFICATE [Permission:SendDbMail]
ENCRYPTION BY PASSWORD = N'MyCertificate!MineMineMine!'
WITH SUBJECT = N'Grant permission to Send DB Mail',
EXPIRY_DATE = '2099-12-31';
-- Sign the Stored Procedure that accesses another DB
ADD SIGNATURE
TO [dbo].[EmailHandler]
BY CERTIFICATE [Permission:SendDbMail]
WITH PASSWORD = N'MyCertificate!MineMineMine!';
-- Copy the Certificate to [msdb]
DECLARE @PublicKey VARBINARY(MAX),
@SQL NVARCHAR(MAX);
SET @PublicKey = CERTENCODED(CERT_ID(N'Permission:SendDbMail'));
SET @SQL = N'
CREATE CERTIFICATE [Permission:SendDbMail]
FROM BINARY = ' + CONVERT(NVARCHAR(MAX), @PublicKey, 1) + N';';
PRINT @SQL; -- DEBUG
EXEC [msdb].[sys].[sp_executesql] @SQL;
-- Create the Certificate-based User in [msdb]
EXEC [msdb].[sys].[sp_executesql] N'CREATE USER [Permission:SendDbMail]
FROM CERTIFICATE [Permission:SendDbMail];
GRANT AUTHENTICATE TO [Permission:SendDbMail];
PRINT ''Adding Certificate-based User to DB Role [DatabaseMailUserRole]...'';
EXEC sp_addrolemember N''DatabaseMailUserRole'', N''Permission:SendDbMail'';
';
[〜#〜]テスト[〜#〜]
USE [SendDbMailFromServiceBrokerQueue];
-- execute statement below if there is an error and the queue is disabled:
-- ALTER QUEUE [FunStuff].[ReceivingQueue] WITH STATUS = ON, ACTIVATION (STATUS = ON);
EXEC [FunStuff].[SendMessage] @Content = N'Woo hoo!';
SQLCLRを使用してこれを機能させることもできました(そして、はい、TRUSTWORTHY
を有効にせずに:-)。
SQLCLR C#コード
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public class UserDefinedFunctions
{
[SqlProcedure()]
public static void ExecSendDbMail([SqlFacet(MaxSize = 255)] SqlString EmailSubject,
SqlString EmailContent)
{
using (SqlConnection _Connection = new
SqlConnection("Server=(local); Trusted_Connection=true; Enlist=false;"))
{
using (SqlCommand _Command = _Connection.CreateCommand())
{
_Command.CommandType = CommandType.StoredProcedure;
_Command.CommandText = @"dbo.sp_send_dbmail";
SqlParameter _ParamProfileName =
new SqlParameter("profile_name", SqlDbType.NVarChar, 128);
_ParamProfileName.Value = "{replace_with_your_profile_name}";
_Command.Parameters.Add(_ParamProfileName);
SqlParameter _ParamRecipients = new
SqlParameter("recipients", SqlDbType.VarChar, (int)SqlMetaData.Max);
_ParamRecipients.Value = "{replace_with_your_recipients}";
_Command.Parameters.Add(_ParamRecipients);
SqlParameter _ParamSubject =
new SqlParameter("subject", SqlDbType.NVarChar, 255);
_ParamSubject.Value = EmailSubject.Value;
_Command.Parameters.Add(_ParamSubject);
SqlParameter _ParamBody = new
SqlParameter("body", SqlDbType.NVarChar, (int)SqlMetaData.Max);
_ParamBody.Value = EmailContent.Value;
_Command.Parameters.Add(_ParamBody);
SqlParameter _ParamExcludeQueryOutput =
new SqlParameter("exclude_query_output", SqlDbType.Bit);
_ParamExcludeQueryOutput.Value = true;
_Command.Parameters.Add(_ParamExcludeQueryOutput);
_Connection.Open();
_Connection.ChangeDatabase("msdb");
_Command.ExecuteNonQuery();
}
}
return;
}
}
[〜#〜]セットアップ[〜#〜]
USE [master];
CREATE DATABASE [SendDbMailFromServiceBrokerQueue]
COLLATE Latin1_General_100_CI_AS_SC
WITH DB_CHAINING OFF,
TRUSTWORTHY OFF;
ALTER DATABASE [SendDbMailFromServiceBrokerQueue]
SET RECOVERY SIMPLE,
PAGE_VERIFY CHECKSUM,
ENABLE_BROKER;
GO
-- Create objects needed to allow for EXTERNAL_ACCESS without TRUSTWORTHY ON:
CREATE ASYMMETRIC KEY [Permission:SendDbMail$Key]
FROM EXECUTABLE FILE = N'C:\...\NoTrustworthy.dll';
CREATE LOGIN [Permission:SendDbMail$Login]
FROM ASYMMETRIC KEY [Permission:SendDbMail$Key];
GRANT EXTERNAL ACCESS Assembly TO [Permission:SendDbMail$Login];
GO
-------------------------------------------------
USE [SendDbMailFromServiceBrokerQueue];
GO
CREATE Assembly [NoTrustworthy]
AUTHORIZATION [dbo]
FROM N'C:\...\NoTrustworthy.dll'
WITH PERMISSION_SET = EXTERNAL_ACCESS;
GO
CREATE PROCEDURE [dbo].[ExecSendDbMail]
(
@EmailSubject NVARCHAR (255),
@EmailContent NVARCHAR (MAX)
)
AS EXTERNAL NAME [NoTrustworthy].[UserDefinedFunctions].[ExecSendDbMail];
GO
CREATE SCHEMA [FunStuff] AUTHORIZATION [dbo];
GO
CREATE USER [BrokerUser] WITHOUT LOGIN WITH DEFAULT_SCHEMA=[dbo];
CREATE QUEUE [FunStuff].[SendingQueue];
CREATE SERVICE [//SendingService]
AUTHORIZATION [dbo]
ON QUEUE [FunStuff].[SendingQueue];
CREATE MESSAGE TYPE [//AuditMessage]
AUTHORIZATION [dbo]
VALIDATION = NONE;
CREATE CONTRACT [//AuditContract]
AUTHORIZATION [dbo]
([//AuditMessage] SENT BY INITIATOR);
GO
CREATE PROCEDURE [FunStuff].[SendMessage]
(
@Content NVARCHAR(MAX)
)
AS
SET NOCOUNT ON;
DECLARE @ConversationHandle UNIQUEIDENTIFIER;
BEGIN DIALOG CONVERSATION @ConversationHandle
FROM SERVICE [//SendingService]
TO SERVICE '//ReceivingService'
ON CONTRACT [//AuditContract]
WITH ENCRYPTION = OFF;
SEND ON CONVERSATION @ConversationHandle
MESSAGE TYPE [//AuditMessage]
(@Content) ;
END CONVERSATION @ConversationHandle ;
GO
---------------------------------------------------------------------------
CREATE PROCEDURE [dbo].[EmailHandler]
(
@EmailSubject VARCHAR(255),
@EmailContent NVARCHAR(MAX)
)
AS
-- other logic
EXEC [dbo].[ExecSendDbMail] @EmailSubject, @EmailContent;
GO
GO
CREATE PROCEDURE [FunStuff].[AuditMessageHandler]
(
@EmailSubject VARCHAR(255),
@EmailContent NVARCHAR(MAX)
)
AS
EXECUTE [dbo].[EmailHandler] @EmailSubject, @EmailContent;
GO
CREATE PROCEDURE [FunStuff].[AuditActivation]
AS
SET XACT_ABORT ON;
DECLARE @ConversationHandle UNIQUEIDENTIFIER,
@ConversationGroupID UNIQUEIDENTIFIER,
@MessageBody NVARCHAR(MAX),
@MessageTypeName NVARCHAR(256),
@RowsReceived INT;
WHILE (1 = 1)
BEGIN
WAITFOR(
GET CONVERSATION GROUP @ConversationGroupID
FROM [FunStuff].[ReceivingQueue]
), TIMEOUT 500;
IF (@ConversationGroupID IS NULL)
BEGIN
BREAK;
END;
WHILE (2 = 2)
BEGIN
BEGIN TRANSACTION;
PRINT 'Get Message';
RECEIVE TOP (1)
@ConversationHandle = [conversation_handle],
@MessageTypeName = [message_type_name],
@MessageBody = [message_body]
FROM [FunStuff].[ReceivingQueue]
WHERE CONVERSATION_GROUP_ID = @ConversationGroupID;
SET @RowsReceived = @@ROWCOUNT;
IF (@RowsReceived = 0)
BEGIN
COMMIT;
BREAK;
END;
IF (@MessageTypeName =
N'http://schemas.Microsoft.com/SQL/ServiceBroker/EndDialog')
BEGIN
END CONVERSATION @ConversationHandle;
END;
IF (@MessageTypeName = N'//AuditMessage')
BEGIN
EXEC [FunStuff].[AuditMessageHandler]
N'Email From Broker test', @MessageBody;
END;
COMMIT TRANSACTION;
END; -- WHILE (2 = 2)
END; -- WHILE (1 = 1)
GO
GRANT EXECUTE ON [FunStuff].[AuditActivation] TO [BrokerUser];
GO
CREATE QUEUE [FunStuff].[ReceivingQueue]
WITH ACTIVATION (STATUS = ON,
PROCEDURE_NAME = [FunStuff].[AuditActivation],
MAX_QUEUE_READERS = 1,
EXECUTE AS N'BrokerUser'
);
CREATE SERVICE [//ReceivingService]
AUTHORIZATION [dbo]
ON QUEUE [FunStuff].[ReceivingQueue]
([//AuditContract]);
GO
---------------------------------------------------------------------------
[〜#〜]テスト[〜#〜]
USE [SendDbMailFromServiceBrokerQueue];
-- execute statement below if there is an error and the queue is disabled:
-- ALTER QUEUE [FunStuff].[ReceivingQueue] WITH STATUS = ON, ACTIVATION (STATUS = ON);
EXEC [FunStuff].[SendMessage] @Content = N'try me!';
[〜#〜]クリーンアップ[〜#〜]
IF (DB_ID(N'SendDbMailFromServiceBrokerQueue') IS NOT NULL)
BEGIN
RAISERROR(N'Dropping DB: [SendDbMailFromServiceBrokerQueue]...', 10, 1) WITH NOWAIT;
ALTER DATABASE [SendDbMailFromServiceBrokerQueue] SET OFFLINE WITH ROLLBACK IMMEDIATE;
ALTER DATABASE [SendDbMailFromServiceBrokerQueue] SET ONLINE WITH ROLLBACK IMMEDIATE;
DROP DATABASE [SendDbMailFromServiceBrokerQueue];
END
DROP LOGIN [Permission:SendDbMail$Login];
DROP ASYMMETRIC KEY [Permission:SendDbMail$Key];
私はこの問題について私の発見をここに投稿します。私の最初の答えはこれでした:
master
に証明書を作成し、そこからログインを作成します。このログインをmsdb
にマッピングし、対応するユーザーをDatabaseMailUserRole
に含めます
証明書をユーザーデータベースにエクスポートする
試してみる前に、ソロモンラッツキーがこの回答へのリンクを教えてくれました。また、DatabaseMailUserRole
へのメンバーシップのみを拒否するのではないかと思ったため、次のようになりました。
上の画像は、私のprocと、それがユーザーテストによってどのように実行されるかを示しています。プロシージャ(特にアクティベーションプロシージャ)は常にログインではなくserとして実行されることを理解することが重要です。つまり、設計上は 内部アクティベーションコンテキスト です。
アクティベーション用に構成されたキューでは、アクティベーションストアドプロシージャを実行するユーザーも指定する必要があります。 SQL Serverは、ストアドプロシージャを開始する前に、これを偽装しますser。
したがって、ログインのないユーザーテストで実行し、次のように作成しました。
create user test without login
さて、私は何が期待されているかを見ました:私の署名されたprocはDatabaseMailUserRole
のメンバーシップを受け取りましたが、DENY ONLY
が付いています。
OPの質問に戻ります。
それは、Service Brokerが実行されている場合でもTheUserのログインの下で何らかの理由で、それはゲストのデータベースユーザーの下で実行されています。
ログインTheUserは、msdbでTheUserと呼ばれるユーザーにもマップされます-確かにゲストにはマップされません。
サービスブローカーを通過するときにmsdbでゲストとして実行されるのはなぜですか?
エラーはここにあります:アクティブ化プロシージャはloginの下では実行されません。 serの下で常に実行されます。
そのため、Service Broker、キュー、アクティブ化プロシージャを設定する必要さえありません... serによって実行される単純なプロシージャでテストを行うのに十分です
そして今、最後の部分です。ソリューションを機能させるには、最後の条件がtrueである必要があります。ユーザーデータベースは、AUTHENTICATE SERVER
権限を持つ所有者のTRUSTWORTHY
である必要があります。このようにしてcertificate userはサンドボックスデータベースから出て行くことができます。guest
のmsdb
ではありませんが、証明書ユーザーになります:
私はそれが良い解決策だとは思いません、私は常にユーザーデータベースをTRUSTWORTHY
にすることに反対しています。
そのユーザーデータベースに、sysadmin
ではないdb_owner
が同時にない場合にのみ安全です
追伸Solomon RutzkyTRUSTWORTHY
にまったく触れないソリューションに賛成します。