问题
I developped a stored procedure in order to restore a database from a backup file and add an application user to it. This stored procedure belongs to the master database.
The issue is that my IT department does not allow me to use an admin user, only to use an EXECUTE AS statement with a sysadmin user.
I can restore the database but I can't find a way to add the user at the end of the process.
The code I use:
CREATE PROCEDURE [dbo].[myProc]
@database VARCHAR(50)
WITH EXECUTE AS 'aSysAdminUser'
AS
BEGIN
--Restore the database (working)
--Add the application user (not working)
SET @sqlScript = 'USE '+@database +';
CREATE USER [myApplicationUser] FROM LOGIN [myApplicationUser];
EXEC sp_addrolemember ''db_owner'', ''myApplicationUser'''
EXEC(@sqlScript)
END
When I run it, I have the following error message:
The server principal "aSysAdminUser" is not able to access the database "database" under the current security context.
Any idea how I could create a user in a dynamic parameter db name from a stored procedure on master db using a EXECUTE AS statement?
Thanks
回答1:
The issue you are facing is a limitation of Impersonation (i.e. switching the security context via EXECUTE AS
). For database-scoped objects, such as stored procedures you are specifying a database-level user
in the EXECUTE AS
clause, not a server-level login
. Hence, this procedure is not really acting as a sysadmin
. But, there is a way to safely grant just this one stored procedure true sysadmin
permissions that will allow it to do the steps in your dynamic SQL, namely:
- CONNECT to the database
- CREATE USER
- ALTER ROLE
The way to do this is through signing the stored procedure with a certificate. The certificate will then also be used to create a server-level login that will be added to the sysadmin
server role. Then, when any user / login (that has EXECUTE
permission on this stored procedure) executes this procedure, it will pick up the permissions of the certificate-based login merely by being signed by the same certificate.
Step 1: Set up the certificate in the [master]
database:
USE [master];
GO
CREATE CERTIFICATE [BackupRestoreCert]
ENCRYPTION BY PASSWORD = N'MyPassword'
WITH SUBJECT = N'Certificate for Managing Backup/Restore Operation Permissions';
GO
Step 2: Create the login and add to the sysadmin
role:
CREATE LOGIN [BackupRestoreOps] FROM CERTIFICATE [BackupRestoreCert];
ALTER SERVER ROLE [sysadmin] ADD MEMBER [BackupRestoreOps];
Step 3: Sign the stored procedure with that certificate, creating the link to the new login:
ADD SIGNATURE
TO [dbo].[myProc]
BY CERTIFICATE [BackupRestoreCert]
WITH PASSWORD = 'MyPassword';
All of the above is actually done within the context of the [master]
database, though it seemed more readable for explanation to have it broken up. But, in practice it would be a single script that the IT folks run once. Of course, if you ever ALTER
that stored procedure, they will have to run the ADD SIGNATURE
command again (i.e. Step 3) as it is lost upon any changes to the definition of the procedure.
And that is that. I have tested this with the stored procedure shown in the question and it did, once I added the signature and the sysadmin
role, create the user and add it to the db_owner
role.
Step 4 (maybe): You could probably remove the EXECUTE AS
clause from the stored procedure. If there were any permissions that it did pass on that allowed the initial restore operation(s) to work, those should be now assumed via the certificate-based login as it is marked as sysadmin
.
DISCLAIMER
I just want / need to make clear that it is understood that, given the stored procedure specified in the question, as it is written, will allow for SQL Injection once this permission is granted. This, of course, was already a consequence of the original intention of the IT staff's decision to use the EXECUTE AS
clause to grant sysadmin
permissions to this stored procedure. I am just pointing it out now that the above steps will actually make their intended behavior a reality. If there are steps above the dynamic SQL that would error if anything other than a database name were passed in, then great, but this still needs to be stated for others who might be looking to duplicate this and just copying / pasting without understanding the full context.
There are two things that should be done:
- While a minor point, the datatype of the input parameter should be
sysname
(alias forNVARCHAR(128)
) as that is how[name]
is defined in[sys].[databases]
. The main thing to do is verify that the value passed in is an existing database name, something along the lines of:
IF (DB_NAME(@database) IS NULL) BEGIN RAISERROR(N'Invalid Database Name', 16, 1); RETURN; END;
EDIT:
I have tried finding another server role that would allow for this that is not sysadmin
in the hopes of not using such a privileged role. I have tried dbcreator
, securityadmin
, serveradmin
, and setupadmin
, but unfortunately none of them worked :(.
来源:https://stackoverflow.com/questions/26599324/sql-server-execute-as-clause-of-stored-procedure-not-granting-sysadmin-permissi