Appel de sp_start_job à partir d'une procédure stockée

8

Nos développeurs doivent pouvoir démarrer un travail SQL Server Agent à partir de leur code .Net. Je sais que je peux appeler msdb..sp_start_job pour faire exactement cela, mais je ne veux pas donner aux comptes d'utilisateurs généraux un accès direct aux travaux d'exécution.

Ce que je voudrais faire, c'est créer une procédure stockée dans la base de données de l'application en utilisant la clause WITH EXECUTE AS pour emprunter l'identité d'un compte proxy. La procédure que nous avons est la suivante:

CREATE PROCEDURE dbo.StartAgentJob 
    WITH EXECUTE AS 'agentProxy'
AS
BEGIN
    EXEC msdb.dbo.sp_start_job N'RunThisJob';
END

Cependant, lorsque nous l'exécutons, nous obtenons le message suivant:

The EXECUTE permission was denied on the object 'sp_start_job', database 'msdb', schema 'dbo'.

Des idées? Est-ce même la meilleure façon de le faire dans SQL2005?

Ed Leighton-Dick
la source
1
Résolu. La solution comportait trois éléments: le chaînage de propriété doit être activé sur le serveur; l'utilisateur utilisé dans l'instruction EXECUTE AS doit être sa ou un utilisateur avec des autorisations similaires pour exécuter les travaux xp_sqlagent_ *; et le travail doit appartenir au même utilisateur que celui répertorié dans l'instruction EXECUTE AS.
Ed Leighton-Dick
Un peu plus d'expérimentation a montré une variation à cette solution. Si vous souhaitez utiliser un utilisateur proxy non-SA pour exécuter le travail, vous pouvez accorder à l'utilisateur proxy les autorisations EXECUTE sur les procédures xp_sqlagent_ * dans la base de données master. (Les deux autres exigences - la propriété de bases de données croisées et la propriété d'emplois - s'appliquent toujours.)
Ed Leighton-Dick

Réponses:

5

Avez-vous mis la connexion agentProxy dans la base de données msdb et lui avez-vous donné les droits pour exécuter sp_start_job? Sinon, vous devrez activer le chaînage des autorisations de base de données pour la base de données msdb et votre base de données utilisateur.

Vous feriez probablement mieux de mettre la connexion dans la base de données msdb et de lui accorder les droits appropriés.

mrdenny
la source
Oui - j'ai commencé par l'ajouter au rôle SQLAgentOperator, puis j'ai essayé les autorisations EXECUTE directes sur sp_start_job lui-même. Ni aidé. Il semble lancer cette erreur indépendamment des autorisations du proxy - même un compte de niveau sysadmin échoue.
Ed Leighton-Dick
Utilisez SQL Profiler et voyez quel compte effectue réellement l'appel. Maintenant que j'y pense plus, Exécuter en tant que consiste à utiliser un utilisateur de base de données, qui ne se traduit probablement pas correctement dans l'autre base de données. Essayez d'activer le chaînage de base de données et voyez si cela fonctionne.
mrdenny le
Le chaînage de la propriété était une grande partie de la solution, donc j'attribue les points ici. Il s'avère également qu'il y a deux autres pièces à cela; Je noterai ceux ci-dessus.
Ed Leighton-Dick
8

Je suis heureux que vous ayez résolu ce problème, mais le chaînage de la propriété n'est pas la solution recommandée. Puisque vous semblez valablement préoccupé par la sécurité et la bonne granularité des droits impliqués, j'ajoute cette réponse, bien que tardive, comme référence à ce qui se passe et comment résoudre ces problèmes.

EXECUTE AS portée d'emprunt d'identité

Les clauses EXECUTE AS sont disponibles en deux versions: EXECUTE AS LOGIN et EXECUTE AS USER. EXECUTE AS LOGIN est authentifié par le serveur et est un contexte d'emprunt d'identité approuvé par l'ensemble de l'instance SQL (portée serveur):

Lors de l'emprunt d'identité d'un principal à l'aide de l'instruction EXECUTE AS LOGIN, ou dans un module à portée de serveur à l'aide de la clause EXECUTE AS, la portée de l'emprunt d'identité est à l'échelle du serveur. Cela signifie qu'après le changement de contexte, toutes les ressources du serveur sur lesquelles la connexion empruntée a des autorisations sont accessibles.

EXECUTE AS USER est authentifié par la base de données et est un contexte d'emprunt d'identité approuvé uniquement par cette base de données (portée de la base de données):

Toutefois, lors de l'emprunt d'identité d'un principal à l'aide de l'instruction EXECUTE AS USER, ou dans un module de portée de base de données à l'aide de la clause EXECUTE AS, la portée de l'emprunt d'identité est limitée à la base de données par défaut. Cela signifie que les références à des objets hors de la portée de la base de données renverront une erreur.

Une procédure stockée qui a une clause EXECUTE AS créera un contexte d'emprunt d'identité de portée de base de données et, en tant que tel, ne pourra pas référencer des objets en dehors de la base de données, au cas où vous ne pourrez pas faire référence msdb.dbo.sp_start_jobcar se trouve dans msdb. Il existe de nombreux autres exemples disponibles, comme essayer d'accéder à un DMV de portée de serveur, essayer d'utiliser un serveur lié ou essayer de remettre un message Service Broker dans une autre base de données.

Activer l'emprunt d'identité de portée de base de données pour accéder à une ressource qui ne serait normalement pas autorisée authentificateur du contexte d'emprunt d'identité doit être approuvé. Pour une usurpation d'identité à base de données, l'authentificateur est le dbo de base de données. Cela peut être réalisé par deux moyens possibles:

  • En activant la propriété TRUSTWORTHY sur la base de données qui a authentifié le contexte d'emprunt d'identité (c'est-à-dire la base de données dans laquelle la clause EXECUTE AS a été émise).
  • En utilisant des signatures de code.

Ces détails sont décrits dans MSDN: Extension de l'emprunt d'identité de base de données en utilisant EXECUTE AS .

Lorsque vous avez résolu le problème via le chaînage de propriété de base de données croisée, vous avez activé le chaînage cross-db au niveau du serveur entier, ce qui est considéré comme un risque pour la sécurité. La façon la plus contrôlée et la plus fine d'obtenir le résultat souhaité est d'utiliser la signature de code:

  • Dans la base de données d'application, créez un certificat auto-signé
  • signer le dbo.StartAgentJob avec ce certificat
  • déposez la clé privée du certificat
  • exporter le certificat sur le disque
  • importer le certificat dans msdb
  • créer un utilisateur dérivé du certificat importé dans msdb
  • accorder l'autorisation AUTHENTICATE à l'utilisateur dérivé dans msdb

Ces étapes garantissent que le contexte EXECUTE AS de la dbo.StartAgentJobprocédure est désormais approuvé msdb, car le contexte est signé par un principal disposant de l'autorisation AUTHENTICATE dans msdb. Cela résout la moitié du puzzle. L'autre moitié consiste à accorder réellement l'autorisation EXECUTE msdb.dbo.sp_start_jobau contexte d'emprunt d'identité désormais fiable. Il existe plusieurs façons de procéder:

  1. carte l'utilisateur personnifié agentProxyutilisateur msdbet lui accorder l' autorisation d' exécution surmsdb.dbo.sp_start_job
  2. accorder l'autorisation d'exécution à l' msdbutilisateur dérivé du certificat d'authentificateur
  3. ajouter une nouvelle signature à la procédure, en dériver un utilisateur msdbet accorder l'autorisation d'exécution à cet utilisateur dérivé

L'option 1. est simple, mais présente un gros inconvénient: l' agentProxyutilisateur peut désormais exécutermsdb.dbo.sp_start_job à sa guise, il a vraiment accès à msdbet a la permission d'exécution.

L'option 3 est tout à fait correcte, mais je pense que c'est une surpuissance inutile.

Donc, je préfère l'option 2: accorder l'autorisation EXECUTE msdb.dbo.sp_start_jobà l'utilisateur dérivé du certificat créé dansmsdb .

Voici le SQL correspondant:

use [<appdb>];
go

create certificate agentProxy 
    ENCRYPTION BY PASSWORD = 'pGFD4bb925DGvbd2439587y'
    with subject = 'agentProxy'
   , start_date='01/01/2009';
go

ADD SIGNATURE TO OBJECT::[StartAgentJob]
      BY CERTIFICATE [agentProxy]
        WITH PASSWORD = 'pGFD4bb925DGvbd2439587y';
go

alter certificate [agentProxy] 
  remove private key;
go

backup certificate [agentProxy] 
 to file='c:\temp\agentProxy.cer';
go

use msdb
go

create certificate [agentProxy] 
  from file='c:\temp\agentProxy.cer';
go

create user [agentProxyAuthenticator] 
 from certificate [agentProxy];
go

grant authenticate to [agentProxyAuthenticator];
grant execute on msdb.dbo.sp_start_job to [agentProxyAuthenticator];
go

use [<appdb>];
go

exec dbo.StartAgentJob;
go

Mon blog contient des articles couvrant ce sujet, écrits dans le contexte des procédures activées par Service Broker (car elles nécessitent une clause EXECUTE AS):

BTW, si vous essayez de tester mon script et que vous vivez dans l'hémisphère oriental ou à l'heure d'été du Royaume-Uni, lisez certainement le dernier article que j'ai lié avant de tester.

Remus Rusanu
la source
0

Puisque vous essayez de démarrer SQL Server Agent à partir du code .NET, cela pourrait être une meilleure question pour StackOverflow?

http://www.stackoverflow.com

KPWINC
la source
1
Je pense que c'est probablement un problème de sécurité de la base de données, mais j'essaierai StackOverflow si nous ne trouvons pas de réponse ici.
Ed Leighton-Dick
0

Vérifier une instance SQL aléatoire sur le réseau SQLAgentOperatorRole ne vous donne pas directement les privilèges sp_start_job, il les hérite de SQLAgentUserRole.

Vérifiez-le en utilisant:

select dp.NAME AS principal_name,
                 dp.type_desc AS principal_type_desc,
                 o.NAME AS object_name,
                 p.permission_name,
                 p.state_desc AS permission_state_desc 
    from    sys.database_permissions p
    left    OUTER JOIN sys.all_objects o on p.major_id = o.OBJECT_ID
    inner   JOIN sys.database_principals dp on p.grantee_principal_id = dp.principal_id
    where o.name = 'sp_start_job'

Exécutez cela dans MSDB et vérifiez que vous n'avez hérité d'aucun accès de refus explicite.

hth.

Andrew
la source
L'utilisateur est explicitement membre de SQLAgentOperatorRole et SQLAgentUserRole.
Ed Leighton-Dick
0

Une façon d'y parvenir sans accorder d'autorisations supplémentaires: ne laissez pas le proc stocké démarrer le travail directement, mais laissez simplement le proc stocké retourner un peu dans une table (dans la base de données d'application); ensuite, laissez le travail s'exécuter toutes les minutes environ, vérifiez si le foret est retourné et si c'est le cas, effectuez le travail et retournez-le à nouveau. Si le travail voit que le bit n'est pas retourné, le travail se terminera simplement.

Fonctionne comme un charme, si cela ne vous dérange pas le délai (et le travail s'exécute très souvent).

Robert van den Berg
la source