diff options
-rw-r--r-- | lib/chef/resource/windows_user_privilege.rb | 34 | ||||
-rw-r--r-- | lib/chef/win32/api/security.rb | 6 | ||||
-rw-r--r-- | lib/chef/win32/security.rb | 38 | ||||
-rw-r--r-- | spec/functional/resource/windows_user_privilege_spec.rb | 60 | ||||
-rw-r--r-- | spec/functional/win32/security_spec.rb | 21 |
5 files changed, 126 insertions, 33 deletions
diff --git a/lib/chef/resource/windows_user_privilege.rb b/lib/chef/resource/windows_user_privilege.rb index b1955bd43b..1a162457ec 100644 --- a/lib/chef/resource/windows_user_privilege.rb +++ b/lib/chef/resource/windows_user_privilege.rb @@ -105,24 +105,34 @@ class Chef 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_exec("(Get-PackageSource -Name PSGallery).name").result.empty? || powershell_exec("(Get-Package -Name cSecurityOptions -WarningAction SilentlyContinue).name").result.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" + users = [] + + # Getting users with its domain for comparison + new_resource.users.each do |user| + user = Chef::ReservedNames::Win32::Security.lookup_account_name(user) + users << user[1].account if user 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 + new_resource.privilege.each do |privilege| + accounts = Chef::ReservedNames::Win32::Security.get_account_with_user_rights(privilege) + + # comparing the existing accounts for privilege with users + unless users == accounts + accounts.each do |account| + converge_by("removing user #{account[1]} from privilege #{privilege}") do + Chef::ReservedNames::Win32::Security.remove_account_right(account[1], privilege) + end + end + + new_resource.users.each do |user| + converge_by("adding user #{user} to privilege #{privilege}") do + Chef::ReservedNames::Win32::Security.add_account_right(user, privilege) + end + end end end end diff --git a/lib/chef/win32/api/security.rb b/lib/chef/win32/api/security.rb index b651283758..16671a9f6d 100644 --- a/lib/chef/win32/api/security.rb +++ b/lib/chef/win32/api/security.rb @@ -413,6 +413,11 @@ class Chef :Buffer, :PWSTR end + # https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/ns-ntsecapi-lsa_enumeration_information + class LSA_ENUMERATION_INFORMATION < FFI::Struct + layout :Sid, :PSID + end + ffi_lib "advapi32" safe_attach_function :AccessCheck, %i{pointer HANDLE DWORD pointer pointer pointer pointer pointer}, :BOOL @@ -448,6 +453,7 @@ class Chef safe_attach_function :LookupPrivilegeDisplayNameW, %i{LPCWSTR LPCWSTR LPWSTR LPDWORD LPDWORD}, :BOOL safe_attach_function :LookupPrivilegeValueW, %i{LPCWSTR LPCWSTR PLUID}, :BOOL safe_attach_function :LsaAddAccountRights, %i{pointer pointer pointer ULONG}, :NTSTATUS + safe_attach_function :LsaEnumerateAccountsWithUserRight, %i{LSA_HANDLE PLSA_UNICODE_STRING PVOID PULONG}, :NTSTATUS safe_attach_function :LsaRemoveAccountRights, %i{pointer pointer BOOL pointer ULONG}, :NTSTATUS safe_attach_function :LsaClose, [ :LSA_HANDLE ], :NTSTATUS safe_attach_function :LsaEnumerateAccountRights, %i{LSA_HANDLE PSID PLSA_UNICODE_STRING PULONG}, :NTSTATUS diff --git a/lib/chef/win32/security.rb b/lib/chef/win32/security.rb index 5b78b652eb..2879131210 100644 --- a/lib/chef/win32/security.rb +++ b/lib/chef/win32/security.rb @@ -214,6 +214,37 @@ class Chef privileges end + def self.get_account_with_user_rights(privilege) + privilege_pointer = FFI::MemoryPointer.new LSA_UNICODE_STRING, 1 + privilege_lsa_string = LSA_UNICODE_STRING.new(privilege_pointer) + privilege_lsa_string[:Buffer] = FFI::MemoryPointer.from_string(privilege.to_wstring) + privilege_lsa_string[:Length] = privilege.length * 2 + privilege_lsa_string[:MaximumLength] = (privilege.length + 1) * 2 + + buffer = FFI::MemoryPointer.new(:pointer) + count = FFI::MemoryPointer.new(:ulong) + + accounts = [] + with_lsa_policy(nil) do |policy_handle, sid| + result = LsaEnumerateAccountsWithUserRight(policy_handle.read_pointer, privilege_pointer, buffer, count) + win32_error = LsaNtStatusToWinError(result) + return [] if win32_error == 1313 # NO_SUCH_PRIVILEGE - https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--1300-1699- + + test_and_raise_lsa_nt_status(result) + + count.read_ulong.times do |i| + sid = LSA_ENUMERATION_INFORMATION.new(buffer.read_pointer + i * LSA_ENUMERATION_INFORMATION.size) + sid_name = lookup_account_sid(sid[:Sid]) + accounts << sid_name + end + + result = LsaFreeMemory(buffer.read_pointer) + test_and_raise_lsa_nt_status(result) + end + + accounts + end + def self.get_ace(acl, index) acl = acl.pointer if acl.respond_to?(:pointer) ace = FFI::Buffer.new :pointer @@ -616,18 +647,21 @@ class Chef end def self.with_lsa_policy(username) - sid = lookup_account_name(username)[1] + sid = lookup_account_name(username)[1] if username access = 0 access |= POLICY_CREATE_ACCOUNT access |= POLICY_LOOKUP_NAMES + access |= POLICY_VIEW_LOCAL_INFORMATION if username.nil? policy_handle = FFI::MemoryPointer.new(:pointer) result = LsaOpenPolicy(nil, LSA_OBJECT_ATTRIBUTES.new, access, policy_handle) test_and_raise_lsa_nt_status(result) + sid_pointer = username.nil? ? nil : sid.pointer + begin - yield policy_handle, sid.pointer + yield policy_handle, sid_pointer ensure result = LsaClose(policy_handle.read_pointer) test_and_raise_lsa_nt_status(result) diff --git a/spec/functional/resource/windows_user_privilege_spec.rb b/spec/functional/resource/windows_user_privilege_spec.rb index cf1320e12a..fa134b4fe7 100644 --- a/spec/functional/resource/windows_user_privilege_spec.rb +++ b/spec/functional/resource/windows_user_privilege_spec.rb @@ -64,25 +64,6 @@ describe Chef::Resource::WindowsUserPrivilege, :windows_only do end describe "#set privilege" do - before(:all) { - powershell_exec("Uninstall-Module -Name cSecurityOptions") unless powershell_exec("(Get-Package -Name cSecurityOptions -WarningAction SilentlyContinue).name").result.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_exec("Install-Module -Name cSecurityOptions -Force") if powershell_exec("(Get-Package -Name cSecurityOptions -WarningAction SilentlyContinue).name").result.empty? - } - after { remove_user_privilege("Administrator", subject.privilege) } let(:principal) { "user_privilege" } @@ -120,6 +101,47 @@ describe Chef::Resource::WindowsUserPrivilege, :windows_only do end end + describe "running with non admin user" do + include Chef::Mixin::UserContext + + let(:user) { "security_user" } + let(:password) { "Security@123" } + let(:principal) { "user_privilege" } + let(:users) { ["Administrators", "#{domain}\\security_user"] } + let(:privilege) { %w{SeCreateSymbolicLinkPrivilege} } + + let(:domain) do + ENV["COMPUTERNAME"] + end + + before do + allow_any_instance_of(Chef::Mixin::UserContext).to receive(:node).and_return({ "platform_family" => "windows" }) + add_user = Mixlib::ShellOut.new("net user #{user} #{password} /ADD") + add_user.run_command + add_user.error! + end + + after do + remove_user_privilege("#{domain}\\#{user}", subject.privilege) + delete_user = Mixlib::ShellOut.new("net user #{user} /delete") + delete_user.run_command + delete_user.error! + end + + 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 + end + def remove_user_privilege(user, privilege) subject.action(:remove) subject.principal = user diff --git a/spec/functional/win32/security_spec.rb b/spec/functional/win32/security_spec.rb index 3eb7bedd48..8caacffd2c 100644 --- a/spec/functional/win32/security_spec.rb +++ b/spec/functional/win32/security_spec.rb @@ -199,6 +199,27 @@ describe "Chef::Win32::Security", :windows_only do end end + describe ".get_account_with_user_rights" do + let(:username) { ENV["USERNAME"] } + + context "when given a valid user right" do + it "gets all accounts associated with given user right" do + Chef::ReservedNames::Win32::Security.add_account_right(username, "SeBatchLogonRight") + expect(Chef::ReservedNames::Win32::Security.get_account_with_user_rights("SeBatchLogonRight").flatten).to include(username) + Chef::ReservedNames::Win32::Security.remove_account_right(username, "SeBatchLogonRight") + expect(Chef::ReservedNames::Win32::Security.get_account_with_user_rights("SeBatchLogonRight").flatten).not_to include(username) + end + end + + context "when given an invalid user right" do + let(:user_right) { "SeTest" } + + it "returns empty array" do + expect(Chef::ReservedNames::Win32::Security.get_account_with_user_rights(user_right)).to be_empty + end + end + end + describe ".test_and_raise_lsa_nt_status" do # NTSTATUS code: 0xC0000001 / STATUS_UNSUCCESSFUL # Windows Error: ERROR_GEN_FAILURE / 31 / 0x1F / A device attached to the system is not functioning. |