Skip to content

FM-1898 Add sqlserver::user::permssion with GRANT, REVOKE and DENY #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 18, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .fixtures.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
fixtures:
forge_modules:
stdlib: "puppetlabs/stdlib"
acl: "puppetlabs/acl"
symlinks:
"sqlserver": "#{source_dir}"
sqlserver: "#{source_dir}"
9 changes: 0 additions & 9 deletions .geppetto-rc.json

This file was deleted.

4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ group :development, :test do
gem 'nokogiri'
gem 'mime-types', '<2.0', :require => false
gem 'rake', :require => false
gem 'rspec-puppet', '~>1.0', :require => false
gem 'rspec-puppet', '~>2.0', :require => false
gem 'puppetlabs_spec_helper', :require => false
gem 'puppet-lint', :require => false
gem 'simplecov', :require => false
gem 'rspec', '~> 2.14.0', :require => false
gem 'rspec', :require => false
gem 'beaker-rspec', :require => false
gem 'yard', :require => false
gem 'pry', :require => false
Expand Down
56 changes: 56 additions & 0 deletions manifests/user/permission.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
##
# == Define Resource Type: sqlserver::user::permission#
#
# === Requirement/Dependencies:
#
# Requires defined type {sqlserver::config} in order to execute against the SQL Server instance
#
#
# === Parameters
# [user]
# The username for which the permission will be manage.
#
# [database]
# The databaser you would like the permission managed on.
#
# [permission]
# The permission you would like managed. i.e. 'SELECT', 'INSERT', 'UPDATE', 'DELETE'
#
# [state]
# The state you would like the permission in. Accepts 'GRANT', 'DENY', 'REVOKE' Please note that REVOKE equates to absent and will default to database and system level permissions.
#
# [instance]
# The name of the instance where the user and database exists. Defaults to 'MSSQLSERVER'
#
##
define sqlserver::user::permission (
$user,
$database,
$permission = $title,
$state = 'GRANT',
$instance = 'MSSQLSERVER',
){
sqlserver_validate_instance_name($instance)

## Validate Permissions
$_permission = upcase($permission)
sqlserver_validate_range($_permission, 4, 128, 'Permission must be between 4 and 128 characters')
validate_re($_permission, '^([A-Z]|\s)+$','Permissions must be alphabetic only')

## Validate state
$_state = upcase($state)
validate_re($_state,'^(GRANT|REVOKE|DENY)$',"State can only be of 'GRANT', 'REVOKE' or 'DENY' you passed ${state}")

sqlserver_validate_range($database, 1, 128, 'Database must be between 1 and 128 characters')

sqlserver_validate_range($user, 1, 128, 'User must be between 1 and 128 characters')

sqlserver_tsql{
"user-permissions-${instance}-${database}-${user}-${$_state}-${_permission}":
instance => $instance,
command => template("sqlserver/create/user_permission.sql.erb"),
onlyif => template('sqlserver/query/user_permission_exists.sql.erb'),
require => Sqlserver::Config[$instance],
}

}
18 changes: 18 additions & 0 deletions spec/defines/config_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require 'spec_helper'
require File.expand_path(File.join(File.dirname(__FILE__), 'manifest_shared_examples.rb'))

RSpec.describe 'sqlserver::config', :type => :define do
let(:title) { 'MSSQLSERVER' }
let(:params) { {
:instance_name => 'MSSQLSERVER',
:admin_user => 'sa',
:admin_pass => 'Pupp3t1@',
} }
let(:facts) { {:osfamily => 'windows', :platform => :windows} }
describe 'compile' do
it {
should contain_file('C:/Program Files/Microsoft SQL Server/.puppet/.MSSQLSERVER.cfg')
should contain_file('C:/Program Files/Microsoft SQL Server/.puppet')
}
end
end
7 changes: 6 additions & 1 deletion spec/defines/database_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
end

describe 'Minimal Params' do
it_behaves_like 'sqlserver_tsql command'
let(:pre_condition) { <<-EOF
define sqlserver::config{}
sqlserver::config {'MSSQLSERVER': }
EOF
}
it_behaves_like 'compile'
end

describe 'Providing log filespec it should compile with valid log on params and' do
Expand Down
12 changes: 11 additions & 1 deletion spec/defines/manifest_shared_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def convert_to_regexp(str)
}

end

shared_examples 'sqlserver_tsql without_command' do
it {
params.merge!(additional_params)
Expand All @@ -50,16 +51,25 @@ def convert_to_regexp(str)
end
}
end

shared_examples 'compile' do
it {
params.merge!(additional_params)
should compile
}
end

shared_examples 'validation error' do
it {
params.merge!(additional_params)
expect { should compile }.to raise_error(error_class, convert_to_regexp(raise_error_check))
expect { should contain_sqlserver_tsql(sqlserver_tsql_title) }.to raise_error(error_class, convert_to_regexp(raise_error_check))
}
end
end

def random_string_of_size(size, include_numeric = true)
pool = [('a'..'z'), ('A'..'Z')]
pool << (0..9) if include_numeric
o = pool.map { |i| i.to_a }.flatten
(0...size).map { o[rand(o.length)] }.join
end
140 changes: 140 additions & 0 deletions spec/defines/user/permission_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
require 'spec_helper'
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'manifest_shared_examples.rb'))

describe 'sqlserver::user::permission' do
let(:facts) { {:osfamily => 'windows'} }
context 'validation errors' do
include_context 'manifests' do
let(:title) { 'myTitle' }
let(:sqlserver_tsql_title) { 'user-permissions-MSSQLSERVER-loggingDb-loggingUser-GRANT-SELECT' }
end
context 'user =>' do
let(:params) { {
:permission => 'SELECT',
:database => 'loggingDb',
} }
let(:raise_error_check) { 'User must be between 1 and 128 characters' }
describe 'missing' do
let(:raise_error_check) { 'Must pass user to Sqlserver::User::Permission[myTitle]' }
it_behaves_like 'validation error'
end
describe 'empty' do
let(:additional_params) { {:user => ''} }
it_behaves_like 'validation error'
end
describe 'over limit' do
let(:additional_params) { {:user => random_string_of_size(129)} }
end
end
context 'permission' do
let(:params) { {
:user => 'loggingUser',
:database => 'loggingDb',
} }
let(:raise_error_check) { 'Permission must be between 4 and 128 characters' }
describe 'empty' do
let(:additional_params) { {:permission => ''} }
it_behaves_like 'validation error'
end
describe 'under limit' do
let(:additional_params) { {:permission => random_string_of_size(3, false)} }
it_behaves_like 'validation error'
end
describe 'over limit' do
let(:additional_params) { {:permission => random_string_of_size(129, false)} }
it_behaves_like 'validation error'
end
end
context 'state =>' do
let(:params) { {
:permission => 'SELECT',
:database => 'loggingDb',
:user => 'loggingUser'
} }
describe 'invalid' do
let(:additional_params) { {:state => 'invalide'} }
let(:raise_error_check) { "State can only be of 'GRANT', 'REVOKE' or 'DENY' you passed invalide" }
it_behaves_like 'validation error'
end
end
end
context 'successfully' do
include_context 'manifests' do
let(:title) { 'myTitle' }
let(:sqlserver_tsql_title) { 'user-permissions-MSSQLSERVER-loggingDb-loggingUser-GRANT-SELECT' }
let(:params) { {
:user => 'loggingUser',
:permission => 'SELECT',
:database => 'loggingDb',
} }
end
%w(revoke grant deny).each do |state|
context "state => '#{state}'" do
let(:sqlserver_tsql_title) { "user-permissions-MSSQLSERVER-loggingDb-loggingUser-#{state.upcase}-SELECT" }
let(:should_contain_command) { ["#{state.upcase} SELECT TO [loggingUser];", 'USE [loggingDb];'] }
describe "lowercase #{state}" do
let(:additional_params) { {:state => state} }
it_behaves_like 'sqlserver_tsql command'
end
state.capitalize!
describe "capitalized #{state}" do
let(:additional_params) { {:state => state} }
it_behaves_like 'sqlserver_tsql command'
end
end
end

context 'permission' do
describe 'upper limit' do
permission =random_string_of_size(128, false)
let(:additional_params) { {:permission => permission} }
let(:sqlserver_tsql_title) { "user-permissions-MSSQLSERVER-loggingDb-loggingUser-GRANT-#{permission.upcase}" }
let(:should_contain_command) { ['USE [loggingDb];'] }
it_behaves_like 'sqlserver_tsql command'
end
describe 'alter' do
let(:additional_params) { {:permission => 'ALTER'} }
let(:should_contain_command) { ['USE [loggingDb];', 'GRANT ALTER TO [loggingUser];'] }
let(:sqlserver_tsql_title) { "user-permissions-MSSQLSERVER-loggingDb-loggingUser-GRANT-ALTER" }
it_behaves_like 'sqlserver_tsql command'
end
end

describe 'Minimal Params' do
let(:pre_condition) { <<-EOF
define sqlserver::config{}
sqlserver::config {'MSSQLSERVER': }
EOF
}
let(:should_contain_command) { ['USE [loggingDb];'] }
it_behaves_like 'compile'
end

end

context 'command syntax' do
include_context 'manifests' do
let(:title) { 'myTitle' }
let(:sqlserver_tsql_title) { 'user-permissions-MSSQLSERVER-loggingDb-loggingUser-GRANT-SELECT' }
let(:params) { {
:user => 'loggingUser',
:permission => 'SELECT',
:database => 'loggingDb',
} }
describe '' do
let(:should_contain_command) { [
'USE [loggingDb];',
'GRANT SELECT TO [loggingUser];',
/DECLARE @perm_state varchar\(250\)/,
/SET @perm_state = ISNULL\(\n\s+\(SELECT perm.state_desc FROM sys\.database_principals princ\n\s+JOIN sys\./,
/JOIN sys\.database_permissions perm ON perm\.grantee_principal_id = princ.principal_id\n\s+WHERE/,
/WHERE princ\.type in \('U','S','G'\) AND name = 'loggingUser' AND permission_name = 'SELECT' \),\n\s+'REVOKE'\);/,
/DECLARE @error_msg varchar\(250\);\nSET @error_msg = 'EXPECTED user \[loggingUser\] to have permission \[SELECT\] with GRANT but got ' \+ @perm_state;/,
/IF @perm_state != 'GRANT'\n\s+THROW 51000, @error_msg, 10/
] }
it_behaves_like 'sqlserver_tsql command'
end
end
end

end
12 changes: 6 additions & 6 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
require 'simplecov'
require 'rspec'

require 'rspec-puppet'
require 'puppetlabs_spec_helper/module_spec_helper'

dir = File.expand_path(File.dirname(__FILE__))
$LOAD_PATH.unshift File.join(dir, 'lib')
module PuppetSpec
FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures") unless defined?(FIXTURE_DIR)
fixture_path = File.expand_path(File.join(__FILE__, '..', 'fixtures'))

RSpec.configure do |c|
c.module_path = File.join(fixture_path, 'modules')
c.manifest_dir = File.join(fixture_path, 'manifests')
end

SimpleCov.start do
Expand Down
7 changes: 7 additions & 0 deletions templates/create/user_permission.sql.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
BEGIN
USE [<%= @database %>];
<%= @_state %> <%= @_permission %> TO [<%= @user %>];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How specific are we setting permissions? This doesn't appear specific enough to pass a security audit/SOx review. Perms at db level may be considered too broad. At least consider also adding ability to go down to object level.

END
BEGIN
<%= scope.function_template(['sqlserver/query/user_permission_exists.sql.erb']) %>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. I wouldn't have thought in module you would need to namespace it. TIL

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe when calling from another template you have to. Inside a Puppet Policy file however that might be different.

END
11 changes: 11 additions & 0 deletions templates/query/user_permission_exists.sql.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
USE [<%= @database %>];
DECLARE @perm_state varchar(250);
SET @perm_state = ISNULL(
(SELECT perm.state_desc FROM sys.database_principals princ
JOIN sys.database_permissions perm ON perm.grantee_principal_id = princ.principal_id
WHERE princ.type in ('U','S','G') AND name = '<%= @user %>' AND permission_name = '<%= @_permission %>' ),
'REVOKE');
DECLARE @error_msg varchar(250);
SET @error_msg = 'EXPECTED user [<%= @user %>] to have permission [<%= @_permission %>] with <%= @_state %> but got ' + @perm_state;
IF @perm_state != '<%= @_state %>'
THROW 51000, @error_msg, 10