SQLServerCentral Article

SQL Server Security: The db_executor Role

,

This article represents the first in what I hope will become a regular series

of articles here at SQLServerCentral.com.  Security is a big topic

nowadays, and security with respect to SQL Server is no exception. SQL Server

has a lot of constructs to help make it a secure database platform. One such

construct is the concept of the role. A role is used to control access to data

within a given database (database roles) or to delegate certain administrative

functions on the server (server roles). Speaking of data access, within the

security world there are typically three ways to handle access:

  • Mandatory Access Control (MAC)
  • Discretionary Access Control (DAC)
  • Role-Based Access Control (RBAC)

Mandatory Access Control is typically seen in military environments where

information is classified at a certain level. If you don't have the level, you

don't have access. For instance, if you bear a Secret clearance but the

information is rated Top Secret, you can't look at the data. However, if you

have Top Secret clearance, you can (provided everything else fits into place,

such as a "Need to Know"). Discretionary Access Control is typically

where permissions are assigned per user. SQL Server has such ability: I can

assign SELECT rights against the Orders table for user JDoe. But SQL Server also

has Role-Based Access Control. And RBAC can really simplify our security plan.

The db_datareader and db_datawiter Roles

Consider the database roles db_datareader and db_datawriter. If I want to

give a particular user the ability to issue a SELECT statement against every

table and view within a given database, I just add the user to the db_datareader

role. Likewise, if I want a given user to have the ability to modify data in any

table or view within the database, I add the user to the db_datawriter role.

Note: Before I go any further, I need to state that I'm not a big fan

of these blanket permission type roles. The main reason is I don't like giving

direct access to tables, preferring to control access through the use of views

and stored procedures. After all, if a user forgets a WHERE clause in a query or

includes it but doesn't get it quite right... you get the idea.. Also, the

db_datareader role has access to all of the system tables within a given

database. This is an information disclosure problem that violates the principle

of least privilege: give a user only the permissions the user needs to complete

his or her work and nothing more.  However, there are instances where these

blanket permission type of roles are implemented because time is short and the

risk is relatively small. The db_executor role will be just such a blanket

permission type of role.

What's Missing: The db_executor Role

While there is a role to SELECT data from all the tables and views in the

db_datareader role and there is a role to INSERT, UPDATE, and DELETE in

db_datawriter, notice that there isn't a role for executing all stored

procedures within a given database. I can fix this by creating a user-defined

role, one which I've named the db_executor since I want it to have EXECUTE

permissions on all stored procedures. To create the db_exector role, I simply

need to use the sp_addrole system stored procedure. I'll go ahead and create

this role in the pubs database:

USE pubs

GO

EXEC sp_addrole 'db_executor'

GO

However, the main problem with the db_executor role is that it currently has

no permissions. This differs from the db_datareader and db_datawriter roles

which implicitly have their rights. In other words, if I add a table or view,

the db_datareader and db_datawriter roles automatically gain access to the table

or view. In contrast, if I add a stored procedure, the db_executor role won't

automatically have EXECUTE rights. Such is the life of a user-defined role!

The Fix: Manually Granting Permission

Since SQL Server isn't going to handle granting permissions for me, I'll have

to do it manually. In order to do this, I'll need to make use of dynamic SQL

since I won't know the owners and stored procedure names at run-time. The trick

is to build the GRANT EXECUTE statement to give db_executor the ability to

execute all stored procedures. The following code does just that in the pubs

database. Notice that I just grant permissions to all stored procedures; I don't

try and compare what db_executor already has versus what it doesn't. If you have

a large number of stored procedures, this might be necessary. I've included a

second script that only retrieves those stored procedures that db_executor

doesn't have access to at the end of the article.

USE pubs

GO

DECLARE @SQL nvarchar(4000),

  @Owner sysname,

  @StoredProcedure sysname,

  @Return int

-- Cursor of all the stored procedures in the current database

DECLARE cursStoredProcedures CURSOR FAST_FORWARD

FOR

SELECT USER_NAME(uid) Owner, [name] StoredProcedure

FROM sysobjects

WHERE xtype = 'P'

OPEN cursStoredProcedures

-- "Prime the pump" and get the first row

FETCH NEXT FROM cursStoredProcedures

INTO @Owner, @StoredProcedure

-- Set the return code to 0

SET @Return = 0

-- Encapsulate the permissions assignment within a transaction

BEGIN TRAN

-- Cycle through the rows of the cursor

-- And grant permissions

WHILE ((@@FETCH_STATUS = 0) AND (@Return = 0))

  BEGIN

    -- Create the SQL Statement. Since we're giving

    -- access to all stored procedures, we have to 

    -- use a two-part naming convention to get the owner.

    SET @SQL = 'GRANT EXECUTE ON [' + @Owner

          + '].[' + @StoredProcedure 

          + '] TO db_executor'

    -- Execute the SQL statement

    EXEC @Return = sp_executesql @SQL

    -- Get the next row

    FETCH NEXT FROM cursStoredProcedures

    INTO @Owner, @StoredProcedure

  END

-- Clean-up after the cursor

CLOSE cursStoredProcedures

DEALLOCATE cursStoredProcedures

-- Check to see if the WHILE loop exited with an error.

IF (@Return = 0)

  BEGIN

    -- Exited fine, commit the permissions

    COMMIT TRAN

  END

ELSE

  BEGIN

    -- Exited with an error, rollback any changes

    ROLLBACK TRAN

    

    -- Report the error

    SET @SQL = 'Error granting permission to ['

    + @Owner + '].[' + @StoredProcedure + ']'

    RAISERROR(@SQL, 16, 1)

  END

GO

After the script runs, the db_executor role will have permission to execute

every stored procedure in the database. That means any user I add to the role

will have the ability to use all of the stored procedures in the database.

Scheduling Permission Updates

This script works great but there is a problem: it only runs once. In order

to keep the db_executor role permissions up-to-date, the script will have to be

run as frequently as is necessary for a given database. If there is an extreme

amount of change, the script may be running once a hour, maybe more. SQL Server

Agent can be used to schedule and run the script. But even with SQL Server

Agent, there will be a time lapse between when a stored procedure is created and

when the db_executor role has permission to execute it. The best solution is to

simply grant permission to the db_executor role when the stored procedure is

created, but this isn't always possible.

For instance, in a development environment you may have granted the

db_ddladmin role to a few users. This role allows a user to create database

objects but the role actually goes a step further: it allows a user to create a

database object with any owner (so long as the owner is a valid user in the

database). But the db_ddladmin role doesn't have the ability to assign

permissions. The db_securityadmin role has such rights but it's rare indeed when

I see a developer put in both roles (db_owner also can, but it's rarer still for

a developer to have such high permissions). If you're using db_ddladmin you may

have to look at scheduling the script in order to keep the db_executor role up-

to- date. How often will depend on the environment.

In a production environment with proper change control procedures, this

should never be a problem. When the stored procedure is put into the

environment, permissions to execute the stored procedure should also be

included. Even if they aren't, when you make an update to the production

environment, you can also run the script manually to update all the permissions

and ensure no stored procedure is missed. So scheduling automatic updates in the

production environment isn't of as great a concern as in development.

In Closing

It is a shame that SQL Server has the db_datareader and db_datawriter roles

but currently has nothing equivalent to a db_executor role. However, we can make

a user-defined role that sort of fits the bill. The problem is, of course,

keeping the permissions up-to-date. In a production environment change should be

carefully controlled and this usually isn't that big an issue, but development

is a different story. Unless developers possess the db_securityadmin role (or

db_owner role), you'll need to look at some automated mechanism (such as through

SQL Server Agent) scheduled regularly enough to be sufficient for the

development effort.  If you have need of the db_executor role, hopefully

this is enough to get you started. 

 © 2003 by K.

Brian Kelley. http://www.truthsolutions.com/

 Author of Start

to Finish Guide to SQL Server Performance Monitoring.

Resources

Rate

4.71 (7)

Share

Share

Rate

4.71 (7)