Here’s a suggestion on how you can grant developers plenty of permissions on a dev or test database without just adding them to the db_owner role (which is going too far).
I found a post by Brent Ozar talking about podcasts he likes. Thanks for the tips, Brent. I checked them out and I particularly like the SQL Data Partners podcast. Thanks for the excellent listening, Carlos. I’m working through the episodes and episode 8 ‘The Principal of Least Privilege’ with Robert Verrell (aka SQL Cowbell) really got me thinking.
Robert talked about why adding developers to the db_owner role is a bad idea, even on a dev server. Then he proposed adding them to a custom high-powered database role, db_developer, instead. This way they have enough rights to do things they need to but hopefully they can’t destroy the server. He blogged about it here.
I like the idea but we regularly refresh our our test databases by restoring from production backups and sanitising sensitive data. This process would undo the db_developer role so I wrapped it up in a stored procedure to make it easy to re-run on each relevant database at the end of the refresh job:
if exists ( select 1 from dbo.sysobjects where id = object_id(N'dbo.dba_AddDBDeveloperRole') and objectproperty(id, N'IsProcedure') = 1 ) drop proc dbo.dba_AddDBDeveloperRole; go set ansi_nulls on; set quoted_identifier on; go create proc dbo.dba_AddDBDeveloperRole ( @DBName sysname , @Login sysname , @DebugMode bit = 0 ) as /* --------------------------------------------------------------------------------------------------------------------------------------------------- Version : 1.01 Date : 22/12/2015 Grant developers lots of permissions without the really dangerous ones like db_owner or sysadmin. Based on this blog post: http://sqlcowbell.com/wordpress/why-nobody-ever-needs-the-db_owner-role/. Original script from that post: CREATE ROLE [db_developer] AUTHORIZATION [dbo] GRANT ALTER ANY APPLICATION ROLE TO [db_developer] GRANT ALTER ANY ASSEMBLY TO [db_developer] GRANT ALTER ANY DATABASE DDL TRIGGER TO [db_developer] GRANT ALTER ANY DATASPACE TO [db_developer] GRANT ALTER ANY FULLTEXT CATALOG TO [db_developer] GRANT ALTER ANY MESSAGE TYPE TO [db_developer] GRANT ALTER ANY SCHEMA TO [db_developer] GRANT CREATE AGGREGATE TO [db_developer] GRANT CREATE ASSEMBLY TO [db_developer] GRANT CREATE DATABASE DDL EVENT NOTIFICATION TO [db_developer] GRANT CREATE DEFAULT TO [db_developer] GRANT CREATE FULLTEXT CATALOG TO [db_developer] GRANT CREATE FUNCTION TO [db_developer] GRANT CREATE PROCEDURE TO [db_developer] GRANT CREATE ROLE TO [db_developer] GRANT CREATE RULE TO [db_developer] GRANT CREATE SCHEMA TO [db_developer] GRANT CREATE SERVICE TO [db_developer] GRANT CREATE SYNONYM TO [db_developer] GRANT CREATE TABLE TO [db_developer] GRANT CREATE TYPE TO [db_developer] GRANT CREATE VIEW TO [db_developer] GRANT CREATE XML SCHEMA COLLECTION TO [db_developer] GRANT DELETE TO [db_developer] GRANT EXECUTE TO [db_developer] GRANT INSERT TO [db_developer] GRANT REFERENCES TO [db_developer] GRANT SELECT TO [db_developer] GRANT SHOWPLAN TO [db_developer] GRANT UPDATE TO [db_developer] GRANT VIEW DATABASE STATE TO [db_developer] GRANT VIEW DEFINITION TO [db_developer] --------------------------------------------------------------------------------------------------------------------------------------------------- */ set nocount on; declare @SQL nvarchar(4000); print 'Database: ' + @DBName; print 'Create role'; set @SQL = 'use [' + @DBName + ']' + ' if not exists (select 1 from sys.database_principals where type_desc = ''DATABASE_ROLE'' and name = ''db_developer'')' + ' create role [db_developer] authorization [dbo]'; if @DebugMode = 1 print @SQL; else exec sp_executesql @SQL; print 'Grant permissions to the role'; set @SQL = 'use [' + @DBName + ']' + ' grant alter any application role to [db_developer];' + ' grant alter any assembly to [db_developer];' + ' grant alter any database ddl trigger to [db_developer];' + ' grant alter any dataspace to [db_developer];' + ' grant alter any fulltext catalog to [db_developer];' + ' grant alter any message type to [db_developer];' + ' grant alter any schema to [db_developer];' + ' grant create aggregate to [db_developer];' + ' grant create assembly to [db_developer];' + ' grant create database ddl event notification to [db_developer];' + ' grant create default to [db_developer];' + ' grant create fulltext catalog to [db_developer];' + ' grant create function to [db_developer];' + ' grant create procedure to [db_developer];' + ' grant create role to [db_developer];' + ' grant create rule to [db_developer];' + ' grant create schema to [db_developer];' + ' grant create service to [db_developer];' + ' grant create synonym to [db_developer];' + ' grant create table to [db_developer];' + ' grant create type to [db_developer];' + ' grant create view to [db_developer];' + ' grant create xml schema collection to [db_developer];' + ' grant delete to [db_developer];' + ' grant execute to [db_developer];' + ' grant insert to [db_developer];' + ' grant references to [db_developer];' + ' grant select to [db_developer];' + ' grant showplan to [db_developer];' + ' grant update to [db_developer];' + ' grant view database state to [db_developer];' + ' grant view definition to [db_developer];'; if @DebugMode = 1 print @SQL; else exec sp_executesql @SQL; print 'Create a user for the login'; set @SQL = 'use [' + @DBName + ']' + ' if not exists (select 1 from sys.database_principals where type_desc = ''SQL_USER'' and name = ''' + @Login + ''')' + ' create user [' + @Login + '] for login [' + @Login + '] with default_schema = [dbo];'; if @DebugMode = 1 print @SQL; else exec sp_executesql @SQL; print 'Add user to role'; set @SQL = 'use [' + @DBName + ']' + ' if not exists (' + 'select 1' + ' from sys.database_role_members rm' + ' inner join sys.database_principals r on r.principal_id = rm.role_principal_id' + ' inner join sys.database_principals m on m.principal_id = rm.member_principal_id' + ' where r.type_desc = ''DATABASE_ROLE''' + ' and r.name = ''db_developer''' + ' and m.name = ''' + @Login + '''' + ')' + ' alter role [db_developer] add member [ITDev];'; if @DebugMode = 1 print @SQL; else exec sp_executesql @SQL; go
The proc is re-runnable and only does the bits that need to be done. It doesn’t check each permission because you don’t get an error if it’s already granted.
Note that the actual permissions bit is just a list so you could easily comment out any you don’t want to grant. Indeed you really should look at all these permissions and see what they mean.
If you set @DebugMode = 1 it will just print out the SQL instead of executing it. This was very handy during development and you could use it if you change anything or if you want to run the changes past somebody for compliance checking.
You can call it for a specific database like this:
exec master.dbo.dba_AddDBDeveloperRole @DBName = 'DatabaseX' , @Login = 'LoginY';
Or you could apply it to all user databases like this:
print 'Grant rights to DevTeam' declare @DBName sysname; declare db_cursor cursor local forward_only for select name from sys.databases where name not in ('master', 'model', 'msdb', 'tempdb') and state_desc = 'ONLINE' and is_read_only = 0 order by name; open db_cursor; fetch next from db_cursor into @DBName; while @@fetch_status = 0 begin exec master.dbo.dba_AddDBDeveloperRole @DBName = @DBName , @Login = 'DevTeam'; fetch next from db_cursor into @DBName; end close db_cursor; deallocate db_cursor;
Tweak the WHERE clause to exclude other databases.
You can download the code here.