web-dev-qa-db-ja.com

Service Brokerでmsdb.dbo.sp_send_dbmailを使用できません-ゲストとして実行しますか?

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 Trace

これは、Service BrokerがTheUserloginで実行されている場合でも、何らかの理由で、データベース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
5
Nathan

データベースに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を使用すると、所有者が変更された場合にストアドプロシージャに再署名する必要があるという警告が表示されます。もちろん、データベースの所有者を変更することも可能です。そのため、自分の選択についても警告が表示されることを期待していましたが、そうではなかったため、現時点ではその潜在的なニュアンスを無視することにしました;-)。

理想的なソリューション(T-SQL)

メインセットアップ

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)

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];
12
Solomon Rutzky

私はこの問題について私の発見をここに投稿します。私の最初の答えはこれでした:

  • masterに証明書を作成し、そこからログインを作成します。
  • このログインをmsdbにマッピングし、対応するユーザーをDatabaseMailUserRoleに含めます

  • 証明書をユーザーデータベースにエクスポートする

  • この証明書でアクティベーションプロシージャに署名します

試してみる前に、ソロモンラッツキーがこの回答へのリンクを教えてくれました。また、DatabaseMailUserRoleへのメンバーシップのみを拒否するのではないかと思ったため、次のようになりました。

enter image description here

上の画像は、私の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はサンドボックスデータベースから出て行くことができます。guestmsdbではありませんが、証明書ユーザーになります:

enter image description here

私はそれが良い解決策だとは思いません、私は常にユーザーデータベースをTRUSTWORTHYにすることに反対しています。

そのユーザーデータベースに、sysadminではないdb_ownerが同時にない場合にのみ安全です

追伸Solomon RutzkyTRUSTWORTHYにまったく触れないソリューションに賛成します。

1
sepupic