diff options
-rw-r--r-- | lib/mixlib/shellout.rb | 6 | ||||
-rw-r--r-- | lib/mixlib/shellout/unix.rb | 4 | ||||
-rw-r--r-- | lib/mixlib/shellout/windows.rb | 5 | ||||
-rw-r--r-- | lib/mixlib/shellout/windows/core_ext.rb | 105 | ||||
-rw-r--r-- | spec/mixlib/shellout_spec.rb | 8 |
5 files changed, 105 insertions, 23 deletions
diff --git a/lib/mixlib/shellout.rb b/lib/mixlib/shellout.rb index ca1cd22..76e6959 100644 --- a/lib/mixlib/shellout.rb +++ b/lib/mixlib/shellout.rb @@ -109,6 +109,9 @@ module Mixlib attr_reader :stdin_pipe, :stdout_pipe, :stderr_pipe, :process_status_pipe + # Runs windows process with elevated privileges. Required for Powershell commands which need elevated privileges + attr_accessor :elevated + # === Arguments: # Takes a single command, or a list of command fragments. These are used # as arguments to Kernel.exec. See the Kernel.exec documentation for more @@ -172,6 +175,7 @@ module Mixlib @valid_exit_codes = [0] @terminate_reason = nil @timeout = nil + @elevated = false if command_args.last.is_a?(Hash) parse_options(command_args.pop) @@ -339,6 +343,8 @@ module Mixlib end when "login" self.login = setting + when "elevated" + self.elevated = setting else raise InvalidCommandOption, "option '#{option.inspect}' is not a valid option for #{self.class.name}" end diff --git a/lib/mixlib/shellout/unix.rb b/lib/mixlib/shellout/unix.rb index 90e63e9..bc0fc69 100644 --- a/lib/mixlib/shellout/unix.rb +++ b/lib/mixlib/shellout/unix.rb @@ -27,7 +27,9 @@ module Mixlib # Option validation that is unix specific def validate_options(opts) - # No options to validate, raise exceptions here if needed + if opts[:elevated] + raise InvalidCommandOption, "Option `elevated` is supported for Powershell commands only" + end end # Whether we're simulating a login shell diff --git a/lib/mixlib/shellout/windows.rb b/lib/mixlib/shellout/windows.rb index adb6583..b586a5f 100644 --- a/lib/mixlib/shellout/windows.rb +++ b/lib/mixlib/shellout/windows.rb @@ -35,6 +35,10 @@ module Mixlib if opts[:user] && !opts[:password] raise InvalidCommandOption, "You must supply a password when supplying a user in windows" end + + if opts[:elevated] && opts[:elevated] != true && opts[:elevated] != false + raise InvalidCommandOption, "Invalid value passed for `elevated`. Please provide true/false." + end end #-- @@ -71,6 +75,7 @@ module Mixlib create_process_args[:domain] = domain.nil? ? "." : domain create_process_args[:with_logon] = with_logon if with_logon create_process_args[:password] = password if password + create_process_args[:elevated] = elevated if elevated # # Start the process diff --git a/lib/mixlib/shellout/windows/core_ext.rb b/lib/mixlib/shellout/windows/core_ext.rb index 17faff2..5f78353 100644 --- a/lib/mixlib/shellout/windows/core_ext.rb +++ b/lib/mixlib/shellout/windows/core_ext.rb @@ -24,7 +24,8 @@ module Process::Constants private LOGON32_LOGON_INTERACTIVE = 0x00000002 - LOGON32_PROVIDER_DEFAULT = 0x00000000 + LOGON32_LOGON_BATCH = 0x00000004 + LOGON32_PROVIDER_DEFAULT = 0x00000000 UOI_NAME = 0x00000002 WAIT_OBJECT_0 = 0 @@ -32,6 +33,9 @@ module Process::Constants WAIT_ABANDONED = 128 WAIT_ABANDONED_0 = WAIT_ABANDONED WAIT_FAILED = 0xFFFFFFFF + + ERROR_PRIVILEGE_NOT_HELD = 1314 + ERROR_LOGON_TYPE_NOT_GRANTED = 0X569 end # Define the functions needed to check with Service windows station @@ -71,7 +75,7 @@ module Process valid_keys = %w{ app_name command_line inherit creation_flags cwd environment startup_info thread_inherit process_inherit close_handles with_logon - domain password + domain password elevated } valid_si_keys = %w{ @@ -284,7 +288,11 @@ module Process ) unless bool - raise SystemCallError.new("LogonUserW", FFI.errno) + if FFI.errno == ERROR_LOGON_TYPE_NOT_GRANTED + raise SystemCallError.new("LogonUserW (You must hold `Log on as a service` and `Log on as a batch job` permissions.)", FFI.errno) + else + raise SystemCallError.new("LogonUserW", FFI.errno) + end end token = token.read_ulong @@ -303,30 +311,83 @@ module Process startinfo, # Startup Info procinfo # Process Info ) + + unless bool + if FFI.errno == ERROR_PRIVILEGE_NOT_HELD + raise SystemCallError.new("CreateProcessAsUserW (You must hold the 'Replace a process level token' permission. Refresh the logon context after adding this right to make it effective.)", FFI.errno) + else + raise SystemCallError.new("CreateProcessAsUserW failed.", FFI.errno) + end + end ensure CloseHandle(token) end - - unless bool - raise SystemCallError.new("CreateProcessAsUserW (You must hold the 'Replace a process level token' permission)", FFI.errno) - end else - bool = CreateProcessWithLogonW( - logon, # User - domain, # Domain - passwd, # Password - LOGON_WITH_PROFILE, # Logon flags - app, # App name - cmd, # Command line - hash["creation_flags"], # Creation flags - env, # Environment - cwd, # Working directory - startinfo, # Startup Info - procinfo # Process Info - ) + if hash["elevated"] + token = FFI::MemoryPointer.new(:ulong) + + bool = LogonUserW( + logon, # User + domain, # Domain + passwd, # Password + LOGON32_LOGON_BATCH, # Logon Type + LOGON32_PROVIDER_DEFAULT, # Logon Provider + token # User token handle + ) - unless bool - raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno) + unless bool + if FFI.errno == ERROR_LOGON_TYPE_NOT_GRANTED + raise SystemCallError.new("LogonUserW (You must hold `Log on as a service` and `Log on as a batch job` permissions.)", FFI.errno) + else + raise SystemCallError.new("LogonUserW", FFI.errno) + end + end + + token = token.read_ulong + + begin + bool = CreateProcessAsUserW( + token, # User token handle + app, # App name + cmd, # Command line + process_security, # Process attributes + thread_security, # Thread attributes + inherit, # Inherit handles + hash["creation_flags"], # Creation Flags + env, # Environment + cwd, # Working directory + startinfo, # Startup Info + procinfo # Process Info + ) + + unless bool + if FFI.errno == ERROR_PRIVILEGE_NOT_HELD + raise SystemCallError.new("CreateProcessAsUserW (You must hold the 'Replace a process level token' permission. Refresh the logon context after adding this right to make it effective.)", FFI.errno) + else + raise SystemCallError.new("CreateProcessAsUserW failed.", FFI.errno) + end + end + ensure + CloseHandle(token) + end + else + bool = CreateProcessWithLogonW( + logon, # User + domain, # Domain + passwd, # Password + LOGON_WITH_PROFILE, # Logon flags + app, # App name + cmd, # Command line + hash["creation_flags"], # Creation flags + env, # Environment + cwd, # Working directory + startinfo, # Startup Info + procinfo # Process Info + ) + + unless bool + raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno) + end end end else diff --git a/spec/mixlib/shellout_spec.rb b/spec/mixlib/shellout_spec.rb index a55b3c0..15bfa39 100644 --- a/spec/mixlib/shellout_spec.rb +++ b/spec/mixlib/shellout_spec.rb @@ -655,6 +655,14 @@ describe Mixlib::ShellOut do it "should run as specified user" do expect(running_user).to eql("#{ENV['COMPUTERNAME'].downcase}\\#{user}") end + + context "when :elevated => true" do + let(:options) { { :user => user, :password => password, :elevated => true } } + + it "raises error" do + expect { running_user }.to raise_error("Logon failure: the user has not been granted the requested logon type at this computer. - LogonUserW (You must hold `Log on as a service` and `Log on as a batch job` permissions.)") + end + end end end |