summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/chef/resource/windows_user_privilege.rb34
-rw-r--r--lib/chef/win32/api/security.rb6
-rw-r--r--lib/chef/win32/security.rb38
-rw-r--r--spec/functional/resource/windows_user_privilege_spec.rb60
-rw-r--r--spec/functional/win32/security_spec.rb21
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.