diff --git a/Gemfile b/Gemfile index 2f7e2ff7..918691c7 100644 --- a/Gemfile +++ b/Gemfile @@ -38,7 +38,7 @@ group :development do gem "github_changelog_generator", '= 1.15.2', require: false end group :system_tests do - gem "puppet_litmus", '< 1.0.0', require: false, platforms: [:ruby, :x64_mingw] + gem "puppet_litmus", '~> 1.0', require: false, platforms: [:ruby, :x64_mingw] gem "serverspec", '~> 2.41', require: false end diff --git a/lib/puppet/functions/sqlserver/password.rb b/lib/puppet/functions/sqlserver/password.rb new file mode 100644 index 00000000..8abf0b88 --- /dev/null +++ b/lib/puppet/functions/sqlserver/password.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# This function exists for usage of a role password that is a deferred function +Puppet::Functions.create_function(:'sqlserver::password') do + dispatch :password do + optional_param 'Any', :pass + return_type 'Any' + end + + def password(pass) + pass + end +end diff --git a/manifests/login.pp b/manifests/login.pp index 7d226c39..c09abdf3 100644 --- a/manifests/login.pp +++ b/manifests/login.pp @@ -76,9 +76,21 @@ 'absent' => 'delete', } + $parameters = { + 'password' => Deferred('sqlserver::password', [$password]), + 'disabled' => $disabled, + 'login_type' => $login_type, + 'login' => $login, + 'default_language' => $default_language, + 'default_database' => $default_database, + 'check_policy' => $check_policy, + 'check_expiration' => $check_expiration, + 'svrroles' => $svrroles, + } + sqlserver_tsql { "login-${instance}-${login}": instance => $instance, - command => template("sqlserver/${_create_delete}/login.sql.erb"), + command => stdlib::deferrable_epp("sqlserver/${_create_delete}/login.sql.epp", $parameters), onlyif => template('sqlserver/query/login_exists.sql.erb'), require => Sqlserver::Config[$instance], } diff --git a/manifests/user.pp b/manifests/user.pp index fbdbec5b..50974b0d 100644 --- a/manifests/user.pp +++ b/manifests/user.pp @@ -62,9 +62,17 @@ 'absent' => 'delete', } + $parameters = { + 'password' => Deferred('sqlserver::password', [$password]), + 'database' => $database, + 'user' => $user, + 'login' => $login, + 'default_schema' => $default_schema, + } + sqlserver_tsql { "user-${instance}-${database}-${user}": instance => $instance, - command => template("sqlserver/${create_delete}/user.sql.erb"), + command => stdlib::deferrable_epp("sqlserver/${create_delete}/user.sql.epp", $parameters), onlyif => template('sqlserver/query/user_exists.sql.erb'), require => Sqlserver::Config[$instance], } diff --git a/metadata.json b/metadata.json index c4209d67..789760c1 100644 --- a/metadata.json +++ b/metadata.json @@ -10,7 +10,7 @@ "dependencies": [ { "name": "puppetlabs/stdlib", - "version_requirement": ">= 4.13.1 < 9.0.0" + "version_requirement": ">= 8.4.0 < 9.0.0" }, { "name": "puppetlabs/powershell", diff --git a/spec/functions/password_spec.rb b/spec/functions/password_spec.rb new file mode 100644 index 00000000..54010644 --- /dev/null +++ b/spec/functions/password_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'sqlserver::password' do + it { is_expected.to run.with_params('password').and_return('password') } + it { is_expected.to run.with_params(nil).and_return(nil) } +end diff --git a/templates/create/login.sql.epp b/templates/create/login.sql.epp new file mode 100644 index 00000000..a7f93c49 --- /dev/null +++ b/templates/create/login.sql.epp @@ -0,0 +1,58 @@ +DECLARE + @is_disabled as tinyint = <%= if $disabled {1} else {0} %>, + @login_type as varchar(255) = NULL; + +SET @login_type = (SELECT [type] FROM sys.server_principals where name = '<%= $login %>') +IF (@login_type IS NULL) +BEGIN + -- Create the login + CREATE LOGIN [<%= $login %>] + <% if $login_type !~ /WINDOWS_LOGIN/ { -%> + WITH + PASSWORD = '<%= $password %>', + CHECK_EXPIRATION = <% if $check_expiration { %>ON<% } else { %>OFF<% } %>, + CHECK_POLICY = <% if $check_policy { %>ON<% } else { %>OFF<% } %>, + <% } else { -%> + FROM WINDOWS WITH + <% } -%> + DEFAULT_LANGUAGE = [<%= $default_language %>], + DEFAULT_DATABASE = [<%= $default_database %>]; + -- Fetch the login type + SET @login_type = (SELECT [type] FROM sys.server_principals where name = '<%= $login %>') +END + +IF (@login_type = 'G') +BEGIN + -- Windows Group type logins can only be granted/denied connection + IF @is_disabled = 0 GRANT CONNECT SQL TO [<%= $login %>] + ELSE DENY CONNECT SQL TO [<%= $login %>] +END +ELSE +BEGIN + IF @is_disabled = 0 ALTER LOGIN [<%= $login %>] ENABLE + ELSE ALTER LOGIN [<%= $login %>] DISABLE +END + +ALTER LOGIN [<%= $login %>] WITH +<% if $login_type != 'WINDOWS_LOGIN' { -%> + CHECK_EXPIRATION = <% if $check_expiration { %>ON<% } else { %>OFF<% } %>, + CHECK_POLICY = <% if $check_policy { %>ON<% } else { %>OFF<% } %>, +<% } -%> + DEFAULT_LANGUAGE = [<%= $default_language %>], + DEFAULT_DATABASE = [<%= $default_database %>]; + +<% $svrroles.each |String $role, Any $enable_bit| { -%> +IF (SELECT COUNT(me.role_principal_id) from sys.server_role_members me + JOIN sys.server_principals rol ON me.role_principal_id = rol.principal_id + JOIN sys.server_principals pri ON me.member_principal_id = pri.principal_id + WHERE rol.type_desc = 'SERVER_ROLE' + AND rol.name = '<%= $role %>' + AND pri.name = '<%= $login %>') != <%= $enable_bit %> +BEGIN + <% if ($enable_bit == '1') or ($enable_bit == 1) { -%> + ALTER SERVER ROLE [<%= $role %>] ADD MEMBER [<%= $login %>]; + <% } else { -%> + ALTER SERVER ROLE [<%= $role %>] DROP MEMBER [<%= $login %>]; + <% } -%> +END +<% } -%> diff --git a/templates/create/login.sql.erb b/templates/create/login.sql.erb deleted file mode 100644 index 58a3a842..00000000 --- a/templates/create/login.sql.erb +++ /dev/null @@ -1,58 +0,0 @@ -DECLARE - @is_disabled as tinyint = <%= @disabled ? 1 : 0 %>, - @login_type as varchar(255) = NULL; - -SET @login_type = (SELECT [type] FROM sys.server_principals where name = '<%= @login %>') -IF (@login_type IS NULL) -BEGIN - -- Create the login - CREATE LOGIN [<%= @login %>] - <% if @login_type !~ /WINDOWS_LOGIN/i -%> - WITH - PASSWORD = '<%= @password %>', - CHECK_EXPIRATION = <% if @check_expiration %>ON<% else %>OFF<% end %>, - CHECK_POLICY = <% if @check_policy %>ON<% else %>OFF<% end %>, - <% else -%> - FROM WINDOWS WITH - <% end -%> - DEFAULT_LANGUAGE = [<%= @default_language %>], - DEFAULT_DATABASE = [<%= @default_database %>]; - -- Fetch the login type - SET @login_type = (SELECT [type] FROM sys.server_principals where name = '<%= @login %>') -END - -IF (@login_type = 'G') -BEGIN - -- Windows Group type logins can only be granted/denied connection - IF @is_disabled = 0 GRANT CONNECT SQL TO [<%= @login %>] - ELSE DENY CONNECT SQL TO [<%= @login %>] -END -ELSE -BEGIN - IF @is_disabled = 0 ALTER LOGIN [<%= @login %>] ENABLE - ELSE ALTER LOGIN [<%= @login %>] DISABLE -END - -ALTER LOGIN [<%= @login %>] WITH -<% if @login_type != 'WINDOWS_LOGIN' -%> - CHECK_EXPIRATION = <% if @check_expiration %>ON<% else %>OFF<% end %>, - CHECK_POLICY = <% if @check_policy %>ON<% else %>OFF<% end %>, -<% end -%> - DEFAULT_LANGUAGE = [<%= @default_language %>], - DEFAULT_DATABASE = [<%= @default_database %>]; - -<% @svrroles.each do |role, enable_bit| -%> -IF (SELECT COUNT(me.role_principal_id) from sys.server_role_members me - JOIN sys.server_principals rol ON me.role_principal_id = rol.principal_id - JOIN sys.server_principals pri ON me.member_principal_id = pri.principal_id - WHERE rol.type_desc = 'SERVER_ROLE' - AND rol.name = '<%= role %>' - AND pri.name = '<%= @login %>') != <%= enable_bit %> -BEGIN - <% if enable_bit == '1' || enable_bit == 1 -%> - ALTER SERVER ROLE [<%= role %>] ADD MEMBER [<%= @login %>]; - <% else -%> - ALTER SERVER ROLE [<%= role %>] DROP MEMBER [<%= @login %>]; - <% end -%> -END -<% end -%> diff --git a/templates/create/user.sql.epp b/templates/create/user.sql.epp new file mode 100644 index 00000000..d813b30c --- /dev/null +++ b/templates/create/user.sql.epp @@ -0,0 +1,18 @@ +USE [<%= $database %>]; +<% if $password { %> + IF EXISTS(select containment from sys.databases WHERE name = '<%= $database %>' AND containment = 0) + THROW 51000, 'Database must be contained in order to use passwords', 10 +<% } %> +CREATE USER [<%= $user %>] +<% if $login { -%> + FROM LOGIN [<%= $login %>] +<% } else { -%> + <% if $password { -%> + WITH PASSWORD = '<%= $password %>' + <% } -%> +<% } -%> +<% if $default_schema { -%> + <% if $password { -%>,<% } else { -%> + WITH <% } -%> + DEFAULT_SCHEMA = <%= $default_schema %> +<% } -%> diff --git a/templates/create/user.sql.erb b/templates/create/user.sql.erb deleted file mode 100644 index b92616e3..00000000 --- a/templates/create/user.sql.erb +++ /dev/null @@ -1,18 +0,0 @@ -USE [<%= @database %>]; -<% if @password %> - IF EXISTS(select containment from sys.databases WHERE name = '<%= @database %>' AND containment = 0) - THROW 51000, 'Database must be contained in order to use passwords', 10 -<% end %> -CREATE USER [<%= @user %>] -<% if @login -%> - FROM LOGIN [<%= @login %>] -<% else -%> - <% if @password -%> - WITH PASSWORD = '<%= @password %>' - <% end -%> -<% end -%> -<% if @default_schema -%> - <% if @password -%>,<% else -%> - WITH <% end -%> - DEFAULT_SCHEMA = <%= @default_schema %> -<% end -%> diff --git a/templates/delete/login.sql.erb b/templates/delete/login.sql.epp similarity index 85% rename from templates/delete/login.sql.erb rename to templates/delete/login.sql.epp index df8edac4..d91b00c2 100644 --- a/templates/delete/login.sql.erb +++ b/templates/delete/login.sql.epp @@ -1,7 +1,7 @@ USE master; -IF exists(select * from sys.server_principals where name = '<%= @login %>') +IF exists(select * from sys.server_principals where name = '<%= $login %>') BEGIN -- need to add logic to kill all possible connections if any exists, -- possible force flag to prevent from happening during transaction if user would prefer to wait - DROP LOGIN [<%= @login %>] + DROP LOGIN [<%= $login %>] END diff --git a/templates/delete/user.sql.epp b/templates/delete/user.sql.epp new file mode 100644 index 00000000..f6b4ddf6 --- /dev/null +++ b/templates/delete/user.sql.epp @@ -0,0 +1,4 @@ +USE [<%= $database %>]; +DROP USER [<%= $user %>]; +IF EXISTS(SELECT name FROM sys.database_principals WHERE name = '<%= $user %>') + THROW 51000, 'Failed to drop user <%= $user %>', 10 diff --git a/templates/delete/user.sql.erb b/templates/delete/user.sql.erb deleted file mode 100644 index fffdbe7d..00000000 --- a/templates/delete/user.sql.erb +++ /dev/null @@ -1,4 +0,0 @@ -USE [<%= @database %>]; -DROP USER [<%= @user %>]; -IF EXISTS(SELECT name FROM sys.database_principals WHERE name = '<%= @user %>') - THROW 51000, 'Failed to drop user <%= @user %>', 10