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:
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
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 ;-).
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!';
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];
Je poste ici mes trouvailles autour de ce problème. Ma première réponse était celle-ci:
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
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é:
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:
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.