views:

53

answers:

1

Hi, I need to set up a system which will allow developers to request an emergency ID for a database. They will be assigned to a role called 'analyst' which will provide them a drop down box with the databases they can gain access to. They will submit the request and a temporary SQL Login will be generated and displayed on screen. The login will have some elevated privs. The login will be removed after 12 hours.

I've got the whole thing working myself as an SA on ASP.net, but now I'm working on modifying the procedures to work using a SQL Login in the application connection string.

I've tried a few things to get it working, but have run into a roadblock.

Here's my procedure that does the real work.

    USE [SQLEmergencyLoginRequest]
GO
/****** Object:  StoredProcedure [dbo].[SQLELR_Login_CREATE]    Script Date: 12/08/2009 14:48:29 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER  PROCEDURE [dbo].[SQLELR_Login_CREATE] 
@SERVER VARCHAR(50),
@DATABASE VARCHAR(50),
@NTLOGIN VARCHAR(50),
@IR INT,
@LOGIN VARCHAR(50) OUTPUT,
@PWD VARCHAR(20) OUTPUT,
@NotifyDBA INT 
WITH EXECUTE AS OWNER 
AS
/*
Emergency_Access_Login_CREATE:  Create Login/PWD, Create User, Create Role, Add User to Role, return Login/PWD.
*/
DECLARE @Random_Login_Extension VARCHAR(20)
DECLARE @sql VARCHAR(1000)

SET @Database = QUOTENAME(@Database);


BEGIN TRANSACTION

--CREATE LOGIN/PWD
EXEC dbo.random_password @Random_Login_Extension OUTPUT;
EXEC dbo.random_password @PWD OUTPUT;

SET @LOGIN = 'Emergency_Login_' + @Random_Login_Extension;

SET @sql= 'CREATE LOGIN [' + @LOGIN + ']' +
'WITH PASSWORD= ''' + @PWD + ''', DEFAULT_DATABASE=[master], ' +
'CHECK_EXPIRATION=OFF, CHECK_POLICY=OFF';

EXEC(@sql);

--CREATE USER
DECLARE @User_Cmd VARCHAR(1000);

SET @User_Cmd = 'USE ' + @DATABASE + ';' +

'CREATE USER [' + @LOGIN + '] FOR LOGIN [' + @LOGIN + '];' +
'EXEC sp_addrolemember N''db_datareader'',''' + @LOGIN + ''';' +
'EXEC sp_addrolemember N''db_datawriter'',''' + @LOGIN + ''';' +
'EXEC sp_addrolemember N''db_ddladmin'',''' + @LOGIN + ''';';

EXEC (@User_Cmd);

INSERT INTO dbo.SQLELR_Emergency_Logins
           ([CreationTime]
           ,[NTLogin]
           ,[IR]
           ,[SERVER]
           ,[DATABASE]
           ,SQLLoginCreated)
     VALUES
           (GETDATE()
           ,@NTLOGIN
           ,@IR
           ,@SERVER
           ,@DATABASE
           ,@LOGIN)


DECLARE @MYBODY VARCHAR(500)
SET @MYBODY = @NTLOGIN + ' has created a temporary login in the ' + @Database + ' Database.  The login name is ' + @LOGIN;

DECLARE @MYSUBJECT VARCHAR(500) 
SET @MYSUBJECT = 'Emergency Login Creation ON server ' + @@SERVERNAME;

IF @NotifyDBA = 1
BEGIN 
    EXEC msdb.dbo.sp_notify_operator
       @profile_name = 'SQLDBA',
       @name = 'SQLDBA',
       @subject = @MYSUBJECT,
       @body = @MYBODY;
END 

COMMIT TRANSACTION

I don't want the application account to be highly privileged in every DB, so I created another account which will go into every db and have db_owner. Evidently the sp_addrolemember using fixed db roles needs a db_owner to work, which is why the acct is db_owner. I'd prefer security admin, but it seems it's not possible.

Back to the problem - using EXECUTE AS with dynamic code does not work.

Is the only way to get this done by creating a stored procedure in every database which creates the user?

We're doing this because we'd like to crank down security on this server and take away db_owner from developers which has been the norm for years. Creating this mechanism will satisfy their only remaining complaint about not having access. They are afraid we won't answer a page and they will be unable to resolve an issue, so this will take care of that.

Of course, any advice on security holes here would be appreciated as well.

+1  A: 

The EXECUTE AS clause on the work procedure puts you into the 'execute as' cage, see Extending Database Impersonation by Using EXECUTE AS. Because the EXECUTE AS of the procedure is an database principal, the execute as context will be trusted only inside the database.

There are two workarounds, the 500lb sledge hammer of ALTER DATABASE [SQLEmergencyLoginRequest] SET TRUSTWORTHY ON or the surgical precission tool of code signing, see Call a procedure in another database from an activated procedure for an example. I highly recommend the code signing approach:

  • craete a certificate in SQLEmergencyLoginRequest
  • sign the procedure
  • drop the private key of the certificate to prevent future use for signing
  • export the certificate
  • import the certificate in master
  • create a login derived from the certificate
  • grant AUTHENTICATE on SERVER to the certificate derived login
  • grant all other priviledges needed for the procedure to this derived login

This would ensure that the procedure has all the needed priviledges to do its work, in any database. You have to redo the whole signing procedure every time you alter it.

Remus Rusanu
"grant all other priviledges needed for the procedure to this derived login" I'm a little confused on why the user is put into master and what permissions need to be granted so it can create logins and users in every database. Thanks again for your help Remus.
Sam
CREATE LOGIN requires ALTER ANY LOGIN at the server level. CREATE USER requires ALTER ANY USER in the database. You can GRANT ALTER ANY LOGIN to the certificate derived login, but to create users in the database you also need a server level permission (since you can't realistically replicate the certificate in each DB), and such a permission would be CONTROL SERVER. Although its a pretty powerful permission, using it under code signing is OK.
Remus Rusanu
It seems that CONTROL SERVER does not work while the sysadmin server role does.
Sam