views:

103

answers:

1

I'm working with a system which had to create objects in one database based on objects being created in another database. The objects are not duplicates, so I can't simply replicate the objects.

I have code below which gives a simplified demonstration of what I'm trying to do. If you uncomment the ALTER DATABASE statements then it will run without any errors. That has the potential of creating a security hole though, so I'd like to avoid it if possible.

I've tried using certificates and impersonation, but nothing seems to work. I think that the DDL trigger is ignoring a lot of the security when it comes to users vs. logins. I've also tried creating a stored procedure in Test_DB_2 which calls the SP in Test_DB_1 and having the trigger call that stored procedure instead, but that didn't help either.

So, your challenge, if you're willing to accept it, is to get the code below to work without setting TRUSTWORTHY ON (or turning on db chaining if that has any effect).

Thanks for any help that you can give!

/************************
   SET-UP THE TEST
************************/
USE master
GO
CREATE LOGIN Test_Security_Login WITH PASSWORD = 'p@ssw0rd1!'
CREATE DATABASE Test_DB_1
CREATE DATABASE Test_DB_2
GO
USE Test_DB_1
GO
CREATE PROCEDURE dbo.Create_View
AS
BEGIN
 EXEC('CREATE VIEW Test_View AS SELECT 1 AS one')
END
GO
CREATE USER Test_Security_User FOR LOGIN Test_Security_Login
GRANT EXECUTE ON dbo.Create_View TO Test_Security_User
GO
USE Test_DB_2
GO
CREATE TRIGGER DDL_TRIGGER ON DATABASE WITH EXECUTE AS 'dbo' FOR DDL_VIEW_EVENTS
AS
BEGIN
 EXEC Test_DB_1.dbo.Create_View
END
GO
CREATE USER Test_Security_User FOR LOGIN Test_Security_Login
EXEC sp_addrolemember 'db_ddladmin', 'Test_Security_User'

/************************
   RUN THE TEST
************************/
USE Test_DB_2
GO
--ALTER DATABASE Test_DB_1 SET TRUSTWORTHY ON
--ALTER DATABASE Test_DB_2 SET TRUSTWORTHY ON
EXECUTE AS USER = 'Test_Security_User'
GO
CREATE VIEW dbo.Test_View_2 AS SELECT 2 AS two
GO
REVERT
GO

/************************
   CLEAN-UP
************************/
USE master
GO
DROP DATABASE Test_DB_1
DROP DATABASE Test_DB_2
DROP LOGIN Test_Security_Login
GO
+3  A: 

Too easy. Use Code Signing:

  • create self signed certificate in db1
  • sign the trigger with a certificate
  • drop private key to prevent abuse
  • export certificate into db2 (backup/create from file)
  • create credential from certificate in db2
  • grant AUTHENTICATE and any other necessary permission to certificate derived credential
  • ?
  • profit

This is bullet proof. See Call a procedure in another database from an activated procedure for a fully fledged example.

Remus Rusanu
Too easy? Not quite... you can't sign a DDL trigger: http://social.msdn.microsoft.com/Forums/en/sqlsecurity/thread/1333eecd-4c66-43d4-ab8f-03511cad4174 ;)
Tom H.
D'oh... OK, then the hard way: instead of using DDL triggers, use Event Notifications. This will make the view addition asynchronous (will happen after the original DDL has committed). There are several ways: server level EN with procedure in [msdb], db level EN with local procedure signed to access x-db or db level EN with SSB x-db delivery and procedure execution in target db. Pick your favorite and I'll guide you how to do it.
Remus Rusanu
I was actually able to get it working by using a stub stored procedure called by the DDL trigger with the stub SP using EXECUTE AS and with certificates implemented using the GRANT AUTHENTICATE. I had tried using a stub SP before and I had tried using certificates, but the missing piece I think was the AUTHENTICATE permission. Once I whittle it down to the minimum required permissions I'll post an answer with the full code, but since you pointed me in the right direction I'm going to accept your answer. Thanks!
Tom H.
Yes, calling a signed SP would obviously work, but I completely missed it...
Remus Rusanu