diff options
-rw-r--r-- | lib/chef/resource.rb | 2 | ||||
-rw-r--r-- | lib/chef/resource/windows_user_privilege.rb | 147 | ||||
-rw-r--r-- | lib/chef/resources.rb | 1 | ||||
-rw-r--r-- | spec/functional/resource/windows_user_privilege_spec.rb | 129 |
4 files changed, 278 insertions, 1 deletions
diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index ca1736aef9..19c6c2e59f 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -588,7 +588,7 @@ class Chef begin return if should_skip?(action) - provider_for_action(action).run_action + provider_for_action(action).run_action(action) rescue StandardError => e if ignore_failure logger.error("#{custom_exception_message(e)}; ignore_failure is set, continuing") diff --git a/lib/chef/resource/windows_user_privilege.rb b/lib/chef/resource/windows_user_privilege.rb new file mode 100644 index 0000000000..ca010210c6 --- /dev/null +++ b/lib/chef/resource/windows_user_privilege.rb @@ -0,0 +1,147 @@ +# +# Author:: Jared Kauppila (<jared@kauppi.la>) +# Author:: Vasundhara Jagdale(<vasundhara.jagdale@chef.io>) +# Copyright 2008-2019, Chef Software, Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require_relative "../resource" + +class Chef + class Resource + class WindowsUserPrivilege < Chef::Resource + privilege_opts = %w{SeTrustedCredManAccessPrivilege + SeNetworkLogonRight + SeTcbPrivilege + SeMachineAccountPrivilege + SeIncreaseQuotaPrivilege + SeInteractiveLogonRight + SeRemoteInteractiveLogonRight + SeBackupPrivilege + SeChangeNotifyPrivilege + SeSystemtimePrivilege + SeTimeZonePrivilege + SeCreatePagefilePrivilege + SeCreateTokenPrivilege + SeCreateGlobalPrivilege + SeCreatePermanentPrivilege + SeCreateSymbolicLinkPrivilege + SeDebugPrivilege + SeDenyNetworkLogonRight + SeDenyBatchLogonRight + SeDenyServiceLogonRight + SeDenyInteractiveLogonRight + SeDenyRemoteInteractiveLogonRight + SeEnableDelegationPrivilege + SeRemoteShutdownPrivilege + SeAuditPrivilege + SeImpersonatePrivilege + SeIncreaseWorkingSetPrivilege + SeIncreaseBasePriorityPrivilege + SeLoadDriverPrivilege + SeLockMemoryPrivilege + SeBatchLogonRight + SeServiceLogonRight + SeSecurityPrivilege + SeRelabelPrivilege + SeSystemEnvironmentPrivilege + SeManageVolumePrivilege + SeProfileSingleProcessPrivilege + SeSystemProfilePrivilege + SeUndockPrivilege + SeAssignPrimaryTokenPrivilege + SeRestorePrivilege + SeShutdownPrivilege + SeSyncAgentPrivilege + SeTakeOwnershipPrivilege + } + + resource_name :windows_user_privilege + description "The windows_user_privilege resource allows to add and set principal (User/Group) to the specified privilege. \n Ref: https://docs.microsoft.com/en-us/windows/security/threat-protection/security-policy-settings/user-rights-assignment" + + property :principal, String, + description: "An optional property to add the user to the given privilege. Use only with add and remove action.", + name_property: true + + property :users, Array, + description: "An optional property to set the privilege for given users. Use only with set action." + + property :privilege, [Array, String], + description: "Privilege to set for users.", + required: true, + callbacks: { + "Option privilege must include any of the: #{privilege_opts}" => lambda { + |v| v.is_a?(Array) ? (privilege_opts & v).size == v.size : privilege_opts.include?(v) + }, + } + + property :sensitive, [true, false], default: true + + load_current_value do |new_resource| + unless new_resource.principal.nil? + privilege Chef::ReservedNames::Win32::Security.get_account_right(new_resource.principal) unless new_resource.action.include?(:set) + end + end + + action :add do + ([*new_resource.privilege] - [*current_resource.privilege]).each do |user_right| + converge_by("adding user privilege #{user_right}") do + Chef::ReservedNames::Win32::Security.add_account_right(new_resource.principal, user_right) + end + end + end + + action :set do + uras = new_resource.privilege + + if new_resource.users.nil? || new_resource.users.empty? + raise Chef::Exceptions::ValidationFailed, "Users are required property with set action." + end + + if powershell_out!("Get-PackageSource -Name PSGallery").stdout.empty? || powershell_out!("(Get-Package -Name cSecurityOptions -WarningAction SilentlyContinue).name").stdout.empty? + raise "This resource needs Powershell module cSecurityOptions to be installed. \n Please install it and then re-run the recipe. \n https://www.powershellgallery.com/packages/cSecurityOptions/3.1.3" + end + + uras.each do |ura| + dsc_resource "URA" do + module_name "cSecurityOptions" + resource :UserRightsAssignment + property :Ensure, "Present" + property :Privilege, ura + property :Identity, new_resource.users + sensitive new_resource.sensitive + end + end + end + + action :remove do + curr_res_privilege = current_resource.privilege + new_res_privilege = new_resource.privilege + + new_res_privilege = [] << new_res_privilege if new_resource.privilege.is_a?(String) + missing_res_privileges = (new_res_privilege - curr_res_privilege) + + unless missing_res_privileges.empty? + Chef::Log.info("Privilege: #{missing_res_privileges.join(", ")} not present. Unable to delete") + end + + (new_res_privilege - missing_res_privileges).each do |user_right| + converge_by("removing user privilege #{user_right}") do + Chef::ReservedNames::Win32::Security.remove_account_right(new_resource.principal, user_right) + end + end + end + end + end +end diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb index 3681d9bd54..8bdd207e84 100644 --- a/lib/chef/resources.rb +++ b/lib/chef/resources.rb @@ -158,3 +158,4 @@ require_relative "resource/windows_task" require_relative "resource/windows_uac" require_relative "resource/windows_workgroup" require_relative "resource/timezone" +require_relative "resource/windows_user_privilege" diff --git a/spec/functional/resource/windows_user_privilege_spec.rb b/spec/functional/resource/windows_user_privilege_spec.rb new file mode 100644 index 0000000000..4f67032ff0 --- /dev/null +++ b/spec/functional/resource/windows_user_privilege_spec.rb @@ -0,0 +1,129 @@ +# +# Author:: Vasundhara Jagdale (<vasundhara.jagdale@chef.io>) +# Copyright 2008-2019, Chef Software, Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require_relative "../../spec_helper" +require_relative "../../functional/resource/base" + +describe Chef::Resource::WindowsUserPrivilege, :windows_only do + include Chef::Mixin::PowershellOut + + let(:principal) { nil } + let(:privilege) { nil } + let(:users) { nil } + let(:sensitive) { true } + + let(:windows_test_run_context) do + node = Chef::Node.new + node.consume_external_attrs(OHAI_SYSTEM.data, {}) # node[:languages][:powershell][:version] + node.automatic["os"] = "windows" + node.automatic["platform"] = "windows" + node.automatic["platform_version"] = "6.1" + node.automatic["kernel"][:machine] = :x86_64 # Only 64-bit architecture is supported + empty_events = Chef::EventDispatch::Dispatcher.new + Chef::RunContext.new(node, {}, empty_events) + end + + subject do + new_resource = Chef::Resource::WindowsUserPrivilege.new(principal, windows_test_run_context) + new_resource.privilege = privilege + new_resource.principal = principal + new_resource.users = users + new_resource + end + + describe "#add privilege" do + after { subject.run_action(:remove) } + + let(:principal) { "Administrator" } + let(:privilege) { "SeCreateSymbolicLinkPrivilege" } + + it "adds user to privilege" do + subject.run_action(:add) + expect(subject).to be_updated_by_last_action + end + + it "is idempotent" do + subject.run_action(:add) + subject.run_action(:add) + expect(subject).not_to be_updated_by_last_action + end + end + + describe "#set privilege" do + before(:all) { + powershell_out!("Uninstall-Module -Name cSecurityOptions") unless powershell_out!("(Get-Package -Name cSecurityOptions -WarningAction SilentlyContinue).name").stdout.empty? + } + + let(:principal) { "user_privilege" } + let(:users) { %w{Administrators Administrator} } + let(:privilege) { %w{SeCreateSymbolicLinkPrivilege} } + + it "raises error if cSecurityOptions is not installed." do + subject.action(:set) + expect { subject.run_action(:set) }.to raise_error(RuntimeError) + end + end + + describe "#set privilege" do + before(:all) { + powershell_out!("Install-Module -Name cSecurityOptions -Force") if powershell_out!("(Get-Package -Name cSecurityOptions -WarningAction SilentlyContinue).name").stdout.empty? + } + + after { remove_user_privilege("Administrator", subject.privilege) } + + let(:principal) { "user_privilege" } + let(:users) { %w{Administrators Administrator} } + let(:privilege) { %w{SeCreateSymbolicLinkPrivilege} } + + it "sets user to privilege" do + subject.action(:set) + subject.run_action(:set) + expect(subject).to be_updated_by_last_action + end + + it "is idempotent" do + subject.action(:set) + subject.run_action(:set) + subject.run_action(:set) + expect(subject).not_to be_updated_by_last_action + end + + it "raise error if users not provided" do + subject.users = nil + subject.action(:set) + expect { subject.run_action(:set) }.to raise_error(Chef::Exceptions::ValidationFailed) + end + end + + describe "#remove privilege" do + let(:principal) { "Administrator" } + let(:privilege) { "SeCreateSymbolicLinkPrivilege" } + + it "remove user from privilege" do + subject.run_action(:add) + subject.run_action(:remove) + expect(subject).to be_updated_by_last_action + end + end + + def remove_user_privilege(user, privilege) + subject.action(:remove) + subject.principal = user + subject.privilege = privilege + subject.run_action(:remove) + end +end |