web-dev-qa-db-fra.com

Impossible d'utiliser msdb.dbo.sp_send_dbmail quand dans Service Broker - Exécute comme invité?

J'ai une procédure TheNotificationProcedure qui fait appel à une base de données croisée à msdb.dbo.sp_send_dbmail.

Il est invoqué (indirectement) d'une file d'attente de courtiers de service:

CREATE QUEUE [Blah].[TheQueue]
WITH ACTIVATION (STATUS = ON, PROCEDURE_NAME = [Blah].[TheQueueProcedure], 
MAX_QUEUE_READERS = 1, EXECUTE AS N'TheUser');

TheQueueProcedure _ appelle finalement TheNotificationProcedure

Si je me connecte dans SSMS sous TheUser et exécutez TheNotificationProcedure, tout fonctionne et les courriels sortent.

Toutefois, lorsque TheNotificationProcedure est invoqué à la suite d'un message arrivant sur la file d'attente, il échoue comme incapable d'accéder à la procédure MSDB.

J'ai essayé tout ce que je peux penser, y compris la création de ma propre procédure dans MSDB qui enveloppe sp_send_dbmail et signer à la fois mon wrapper DBmail et TheNotificationProcedure avec le même certificat et assurez-vous que l'utilisateur du certificat de MSDB est membre de " Databasemailusererrole ".

Enfin, après avoir fait beaucoup de traces plus détaillées, j'ai finalement remarqué ce qui suit:

Service Broker Trace

C'est même si le courtier de service exécute sous le login de TheUser, pour une raison quelconque, il est exécuté dans la base de données utilisateur de guest, que je soupçonne au moins partiellement explique mes problèmes d'autorisations.

Le login TheUser est également mappé sur un utilisateur Dans MSDB, appelé TheUser - il n'est certainement pas cartographié à l'invité.

Alors, pourquoi est-il exécuté en tant qu'invité dans MSDB lorsqu'il passe par courtier de service?

J'ai besoin d'éviter de marquer la base de données comme Trustworthy. J'espérais que en signant les procédures (par exemple http://www.sommarskog.se/grantperm.html ), je pourrais obtenir des autorisations à transférer à travers la base de données - execute as Nier Les autorisations qui seraient généralement associées via l'utilisateur du certificat?

Voici un script pour dupliquer le problème des autorisations ci-dessus sans l'une quelconque de la signature du module (qui donne la même trace "invité") lors du passage du service de service:

Installer:

--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

Puis pour tout botter:

--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

J'ai besoin d'éviter de marquer la base de données comme Trustworthy.

C'est certainement la bonne attitude à avoir envers TRUSTWORTHY, et oui, c'est possible.

Alors, pourquoi est-il exécuté en tant qu'invité dans MSDB lorsqu'il passe par courtier de service?

J'avais initialement pensé que la question était la cause typique des problèmes croisés de la base de données lors de l'utilisation d'une impersonnation: par défaut, je saisonnant un directeur au niveau de la base de données (qui est tout ce que le EXECUTE AS Clause, non déclaration, peut faire) sera mis en quarantaine dans la base de données locale.

Cependant, des tests supplémentaires et des discussions avec le O.P. conduisent à la découverte que ce scénario était légèrement différent. Il semble que d'utiliser un courtier de service est l'un des très peu occasions que la signature de module ne résout pas tous les problèmes de sécurité. C'est ce que je == avait pensée lors de la mise en place de la mise en œuvre typique de la signature de module car elle ne fonctionnait pas. Donc, j'ai essayé plusieurs choses et j'ai constaté que seule SQLCLR a pu accomplir cela.

Puis, j'ai récemment trouvé une question connexe, SA ne dispose pas d'autorisations à une autre base de données via Synonymes avec le courtier de service , qui a référencé A poste de @Remus Rusan dans lequel Remus dit que c'est bien possible. Figurant que le code d'exemple de Remsus a fonctionné, j'ai conclu que je devais avoir manqué quelques détails mineurs. Et, en examinant les détails, j'ai trouvé une option contre-intuitive utilisée:

modifier la procédure d'exécution en tant que clause (sinon, l'infrastructure de signature de code ne fonctionne pas)

Habituellement, la signature de module vous permet de SupprimerEXECUTE AS clauses et déclarations, mais ici est requise. Et il est requis en raison du courtier de service travaillant dans un contexte de sécurité uniquement de la base de données via un EXECUTE AS USER = déclaration. En ajoutant le EXECUTE AS clause au CREATE PROCEDURE Déclaration, un nouveau contexte de sécurité est créé et peut accéder aux bases de données de niveau du serveur et/ou d'autres bases de données, ce qui correspond à ce que le reste de la configuration de la configuration de la signature du module.

Sooooo, euh, désolé de la faire correctement, puis de la modifier pour être quelque chose de manifestable mais pas idéal pour manquer cette pièce ;-). Mais, je l'ai maintenant travaillé comme je l'ai dit à l'origine de travailler :-). La première série de code d'exemple ci-dessous est la pure t-SQL, une approche de signature de module qui fonctionne avec TRUSTWORTHY OFF (Maintenant que j'ai ajouté que disparu WITH EXECUTE AS N'dbo'). Je tiendrai l'approche SQLCLR en bas puisque elle fonctionnait et pourrait mieux être mieux que d'autres scénarios.

J'espérais que en signant les procédures ... Je pourrais obtenir des autorisations pour transférer à travers la base de données

Vous pouvez. Je ne pense pas avoir jamais vu votre configuration de la signature de votre module, mais cela pourrait très bien être que vous avez manqué une petite option atypique que j'avais manquée à l'origine (c'est la clé de l'obtention de tout ce qui est de travailler).

est-ce que execute as Nettère toutes les autorisations qui seraient généralement associées via l'utilisateur de certificat?

Seulement si c'est le EXECUTE AS USER déclaration (pas le EXECUTE AS clause d'un CREATE object déclaration, ni la EXECUTE AS LOGIN déclaration). Dans ce cas, le contexte de sécurité est et ne peut être que la base de données uniquement et ne peut pas voir le niveau de serveur ou d'autres bases de données, même avec le module signature en place. Et comme la chance l'aurait, cela (c'est-à-dire le EXECUTE AS USER Déclaration) est exactement ce que fait le courtier de service pour exécuter la procédure d'activation. Donc, oui, c'est ce qui empêchait votre tentative initiale du module de travailler. Et le truc pour la fixer est de simplement ajouter un WITH EXECUTE AS N'dbo' clause au CREATE PROCEDURE Déclaration pour la procédure qui accédait à une autre base de données. Peu importe l'utilisateur que vous utilisez, mais j'ai trouvé dbo _ à l'aide de OWNER _ donné un avertissement sur la nécessité de signer la procédure stockée si ce propriétaire a jamais changé. Bien sûr, il est également possible de changer le propriétaire d'une base de données, alors j'aurais pu m'attendre à ce que je souhaitais un avertissement sur mon choix, mais je n'ai donc pas choisi d'ignorer cette nuance potentielle pour le moment ;-).

Solution idéale (T-SQL)

Configuration principale

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

Module Signature des étapes pour faire ce travail

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'';
';

TEST

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!';

Solution alternative (SQLCLR)

J'ai également été capable de faire fonctionner cela avec SQLCLR (et oui, sans que vous activiez TRUSTWORTHY :-).

code 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;
    }
}

[~ # ~] Configuration [~ # ~ ~]

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

---------------------------------------------------------------------------

TEST

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!';

NETTOYAGE

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

Je poste ici mes trouvailles autour de ce problème. Ma première réponse était celle-ci:

  • créez le certificat dans master et créez un identifiant de celui-ci;
  • carte de cette connexion à msdb et incluez l'utilisateur correntPonding dans DatabaseMailUserRole

  • exporter le certificat vers votre base de données utilisateur

  • signez votre processus d'activation avec ce certificat

Même avant que je puisse l'essayer, Solomon Rutzky m'a donné le lien avec cette réponse et je soupçonnais aussi que je ne serai pas nier à DatabaseMailUserRole abonnement, et c'est donc arrivé:

enter image description here

L'image ci-dessus montre ma procréation et comment elle est exécutée par test de l'utilisateur. Il est important de comprendre que la procédure (procédure d'activation en particulier) sera toujours exécutée comme user , pas connecté, c'est-à-dire par conception: Contexte d'activation interne

Une file d'attente configurée pour l'activation doit également spécifier l'utilisateur que la procédure stockée d'activation fonctionne comme . SQL Server Impersonate Ceci User Avant de démarrer la procédure stockée.

Je l'exécute donc par test d'utilisateur qui n'a pas de connexion, j'ai créé de cette façon:

create user test without login

Ok, j'ai vu ce qui était attendu: mon procédé signé a reçu une adhésion reçue à DatabaseMailUserRole mais avec DENY ONLY.

Maintenant, je peux retourner à la question de l'OP:

C'est même si le courtier de service exécute sous la connexion de TheUser , pour une raison quelconque, il s'exécute sous l'utilisateur de la base de données d'invité, que je suppose au moins partiellement explique mes autorisations. problèmes.

Le numéro de connexion TheUser est également mappé à un utilisateur de MSDB appelé TheUser - il n'est certainement pas cartographié à l'invité.

Alors pourquoi est-il exécuté en tant qu'invité dans MSDB lors du passage du courtier de service?

L'erreur est ici: Activation Proc n'est pas exécutée sous login . Il est toujours exécuté sous le User .

C'est pourquoi je n'ai même pas besoin de configurer le courtier de service, la file d'attente, la procréation d'activation ... Il suffit de faire le test avec un simple procureur uxecuted par User

Et maintenant la partie enfin. Pour faire de ma solution, j'ai besoin de la dernière condition pour être true: la base de données utilisateur doit être TRUSTWORTHY avec le propriétaire qui a AUTHENTICATE SERVER Autorisation. De cette façon, le certificat utilisateur Pourra sortir de sa base de données de sandbox, ce ne sera pas un guest in msdb mais il sera Soyez utilisateur de certificat:

enter image description here

Je ne pense pas que ce soit une bonne solution, je suis toujours contre la base de données utilisateur TRUSTWORTHY.

Il est sûr que lorsque cette base de données utilisateur n'a pas de db_owner Ce n'est pas un sysadmin en même temps

P.s. I upvote Solomon Rutzky Solution qui ne touche pas TRUSTWORTHY du tout.

1
sepupic