winrm 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ad05b5c1fe16cfa1586a6a001022d0463e87d9cb
4
- data.tar.gz: 07a2d85d2a0a9458049eaac2b23bbd68e518ae2d
3
+ metadata.gz: bc4d15623138a5d39c308936ad8e6738621317c0
4
+ data.tar.gz: 64064ba319d3d5d856ba346a383823ccd0054f40
5
5
  SHA512:
6
- metadata.gz: c2181da8210604d6703c2f694c3388e9e8f670fba9673a3fac68b1b9d257e2f0e3412f810b3c94966a57e7035e1d1b153ee815a7b993694c71885925d6bff09e
7
- data.tar.gz: 47d0c26bfab5a7f096091bf74f042ded6a25fe59683c83829907863176f5bbb337bb6b972fcc4e2d3d5977c150c65c81413c6044a23b75787b82ae8738e1fb38
6
+ metadata.gz: dab6833383b786a008734ef928a4f7ed2346b912f6e342fde139053053bede56296e097f17726e801b5ef7d0023ba9ee8b373ff7b5d545e78564c6ff13900f94
7
+ data.tar.gz: 5c7c43892944ed073c2cc66a6bf590e4164e052c6b6d596dec684408dd11594d3da8ea275f39d724d197571ce746ce65378a2e1f9a6394bae4955f4c09ed1efd
@@ -1,7 +1,9 @@
1
1
  AllCops:
2
2
  Exclude:
3
3
  - 'appveyor.yml'
4
+ - 'scripts/**/*'
4
5
  - 'lib/winrm/winrm_service.rb'
6
+ - 'lib/winrm/http/transport.rb'
5
7
 
6
8
  Style/Encoding:
7
9
  Enabled: true
@@ -5,3 +5,8 @@ rvm:
5
5
  - 2.1.0
6
6
  before_install:
7
7
  - gem update bundler
8
+
9
+ # This prevents testing branches that are created just for PRs
10
+ branches:
11
+ only:
12
+ - master
data/README.md CHANGED
@@ -38,7 +38,13 @@ As of version 1.5.0 `WinRM::WinRMWebService` methods `cmd`, `run_cmd`, `powershe
38
38
 
39
39
  Use the `run_cmd` and `run_powershell_script` of the `WinRM::CommandExecutor` class instead. The `CommandExecutor` allows multiple commands to be run from the same WinRM shell providing a significant performance improvement when issuing multiple calls.
40
40
 
41
+ #### NTLM/Negotiate
42
+ ```ruby
43
+ winrm = WinRM::WinRMWebService.new(endpoint, :negotiate, :user => myuser, :pass => mypass)
44
+ ```
45
+
41
46
  #### Plaintext
47
+ Note: It is strongly recommended that you use `:negotiate` instead of `:plaintext`. As the name infers, the `:plaintext` transport includes authentication credentials in plain text.
42
48
  ```ruby
43
49
  WinRM::WinRMWebService.new(endpoint, :plaintext, :user => myuser, :pass => mypass, :disable_sspi => true)
44
50
 
@@ -99,7 +105,7 @@ The `WinRMWebService` exposes a `logger` attribute and uses the [logging](https:
99
105
  winrm = WinRM::WinRMWebService.new(endpoint, :ssl, :user => myuser, :pass => mypass)
100
106
 
101
107
  # suppress warnings
102
- winrm.logger.warn = :error
108
+ winrm.logger.level = :error
103
109
 
104
110
  # Log to a file
105
111
  winrm.logger.add_appenders(Logging.appenders.file('error.log'))
data/Rakefile CHANGED
@@ -8,6 +8,14 @@ require 'bundler/gem_tasks'
8
8
  # Change to the directory of this file.
9
9
  Dir.chdir(File.expand_path('../', __FILE__))
10
10
 
11
+ desc 'Open a Pry console for this library'
12
+ task :console do
13
+ require 'pry'
14
+ require 'winrm'
15
+ ARGV.clear
16
+ Pry.start
17
+ end
18
+
11
19
  RSpec::Core::RakeTask.new(:spec) do |task|
12
20
  task.pattern = 'spec/*_spec.rb'
13
21
  task.rspec_opts = ['--color', '-f documentation']
@@ -1,5 +1,11 @@
1
1
  # WinRM Gem Changelog
2
2
 
3
+ # 1.6.0
4
+ - Adding `:negotiate` transport providing NTLM/Negotiate encryption of WinRM requests and responses
5
+ - Removed dependency on UUIDTools gem
6
+ - Extending accepted error codes for retry behavior to include `Errno::ETIMEDOUT`
7
+ - Correct deprecation warning for WinRMWebService.run_powershell_script
8
+
3
9
  # 1.5.0
4
10
  - Deprecating `WinRM::WinRMWebService` methods `cmd`, `run_cmd`, `powershell`, and `run_powershell_script` in favor of the `run_cmd` and `run_powershell_script` methods of the `WinRM::CommandExecutor` class. The `CommandExecutor` allows multiple commands to be run from the same WinRM shell providing a significant performance improvement when issuing multiple calls.
5
11
  - Added an `:ssl_peer_fingerprint` option to be used instead of `:no_ssl_peer_verification` and allows a specific certificate to be verified.
@@ -226,7 +226,7 @@ module WinRM
226
226
 
227
227
  RESCUE_EXCEPTIONS_ON_ESTABLISH = lambda do
228
228
  [
229
- Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED,
229
+ Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
230
230
  Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
231
231
  ::WinRM::WinRMHTTPTransportError, ::WinRM::WinRMAuthorizationError,
232
232
  HTTPClient::KeepAliveDisconnected,
@@ -128,6 +128,7 @@ module WinRM
128
128
  end
129
129
  end
130
130
 
131
+
131
132
  # Plain text, insecure, HTTP transport
132
133
  class HttpPlaintext < HttpTransport
133
134
  def initialize(endpoint, user, pass, opts)
@@ -139,6 +140,89 @@ module WinRM
139
140
  end
140
141
  end
141
142
 
143
+
144
+ # NTLM/Negotiate, secure, HTTP transport
145
+ class HttpNegotiate < HttpTransport
146
+ def initialize(endpoint, user, pass, opts)
147
+ super(endpoint)
148
+ no_sspi_auth!
149
+
150
+ user_parts = user.split('\\')
151
+ if(user_parts.length > 1)
152
+ opts[:domain] = user_parts[0]
153
+ user = user_parts[1]
154
+ end
155
+
156
+ @ntlmcli = Net::NTLM::Client.new(user, pass, opts)
157
+ @retryable = true
158
+ no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
159
+ end
160
+
161
+ def send_request(message, auth_header = nil)
162
+ auth_header = init_auth if @ntlmcli.session.nil?
163
+
164
+ original_length = message.length
165
+
166
+ emessage = @ntlmcli.session.seal_message message
167
+ signature = @ntlmcli.session.sign_message message
168
+ seal = "\x10\x00\x00\x00#{signature}#{emessage}"
169
+
170
+ hdr = {
171
+ "Content-Type" => "multipart/encrypted;protocol=\"application/HTTP-SPNEGO-session-encrypted\";boundary=\"Encrypted Boundary\""
172
+ }
173
+ hdr.merge!(auth_header) if auth_header
174
+
175
+ body = [
176
+ "--Encrypted Boundary",
177
+ "Content-Type: application/HTTP-SPNEGO-session-encrypted",
178
+ "OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{original_length}",
179
+ "--Encrypted Boundary",
180
+ "Content-Type: application/octet-stream",
181
+ "#{seal}--Encrypted Boundary--",
182
+ ""
183
+ ].join("\r\n")
184
+
185
+ resp = @httpcli.post(@endpoint, body, hdr)
186
+ if resp.status == 401 && @retryable
187
+ @retryable = false
188
+ send_request(message, init_auth)
189
+ else
190
+ @retryable = true
191
+ decrypted_body = resp.body.empty? ? '' : winrm_decrypt(resp.body)
192
+ handler = WinRM::ResponseHandler.new(decrypted_body, resp.status)
193
+ handler.parse_to_xml()
194
+ end
195
+ end
196
+
197
+ private
198
+
199
+ def winrm_decrypt(str)
200
+ str.force_encoding('BINARY')
201
+ str.sub!(/^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$/m, '\1')
202
+
203
+ signature = str[4..19]
204
+ message = @ntlmcli.session.unseal_message str[20..-1]
205
+ if @ntlmcli.session.verify_signature(signature, message)
206
+ message
207
+ else
208
+ raise WinRMWebServiceError, "Could not verify SOAP message."
209
+ end
210
+ end
211
+
212
+ def init_auth
213
+ @logger.debug "Initializing Negotiate for #{@service}"
214
+ auth1 = @ntlmcli.init_context
215
+ hdr = {"Authorization" => "Negotiate #{auth1.encode64}",
216
+ "Content-Type" => "application/soap+xml;charset=UTF-8"
217
+ }
218
+ @logger.debug "Sending HTTP POST for Negotiate Authentication"
219
+ r = @httpcli.post(@endpoint, "", hdr)
220
+ itok = r.header["WWW-Authenticate"].pop.split.last
221
+ auth3 = @ntlmcli.init_context itok
222
+ { "Authorization" => "Negotiate #{auth3.encode64}" }
223
+ end
224
+ end
225
+
142
226
  # Uses SSL to secure the transport
143
227
  class HttpSSL < HttpTransport
144
228
  def initialize(endpoint, user, pass, ca_trust_path = nil, opts)
@@ -209,15 +293,15 @@ module WinRM
209
293
  'protocol="application/HTTP-Kerberos-session-encrypted";' \
210
294
  'boundary="Encrypted Boundary"'
211
295
  }
212
-
213
- body = <<-EOF
214
- --Encrypted Boundary\r
215
- Content-Type: application/HTTP-Kerberos-session-encrypted\r
216
- OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{original_length + pad_len}\r
217
- --Encrypted Boundary\r
218
- Content-Type: application/octet-stream\r
219
- #{emsg}--Encrypted Boundary\r
220
- EOF
296
+ body = [
297
+ "--Encrypted Boundary",
298
+ "Content-Type: application/HTTP-Kerberos-session-encrypted",
299
+ "OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{original_length + pad_len}",
300
+ "--Encrypted Boundary",
301
+ "Content-Type: application/octet-stream",
302
+ "#{emsg}--Encrypted Boundary--",
303
+ ""
304
+ ].join("\r\n")
221
305
 
222
306
  resp = @httpcli.post(@endpoint, body, hdr)
223
307
  log_soap_message(resp.http_body.content)
@@ -17,7 +17,6 @@
17
17
  require 'httpclient'
18
18
  require 'builder'
19
19
  require 'gyoku'
20
- require 'uuidtools'
21
20
  require 'base64'
22
21
 
23
22
  # SOAP constants for WinRM
@@ -3,5 +3,5 @@
3
3
  # WinRM module
4
4
  module WinRM
5
5
  # The version of the WinRM library
6
- VERSION = '1.5.0'
6
+ VERSION = '1.6.0'
7
7
  end
@@ -16,6 +16,7 @@
16
16
 
17
17
  require 'nori'
18
18
  require 'rexml/document'
19
+ require 'securerandom'
19
20
  require 'winrm/command_executor'
20
21
  require_relative 'helpers/powershell_script'
21
22
 
@@ -36,6 +37,7 @@ module WinRM
36
37
  # @param [Hash] opts Misc opts for the various transports.
37
38
  # @see WinRM::HTTP::HttpTransport
38
39
  # @see WinRM::HTTP::HttpGSSAPI
40
+ # @see WinRM::HTTP::HttpNegotiate
39
41
  # @see WinRM::HTTP::HttpSSL
40
42
  def initialize(endpoint, transport = :kerberos, opts = {})
41
43
  @endpoint = endpoint
@@ -44,20 +46,32 @@ module WinRM
44
46
  @locale = DEFAULT_LOCALE
45
47
  setup_logger
46
48
  configure_retries(opts)
47
- case transport
48
- when :kerberos
49
- require 'gssapi'
50
- require 'gssapi/extensions'
51
- @xfer = HTTP::HttpGSSAPI.new(endpoint, opts[:realm], opts[:service], opts[:keytab], opts)
52
- when :plaintext
53
- @xfer = HTTP::HttpPlaintext.new(endpoint, opts[:user], opts[:pass], opts)
54
- when :ssl
55
- @xfer = HTTP::HttpSSL.new(endpoint, opts[:user], opts[:pass], opts[:ca_trust_path], opts)
56
- else
57
- raise "Invalid transport '#{transport}' specified, expected: kerberos, plaintext, ssl."
49
+ begin
50
+ @xfer = send "init_#{transport}_transport", opts.merge({endpoint: endpoint})
51
+ rescue NoMethodError => e
52
+ raise "Invalid transport '#{transport}' specified, expected: negotiate, kerberos, plaintext, ssl."
58
53
  end
59
54
  end
60
55
 
56
+ def init_negotiate_transport(opts)
57
+ require 'rubyntlm'
58
+ HTTP::HttpNegotiate.new(opts[:endpoint], opts[:user], opts[:pass], opts)
59
+ end
60
+
61
+ def init_kerberos_transport(opts)
62
+ require 'gssapi'
63
+ require 'gssapi/extensions'
64
+ HTTP::HttpGSSAPI.new(opts[:endpoint], opts[:realm], opts[:service], opts[:keytab], opts)
65
+ end
66
+
67
+ def init_plaintext_transport(opts)
68
+ HTTP::HttpPlaintext.new(opts[:endpoint], opts[:user], opts[:pass], opts)
69
+ end
70
+
71
+ def init_ssl_transport(opts)
72
+ HTTP::HttpSSL.new(opts[:endpoint], opts[:user], opts[:pass], opts[:ca_trust_path], opts)
73
+ end
74
+
61
75
  # Operation timeout.
62
76
  #
63
77
  # Unless specified the client receive timeout will be 10s + the operation
@@ -319,7 +333,7 @@ module WinRM
319
333
  # @param [String] an existing and open shell id to reuse
320
334
  # @return [Hash] :stdout and :stderr
321
335
  def run_powershell_script(script_file, &block)
322
- logger.warn("WinRM::WinRMWebService#run_powershell_script is deprecated. Use WinRM::CommandExecutor#run_cmd instead")
336
+ logger.warn("WinRM::WinRMWebService#run_powershell_script is deprecated. Use WinRM::CommandExecutor#run_powershell_script instead")
323
337
  create_executor do |executor|
324
338
  executor.run_powershell_script(script_file, &block)
325
339
  end
@@ -427,7 +441,7 @@ module WinRM
427
441
  "#{NS_ADDRESSING}:Address" => 'http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous',
428
442
  :attributes! => {"#{NS_ADDRESSING}:Address" => {'mustUnderstand' => true}}},
429
443
  "#{NS_WSMAN_DMTF}:MaxEnvelopeSize" => @max_env_sz,
430
- "#{NS_ADDRESSING}:MessageID" => "uuid:#{UUIDTools::UUID.random_create.to_s.upcase}",
444
+ "#{NS_ADDRESSING}:MessageID" => "uuid:#{SecureRandom.uuid.to_s.upcase}",
431
445
  "#{NS_WSMAN_DMTF}:Locale/" => '',
432
446
  "#{NS_WSMAN_MSFT}:DataLocale/" => '',
433
447
  "#{NS_WSMAN_DMTF}:OperationTimeout" => @timeout,
@@ -459,6 +473,7 @@ module WinRM
459
473
  # another Receive request.
460
474
  # http://msdn.microsoft.com/en-us/library/cc251676.aspx
461
475
  if e.fault_code == '2150858793'
476
+ logger.debug("[WinRM] retrying receive request after timeout")
462
477
  retry
463
478
  else
464
479
  raise
@@ -5,11 +5,19 @@ describe 'issue 59', integration: true do
5
5
  end
6
6
 
7
7
  describe 'long running script without output' do
8
+ let(:logged_output) { StringIO.new }
9
+ let(:logger) { Logging.logger(logged_output) }
10
+
8
11
  it 'should not error' do
9
- out = @winrm.powershell('$ProgressPreference="SilentlyContinue";sleep 60; Write-Host "Hello"')
12
+ @winrm.set_timeout(1)
13
+ @winrm.logger = logger
14
+
15
+ out = @winrm.powershell('$ProgressPreference="SilentlyContinue";sleep 3; Write-Host "Hello"')
16
+
10
17
  expect(out).to have_exit_code 0
11
18
  expect(out).to have_stdout_match(/Hello/)
12
19
  expect(out).to have_no_stderr
20
+ expect(logged_output.string).to match(/retrying receive request/)
13
21
  end
14
22
  end
15
23
  end
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+ require 'rubyntlm'
3
+ require 'winrm/http/transport'
4
+
5
+ describe WinRM::HTTP::HttpNegotiate, unit: true do
6
+ describe '#init' do
7
+ let(:endpoint) { 'some_endpoint' }
8
+ let(:domain) { 'some_domain' }
9
+ let(:user) { 'some_user' }
10
+ let(:password) { 'some_password' }
11
+ let(:options) { {} }
12
+
13
+ context 'user is not domain prefixed' do
14
+ it 'does not pass a domain to the NTLM client' do
15
+ expect(Net::NTLM::Client).to receive(:new).with(user, password, options)
16
+ WinRM::HTTP::HttpNegotiate.new(endpoint, user, password, options)
17
+ end
18
+ end
19
+
20
+ context 'user is domain prefixed' do
21
+ it 'passes prefixed domain to the NTLM client' do
22
+ expect(Net::NTLM::Client).to receive(:new) do |passed_user, passed_password, passed_options|
23
+ expect(passed_user).to eq user
24
+ expect(passed_password).to eq password
25
+ expect(passed_options[:domain]).to eq domain
26
+ end
27
+ WinRM::HTTP::HttpNegotiate.new(endpoint, "#{domain}\\#{user}", password, options)
28
+ end
29
+ end
30
+
31
+ context 'option is passed with a domain' do
32
+ let(:options) { { domain: domain } }
33
+
34
+ it 'passes domain option to the NTLM client' do
35
+ expect(Net::NTLM::Client).to receive(:new) do |passed_user, passed_password, passed_options|
36
+ expect(passed_user).to eq user
37
+ expect(passed_password).to eq password
38
+ expect(passed_options[:domain]).to eq domain
39
+ end
40
+ WinRM::HTTP::HttpNegotiate.new(endpoint, user, password, options)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -28,8 +28,7 @@ Gem::Specification.new do |s|
28
28
  s.required_ruby_version = '>= 1.9.0'
29
29
  s.add_runtime_dependency 'gssapi', '~> 1.2'
30
30
  s.add_runtime_dependency 'httpclient', '~> 2.2', '>= 2.2.0.2'
31
- s.add_runtime_dependency 'rubyntlm', '~> 0.4.0'
32
- s.add_runtime_dependency 'uuidtools', '~> 2.1.2'
31
+ s.add_runtime_dependency 'rubyntlm', '~> 0.5.0', '>= 0.5.3'
33
32
  s.add_runtime_dependency 'logging', ['>= 1.6.1', '< 3.0']
34
33
  s.add_runtime_dependency 'nori', '~> 2.0'
35
34
  s.add_runtime_dependency 'gyoku', '~> 1.0'
@@ -37,4 +36,5 @@ Gem::Specification.new do |s|
37
36
  s.add_development_dependency 'rspec', '~> 3.2'
38
37
  s.add_development_dependency 'rake', '~> 10.3'
39
38
  s.add_development_dependency 'rubocop', '~> 0.28'
39
+ s.add_development_dependency 'pry'
40
40
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: winrm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Wanek
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-01-11 00:00:00.000000000 Z
12
+ date: 2016-01-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: gssapi
@@ -51,28 +51,20 @@ dependencies:
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.4.0
55
- type: :runtime
56
- prerelease: false
57
- version_requirements: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 0.4.0
62
- - !ruby/object:Gem::Dependency
63
- name: uuidtools
64
- requirement: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
54
+ version: 0.5.0
55
+ - - ">="
67
56
  - !ruby/object:Gem::Version
68
- version: 2.1.2
57
+ version: 0.5.3
69
58
  type: :runtime
70
59
  prerelease: false
71
60
  version_requirements: !ruby/object:Gem::Requirement
72
61
  requirements:
73
62
  - - "~>"
74
63
  - !ruby/object:Gem::Version
75
- version: 2.1.2
64
+ version: 0.5.0
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 0.5.3
76
68
  - !ruby/object:Gem::Dependency
77
69
  name: logging
78
70
  requirement: !ruby/object:Gem::Requirement
@@ -177,6 +169,20 @@ dependencies:
177
169
  - - "~>"
178
170
  - !ruby/object:Gem::Version
179
171
  version: '0.28'
172
+ - !ruby/object:Gem::Dependency
173
+ name: pry
174
+ requirement: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - ">="
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ type: :development
180
+ prerelease: false
181
+ version_requirements: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
180
186
  description: |2
181
187
  Ruby library for Windows Remote Management
182
188
  email:
@@ -229,6 +235,7 @@ files:
229
235
  - spec/stubs/responses/soap_fault_v1.xml
230
236
  - spec/stubs/responses/soap_fault_v2.xml
231
237
  - spec/stubs/responses/wmi_error_v2.xml
238
+ - spec/transport_spec.rb
232
239
  - spec/winrm_options_spec.rb
233
240
  - spec/winrm_primitives_spec.rb
234
241
  - spec/wql_spec.rb