SQL Server Only allow user to Execute a specific procedure

b91juud3  于 11个月前  发布在  其他
关注(0)|答案(1)|浏览(92)

I'm looking for a solution where a user's only permission it to execute a procedure. In my example below, that procedure is lowlevel.ZipFile (Main Procedure). The problem is that the procedure itself executes other procedures which causes errors.. As long as the user executes the main procedure and not any of the nested procedures themselves I want it to be approved. I've looked around and found that the easiest way to deny access to execute other stored is to create a new schema. I then Created an other user that has access to the nested procedures, and created my main procedureWITH EXECUTE AS signed to the user who has access. However,when I execute the main procedure with that user, he all of a sudden doesn't have permission to execute the nested procedure?

The code below is for testing purposes. Be aware if you've a database named "SiteDatabase44", a user named "LowLevelUser_Executor" or a procedure named "___ZipFile" in the master database. A clean up-script is commented at the end.

SET NOCOUNT ON
USE master;

IF NOT EXISTS(SELECT 1 FROM sys.databases WHERE Name = 'SiteDatabase44') 
    EXECUTE('CREATE DATABASE SiteDatabase44')

IF NOT EXISTS (SELECT 1 FROM sys.syslogins WHERE NAME = 'LowLevelUser_Executor')
BEGIN
    CREATE LOGIN [LowLevelUser_Executor] WITH PASSWORD = N'ShouldNeverBeLoggedInAs_asdasd', CHECK_POLICY = OFF;
    GRANT CONNECT SQL TO [LowLevelUser_Executor]
END
GO

CREATE OR ALTER PROCEDURE dbo.___ZipFile
    @Filepath varchar(500) NULL
AS 
    SELECT 22
GO
IF NOT EXISTS(SELECT 1 FROM sys.sysusers WHERE name = 'LowLevelUser_Executor')
    CREATE USER LowLevelUser_Executor FOR LOGIN LowLevelUser_Executor WITH DEFAULT_SCHEMA = dbo

GRANT EXECUTE ON [dbo].___ZipFile TO [LowLevelUser_Executor]

USE [SiteDatabase44];

IF NOT EXISTS(SELECT 1 FROM sys.schemas WHERE [name] = 'lowlevel') 
    EXECUTE('CREATE SCHEMA lowlevel')
GO
IF NOT EXISTS(SELECT 1 FROM sys.sysusers WHERE name = 'LowLevelUser_Executor')
    CREATE USER [LowLevelUser_Executor] FOR LOGIN [LowLevelUser_Executor] WITH DEFAULT_SCHEMA = dbo
GO
CREATE OR ALTER PROCEDURE lowlevel.ZipFile
    @Filepath varchar(500) NULL
WITH EXECUTE AS 'LowLevelUser_Executor'
AS 
    SELECT 'Zipping file...'
    EXECUTE master.dbo.___ZipFile @Filepath
GO

GRANT SELECT, EXECUTE ON SCHEMA::lowlevel TO [LowLevelUser_Executor] WITH GRANT OPTION;

EXECUTE AS LOGIN = 'LowLevelUser_Executor'
exec master.dbo.___ZipFile @Filepath = '' -- Works as expected
EXECUTE [lowlevel].ZipFile @Filepath = '' -- Doesn't work? Get's this error: The EXECUTE permission was denied on the object '___ZipFile', database 'master', schema 'dbo'.

go
REVERT;

/*
CLEAN UP

USE MASTER;
DROP DATABASE SiteDatabase44
GO
DROP PROCEDURE dbo.___ZipFile
GO
DROP USER [LowLevelUser_Executor]
GO
DROP LOGIN [LowLevelUser_Executor]

*/
1hdlvixo

1hdlvixo1#

As I mention in the comments, the problem here is that you have a cross database reference. When working in the same database ownership chaining can occur, however, for cross databases this is off by default. Though it could be configured, there are (multiple) security implications with such a set up, so without knowing your environment I can't recommend it.

As a result an alternative would be to implement certificates. This is a simplified example, you may want/need to encrypt your certificates with a password, for example. if you do you'll need to pass the private key to the CREATE CERTIFICATE statement (which can obtained using CERTPRIVATEKEY ) when creating it in the second database. This is the simplest set of steps to get to the end goal; you will likely need more specific to your needs.

First, lets create a couple of test databases, and a test login:

USE master;
GO
CREATE DATABASE SomeDatabase;
GO
CREATE LOGIN AnotherLogin WITH PASSWORD = '123abc*()';
GO
CREATE DATABASE AnotherDatabase;
GO

Now I'm going to create a procedure in both databases, with one referencing the other. Then I'll create a USER for the test LOGIN and GRANT them permission to EXECUTE the procedure that references the other:

USE SomeDatabase;
GO
CREATE PROC dbo.SomeProc AS
BEGIN
    SELECT [name]
    FROM sys.objects;
END;
GO
USE AnotherDatabase;
GO

CREATE PROC dbo.AnotherProc AS
BEGIN

    EXEC SomeDatabase.dbo.SomeProc;
END;
GO
CREATE USER AnotherUser FOR LOGIN AnotherLogin;
GO

Great, now we have a similar set up to what you do. We can now test to see if things work (they won't):

GRANT EXECUTE ON dbo.AnotherProc TO Anotheruser;
GO
EXECUTE AS LOGIN = 'AnotherLogin'
GO
EXEC dbo.AnotherProc --Fails, no access to the other database
GO
REVERT;
GO

Msg 916, Level 14, State 2, Procedure dbo.AnotherProc, Line 5 [Batch Start Line 32]
The server principal "AnotherLogin" is not able to access the database "SomeDatabase" under the current security context.

So yeah, that was expected.

So, let's create a certificate. As this is a new test database I need to create a MASTER KEY , but this may well already exist in your database (if it does, you don't need to create one again, but for the purposes here I do). I also then sign the procedure that we created before. As mentioned before, the certificate isn't encrypted, but this is something you may want/need to do.

CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'abc123!"£'; --As this is a new db, we need a master key. Obviously you'd use a better password
GO
--Create a cert
CREATE CERTIFICATE AnotherCert WITH SUBJECT = N'Allow Cross Database Queries between AnotherDB and SomeDB';
GO

ADD SIGNATURE TO dbo.AnotherProc BY CERTIFICATE AnotherCert;
GO

Great, but now we need to alsoc create that certificate in the other database. Fortunately this is much easier than the days of SQL Server 2008, and we can get the binary value of the cert using CERTENCODED . As, however, CREATE CERTIFICATE needs a literal value, we'll grab the value and concatenate it to a "dynamic" batch and use sys.sp_executesql to execute that batch. Like before, as this is a test, it create a MASTER KEY but you may well not need to do that.

DECLARE @CertBinary varbinary(8000) = CERTENCODED(CERT_ID(N'AnotherCert'));

DECLARE @SQL nvarchar(MAX) = N'CREATE MASTER KEY ENCRYPTION BY PASSWORD = ''abc123!"£'';' + NCHAR(13) + NCHAR(10) + --As this is a new db, we need a master key. Obviously you'd use a better password
                             N'CREATE CERTIFICATE AnotherCert FROM BINARY = ' + CONVERT(varchar(8000),@CertBinary,1) + N';';

EXEC SomeDatabase.sys.sp_executesql @SQL; --Will create in SomeDatabase
GO

Great, we're almost there. Finally we need to create a USER based on that cert in the other database SomeDatabase and also create a USER for the LOGINAnotherLogin in the database. Note, however, I don't give any permissions to the USER , but they do still need to be able to connect to the other database. Finally I give the certificate's USER permission to EXECUTE the procedure, (notice it is notAnotherUser ).

CREATE USER AnotherCertUser FROM CERTIFICATE AnotherCert;
CREATE USER AnotherUser FROM LOGIN AnotherLogin; --They still need to exist in the DB, edven if they have no access
GO
GRANT EXECUTE ON dbo.SomeProc TO AnotherCertUser;

Ok, we are now there and we can test again:

USE AnotherDatabase;
GO
EXECUTE AS LOGIN = 'AnotherLogin'
GO
EXEC dbo.AnotherProc --Works, permissions of tbhe certificate are used
GO
EXEC SomeDatabase.dbo.SomeProc; --To demonstrate it doesn't work.
GO
REVERT;
GO

Success! The procedure executes the procedure that callsSomeDatabase.dbo.SomeProc , but the call directly to that procedure fails.

Clean-up:

USE master;
GO
DROP DATABASE SomeDatabase;
DROP DATABASE AnotherDatabase;
DROP LOGIN AnotherLogin;

Of course, the alternative would be to just give the LOGIN 's USER in master permission to execute dbo.___ZipFile , but it appears you explicitly don't want to do that.

相关问题