chef-winrm 2.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +202 -0
  3. data/README.md +277 -0
  4. data/bin/rwinrm +90 -0
  5. data/lib/winrm/connection.rb +84 -0
  6. data/lib/winrm/connection_opts.rb +92 -0
  7. data/lib/winrm/exceptions.rb +88 -0
  8. data/lib/winrm/http/response_handler.rb +127 -0
  9. data/lib/winrm/http/transport.rb +466 -0
  10. data/lib/winrm/http/transport_factory.rb +64 -0
  11. data/lib/winrm/output.rb +58 -0
  12. data/lib/winrm/psrp/create_pipeline.xml.erb +167 -0
  13. data/lib/winrm/psrp/fragment.rb +68 -0
  14. data/lib/winrm/psrp/init_runspace_pool.xml.erb +224 -0
  15. data/lib/winrm/psrp/message.rb +128 -0
  16. data/lib/winrm/psrp/message_data/base.rb +47 -0
  17. data/lib/winrm/psrp/message_data/error_record.rb +68 -0
  18. data/lib/winrm/psrp/message_data/pipeline_host_call.rb +30 -0
  19. data/lib/winrm/psrp/message_data/pipeline_output.rb +48 -0
  20. data/lib/winrm/psrp/message_data/pipeline_state.rb +38 -0
  21. data/lib/winrm/psrp/message_data/runspacepool_host_call.rb +30 -0
  22. data/lib/winrm/psrp/message_data/runspacepool_state.rb +37 -0
  23. data/lib/winrm/psrp/message_data/session_capability.rb +34 -0
  24. data/lib/winrm/psrp/message_data.rb +40 -0
  25. data/lib/winrm/psrp/message_defragmenter.rb +62 -0
  26. data/lib/winrm/psrp/message_factory.rb +86 -0
  27. data/lib/winrm/psrp/message_fragmenter.rb +58 -0
  28. data/lib/winrm/psrp/powershell_output_decoder.rb +142 -0
  29. data/lib/winrm/psrp/receive_response_reader.rb +95 -0
  30. data/lib/winrm/psrp/session_capability.xml.erb +7 -0
  31. data/lib/winrm/psrp/uuid.rb +39 -0
  32. data/lib/winrm/shells/base.rb +192 -0
  33. data/lib/winrm/shells/cmd.rb +59 -0
  34. data/lib/winrm/shells/power_shell.rb +202 -0
  35. data/lib/winrm/shells/retryable.rb +44 -0
  36. data/lib/winrm/shells/shell_factory.rb +56 -0
  37. data/lib/winrm/version.rb +5 -0
  38. data/lib/winrm/wsmv/base.rb +57 -0
  39. data/lib/winrm/wsmv/cleanup_command.rb +60 -0
  40. data/lib/winrm/wsmv/close_shell.rb +49 -0
  41. data/lib/winrm/wsmv/command.rb +100 -0
  42. data/lib/winrm/wsmv/command_output.rb +75 -0
  43. data/lib/winrm/wsmv/command_output_decoder.rb +54 -0
  44. data/lib/winrm/wsmv/configuration.rb +44 -0
  45. data/lib/winrm/wsmv/create_pipeline.rb +64 -0
  46. data/lib/winrm/wsmv/create_shell.rb +115 -0
  47. data/lib/winrm/wsmv/header.rb +213 -0
  48. data/lib/winrm/wsmv/init_runspace_pool.rb +96 -0
  49. data/lib/winrm/wsmv/iso8601_duration.rb +58 -0
  50. data/lib/winrm/wsmv/keep_alive.rb +66 -0
  51. data/lib/winrm/wsmv/receive_response_reader.rb +128 -0
  52. data/lib/winrm/wsmv/send_data.rb +66 -0
  53. data/lib/winrm/wsmv/soap.rb +49 -0
  54. data/lib/winrm/wsmv/wql_pull.rb +54 -0
  55. data/lib/winrm/wsmv/wql_query.rb +98 -0
  56. data/lib/winrm/wsmv/write_stdin.rb +86 -0
  57. data/lib/winrm.rb +37 -0
  58. metadata +333 -0
@@ -0,0 +1,466 @@
1
+ # Copyright 2010 Dan Wanek <dan.wanek@gmail.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'httpclient'
16
+ require_relative 'response_handler'
17
+
18
+ module WinRM
19
+ module HTTP
20
+ # A generic HTTP transport that utilized HTTPClient to send messages back and forth.
21
+ # This backend will maintain state for every WinRMWebService instance that is instantiated so it
22
+ # is possible to use GSSAPI with Keep-Alive.
23
+ class HttpTransport
24
+ attr_reader :endpoint
25
+
26
+ def initialize(endpoint, options)
27
+ @endpoint = endpoint.is_a?(String) ? URI.parse(endpoint) : endpoint
28
+ @httpcli = HTTPClient.new
29
+ @logger = Logging.logger[self]
30
+ @httpcli.receive_timeout = options[:receive_timeout]
31
+ @httpcli.default_header = { 'User-Agent': options[:user_agent] }
32
+ end
33
+
34
+ # Sends the SOAP payload to the WinRM service and returns the service's
35
+ # SOAP response. If an error occurrs an appropriate error is raised.
36
+ #
37
+ # @param [String] The XML SOAP message
38
+ # @returns [REXML::Document] The parsed response body
39
+ def send_request(message)
40
+ ssl_peer_fingerprint_verification!
41
+ log_soap_message(message)
42
+ hdr = { 'Content-Type' => 'application/soap+xml;charset=UTF-8',
43
+ 'Content-Length' => message.bytesize }
44
+ # We need to add this header if using Client Certificate authentication
45
+ unless @httpcli.ssl_config.client_cert.nil?
46
+ hdr['Authorization'] = 'http://schemas.dmtf.org/wbem/wsman/1/wsman/secprofile/https/mutual'
47
+ end
48
+
49
+ resp = @httpcli.post(@endpoint, message, hdr)
50
+ log_soap_message(resp.http_body.content)
51
+ verify_ssl_fingerprint(resp.peer_cert)
52
+ handler = WinRM::ResponseHandler.new(resp.http_body.content, resp.status)
53
+ handler.parse_to_xml
54
+ end
55
+
56
+ # We'll need this to force basic authentication if desired
57
+ def basic_auth_only!
58
+ auths = @httpcli.www_auth.instance_variable_get('@authenticator')
59
+ auths.delete_if { |i| i.scheme !~ /basic/i }
60
+ end
61
+
62
+ # Disable SSPI Auth
63
+ def no_sspi_auth!
64
+ auths = @httpcli.www_auth.instance_variable_get('@authenticator')
65
+ auths.delete_if { |i| i.is_a? HTTPClient::SSPINegotiateAuth }
66
+ end
67
+
68
+ # Disable SSL Peer Verification
69
+ def no_ssl_peer_verification!
70
+ @httpcli.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
71
+ end
72
+
73
+ # SSL Peer Fingerprint Verification prior to connecting
74
+ def ssl_peer_fingerprint_verification!
75
+ return unless @ssl_peer_fingerprint && !@ssl_peer_fingerprint_verified
76
+
77
+ with_untrusted_ssl_connection do |connection|
78
+ connection_cert = connection.peer_cert
79
+ verify_ssl_fingerprint(connection_cert)
80
+ end
81
+ @logger.info("initial ssl fingerprint #{@ssl_peer_fingerprint} verified\n")
82
+ @ssl_peer_fingerprint_verified = true
83
+ no_ssl_peer_verification!
84
+ end
85
+
86
+ # Connect without verification to retrieve untrusted ssl context
87
+ def with_untrusted_ssl_connection
88
+ noverify_peer_context = OpenSSL::SSL::SSLContext.new
89
+ noverify_peer_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
90
+ tcp_connection = TCPSocket.new(@endpoint.host, @endpoint.port)
91
+ begin
92
+ ssl_connection = OpenSSL::SSL::SSLSocket.new(tcp_connection, noverify_peer_context)
93
+ ssl_connection.connect
94
+ yield ssl_connection
95
+ ensure
96
+ tcp_connection.close
97
+ end
98
+ end
99
+
100
+ # compare @ssl_peer_fingerprint to current ssl context
101
+ def verify_ssl_fingerprint(cert)
102
+ return unless @ssl_peer_fingerprint
103
+
104
+ conn_fingerprint = OpenSSL::Digest::SHA1.new(cert.to_der).to_s
105
+ return unless @ssl_peer_fingerprint.casecmp(conn_fingerprint) != 0
106
+
107
+ raise "ssl fingerprint mismatch!!!!\n"
108
+ end
109
+
110
+ protected
111
+
112
+ def body(message, length, type = 'application/HTTP-SPNEGO-session-encrypted')
113
+ [
114
+ '--Encrypted Boundary',
115
+ "Content-Type: #{type}",
116
+ "OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{length}",
117
+ '--Encrypted Boundary',
118
+ 'Content-Type: application/octet-stream',
119
+ "#{message}--Encrypted Boundary--"
120
+ ].join("\r\n").concat("\r\n")
121
+ end
122
+
123
+ def log_soap_message(message)
124
+ return unless @logger.debug?
125
+
126
+ xml_msg = REXML::Document.new(message)
127
+ formatter = REXML::Formatters::Pretty.new(2)
128
+ formatter.compact = true
129
+ formatter.write(xml_msg, @logger)
130
+ @logger.debug("\n")
131
+ rescue StandardError => e
132
+ @logger.debug("Couldn't log SOAP request/response: #{e.message} - #{message}")
133
+ end
134
+ end
135
+
136
+ # Plain text, insecure, HTTP transport
137
+ class HttpPlaintext < HttpTransport
138
+ def initialize(endpoint, user, pass, opts)
139
+ super(endpoint, opts)
140
+ @httpcli.set_auth(nil, user, pass)
141
+ no_sspi_auth! if opts[:disable_sspi]
142
+ basic_auth_only! if opts[:basic_auth_only]
143
+ no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
144
+ end
145
+ end
146
+
147
+ # NTLM/Negotiate, secure, HTTP transport
148
+ class HttpNegotiate < HttpTransport
149
+ def initialize(endpoint, user, pass, opts)
150
+ super(endpoint, opts)
151
+ require 'rubyntlm'
152
+ no_sspi_auth!
153
+
154
+ user_parts = user.split('\\')
155
+ if user_parts.length > 1
156
+ opts[:domain] = user_parts[0]
157
+ user = user_parts[1]
158
+ end
159
+
160
+ @ntlmcli = Net::NTLM::Client.new(user, pass, opts)
161
+ @retryable = true
162
+ no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
163
+ @ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
164
+ @httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
165
+ @httpcli.ssl_config.cert_store = opts[:cert_store] if opts[:cert_store]
166
+ end
167
+
168
+ def send_request(message)
169
+ ssl_peer_fingerprint_verification!
170
+ init_auth if @ntlmcli.session.nil?
171
+ log_soap_message(message)
172
+
173
+ hdr = {
174
+ 'Content-Type' => 'multipart/encrypted;'\
175
+ 'protocol="application/HTTP-SPNEGO-session-encrypted";boundary="Encrypted Boundary"'
176
+ }
177
+
178
+ resp = @httpcli.post(@endpoint, body(seal(message), message.bytesize), hdr)
179
+ verify_ssl_fingerprint(resp.peer_cert)
180
+ if resp.status == 401 && @retryable
181
+ @retryable = false
182
+ init_auth
183
+ send_request(message)
184
+ else
185
+ @retryable = true
186
+ decrypted_body = winrm_decrypt(resp)
187
+ log_soap_message(decrypted_body)
188
+ WinRM::ResponseHandler.new(decrypted_body, resp.status).parse_to_xml
189
+ end
190
+ end
191
+
192
+ private
193
+
194
+ def seal(message)
195
+ emessage = @ntlmcli.session.seal_message message
196
+ signature = @ntlmcli.session.sign_message message
197
+ "\x10\x00\x00\x00#{signature}#{emessage}"
198
+ end
199
+
200
+ def winrm_decrypt(resp)
201
+ # OMI server doesn't always respond to encrypted messages with encrypted responses over SSL
202
+ return resp.body if resp.header['Content-Type'].first =~ %r{\Aapplication\/soap\+xml}i
203
+ return '' if resp.body.empty?
204
+
205
+ str = resp.body.force_encoding('BINARY')
206
+ str.sub!(%r{^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$}m, '\1')
207
+
208
+ signature = str[4..19]
209
+ message = @ntlmcli.session.unseal_message str[20..-1]
210
+ return message if @ntlmcli.session.verify_signature(signature, message)
211
+
212
+ raise WinRMHTTPTransportError, 'Could not decrypt NTLM message.'
213
+ end
214
+
215
+ def issue_challenge_response(negotiate)
216
+ auth_header = {
217
+ 'Authorization' => "Negotiate #{negotiate.encode64}",
218
+ 'Content-Type' => 'application/soap+xml;charset=UTF-8'
219
+ }
220
+
221
+ # OMI Server on Linux requires an empty payload with the new auth header to proceed
222
+ # because the config check for max payload size will otherwise break the auth handshake
223
+ # given the OMI server does not support that check
224
+ @httpcli.post(@endpoint, '', auth_header)
225
+
226
+ # return an empty hash of headers for subsequent requests to use
227
+ {}
228
+ end
229
+
230
+ def init_auth
231
+ @logger.debug "Initializing Negotiate for #{@endpoint}"
232
+ auth1 = @ntlmcli.init_context
233
+ hdr = {
234
+ 'Authorization' => "Negotiate #{auth1.encode64}",
235
+ 'Content-Type' => 'application/soap+xml;charset=UTF-8'
236
+ }
237
+ @logger.debug 'Sending HTTP POST for Negotiate Authentication'
238
+ r = @httpcli.post(@endpoint, '', hdr)
239
+ verify_ssl_fingerprint(r.peer_cert)
240
+ auth_header = r.header['WWW-Authenticate'].pop
241
+ unless auth_header
242
+ msg = "Unable to parse authorization header. Headers: #{r.headers}\r\nBody: #{r.body}"
243
+ raise WinRMHTTPTransportError.new(msg, r.status_code)
244
+ end
245
+ itok = auth_header.split.last
246
+ auth3 = @ntlmcli.init_context(itok, channel_binding(r))
247
+ issue_challenge_response(auth3)
248
+ end
249
+
250
+ def channel_binding(response)
251
+ if response.peer_cert.nil?
252
+ nil
253
+ else
254
+ cert = if RUBY_PLATFORM == 'java'
255
+ OpenSSL::X509::Certificate.new(response.peer_cert.cert.getEncoded)
256
+ else
257
+ response.peer_cert
258
+ end
259
+ Net::NTLM::ChannelBinding.create(OpenSSL::X509::Certificate.new(cert))
260
+ end
261
+ end
262
+ end
263
+
264
+ # Uses SSL to secure the transport
265
+ class BasicAuthSSL < HttpTransport
266
+ def initialize(endpoint, user, pass, opts)
267
+ super(endpoint, opts)
268
+ @httpcli.set_auth(endpoint, user, pass)
269
+ basic_auth_only!
270
+ no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
271
+ @ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
272
+ @httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
273
+ @httpcli.ssl_config.cert_store = opts[:cert_store] if opts[:cert_store]
274
+ end
275
+ end
276
+
277
+ # Uses Client Certificate to authenticate and SSL to secure the transport
278
+ class ClientCertAuthSSL < HttpTransport
279
+ def initialize(endpoint, client_cert, client_key, key_pass, opts)
280
+ super(endpoint, opts)
281
+ @httpcli.ssl_config.set_client_cert_file(client_cert, client_key, key_pass)
282
+ @httpcli.www_auth.instance_variable_set('@authenticator', [])
283
+ no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
284
+ @ssl_peer_fingerprint = opts[:ssl_peer_fingerprint]
285
+ @httpcli.ssl_config.set_trust_ca(opts[:ca_trust_path]) if opts[:ca_trust_path]
286
+ @httpcli.ssl_config.cert_store = opts[:cert_store] if opts[:cert_store]
287
+ end
288
+ end
289
+
290
+ # Uses Kerberos/GSSAPI to authenticate and encrypt messages
291
+ class HttpGSSAPI < HttpTransport
292
+ # @param [String,URI] endpoint the WinRM webservice endpoint
293
+ # @param [String] realm the Kerberos realm we are authenticating to
294
+ # @param [String<optional>] service the service name, default is HTTP
295
+ def initialize(endpoint, realm, opts, service = nil)
296
+ require 'gssapi'
297
+ require 'gssapi/extensions'
298
+
299
+ super(endpoint, opts)
300
+ # Remove the GSSAPI auth from HTTPClient because we are doing our own thing
301
+ no_sspi_auth!
302
+ service ||= 'HTTP'
303
+ @service = "#{service}/#{@endpoint.host}@#{realm}"
304
+ no_ssl_peer_verification! if opts[:no_ssl_peer_verification]
305
+ init_krb
306
+ end
307
+
308
+ # Sends the SOAP payload to the WinRM service and returns the service's
309
+ # SOAP response. If an error occurrs an appropriate error is raised.
310
+ #
311
+ # @param [String] The XML SOAP message
312
+ # @returns [REXML::Document] The parsed response body
313
+ def send_request(message)
314
+ resp = send_kerberos_request(message)
315
+
316
+ if resp.status == 401
317
+ @logger.debug 'Got 401 - reinitializing Kerberos and retrying one more time'
318
+ init_krb
319
+ resp = send_kerberos_request(message)
320
+ end
321
+
322
+ handler = WinRM::ResponseHandler.new(winrm_decrypt(resp.http_body.content), resp.status)
323
+ handler.parse_to_xml
324
+ end
325
+
326
+ private
327
+
328
+ # Sends the SOAP payload to the WinRM service and returns the service's
329
+ # HTTP response.
330
+ #
331
+ # @param [String] The XML SOAP message
332
+ # @returns [Object] The HTTP response object
333
+ def send_kerberos_request(message)
334
+ log_soap_message(message)
335
+ original_length = message.bytesize
336
+ pad_len, emsg = winrm_encrypt(message)
337
+ req_length = original_length + pad_len
338
+ hdr = {
339
+ 'Connection' => 'Keep-Alive',
340
+ 'Content-Type' => 'multipart/encrypted;' \
341
+ 'protocol="application/HTTP-Kerberos-session-encrypted";boundary="Encrypted Boundary"'
342
+ }
343
+
344
+ resp = @httpcli.post(
345
+ @endpoint,
346
+ body(emsg, req_length, 'application/HTTP-Kerberos-session-encrypted'),
347
+ hdr
348
+ )
349
+ log_soap_message(resp.http_body.content)
350
+ resp
351
+ end
352
+
353
+ def init_krb
354
+ @logger.debug "Initializing Kerberos for #{@service}"
355
+ @gsscli = GSSAPI::Simple.new(@endpoint.host, @service)
356
+ token = @gsscli.init_context
357
+ auth = Base64.strict_encode64 token
358
+
359
+ hdr = {
360
+ 'Authorization' => "Kerberos #{auth}",
361
+ 'Connection' => 'Keep-Alive',
362
+ 'Content-Type' => 'application/soap+xml;charset=UTF-8'
363
+ }
364
+ @logger.debug 'Sending HTTP POST for Kerberos Authentication'
365
+ r = @httpcli.post(@endpoint, '', hdr)
366
+ itok = r.header['WWW-Authenticate'].pop
367
+ itok = itok.split.last
368
+ itok = Base64.strict_decode64(itok)
369
+ @gsscli.init_context(itok)
370
+ end
371
+
372
+ # rubocop:disable Metrics/MethodLength
373
+ # rubocop:disable Metrics/AbcSize
374
+
375
+ # @return [String] the encrypted request string
376
+ def winrm_encrypt(str)
377
+ @logger.debug "Encrypting SOAP message:\n#{str}"
378
+ iov_cnt = 3
379
+ iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)
380
+
381
+ iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
382
+ iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | \
383
+ GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
384
+
385
+ iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
386
+ FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1))
387
+ )
388
+ iov1[:type] = GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA
389
+ iov1[:buffer].value = str
390
+
391
+ iov2 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
392
+ FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 2))
393
+ )
394
+ iov2[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_PADDING | \
395
+ GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
396
+
397
+ conf_state = FFI::MemoryPointer.new :uint32
398
+ min_stat = FFI::MemoryPointer.new :uint32
399
+
400
+ GSSAPI::LibGSSAPI.gss_wrap_iov(
401
+ min_stat,
402
+ @gsscli.context,
403
+ 1,
404
+ GSSAPI::LibGSSAPI::GSS_C_QOP_DEFAULT,
405
+ conf_state,
406
+ iov,
407
+ iov_cnt
408
+ )
409
+
410
+ token = [iov0[:buffer].length].pack('L')
411
+ token += iov0[:buffer].value
412
+ token += iov1[:buffer].value
413
+ pad_len = iov2[:buffer].length
414
+ token += iov2[:buffer].value if pad_len > 0
415
+ [pad_len, token]
416
+ end
417
+
418
+ # @return [String] the unencrypted response string
419
+ def winrm_decrypt(str)
420
+ @logger.debug "Decrypting SOAP message:\n#{str}"
421
+ iov_cnt = 3
422
+ iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)
423
+
424
+ iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
425
+ iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | \
426
+ GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
427
+
428
+ iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
429
+ FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1))
430
+ )
431
+ iov1[:type] = GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA
432
+
433
+ iov2 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(
434
+ FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 2))
435
+ )
436
+ iov2[:type] = GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA
437
+
438
+ str.force_encoding('BINARY')
439
+ str.sub!(%r{^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$}m, '\1')
440
+
441
+ len = str.unpack('L').first
442
+ iov_data = str.unpack("La#{len}a*")
443
+ iov0[:buffer].value = iov_data[1]
444
+ iov1[:buffer].value = iov_data[2]
445
+
446
+ min_stat = FFI::MemoryPointer.new :uint32
447
+ conf_state = FFI::MemoryPointer.new :uint32
448
+ conf_state.write_int(1)
449
+ qop_state = FFI::MemoryPointer.new :uint32
450
+ qop_state.write_int(0)
451
+
452
+ maj_stat = GSSAPI::LibGSSAPI.gss_unwrap_iov(
453
+ min_stat, @gsscli.context, conf_state, qop_state, iov, iov_cnt
454
+ )
455
+
456
+ @logger.debug "SOAP message decrypted (MAJ: #{maj_stat}, " \
457
+ "MIN: #{min_stat.read_int}):\n#{iov1[:buffer].value}"
458
+
459
+ iov1[:buffer].value
460
+ end
461
+ # rubocop:enable Metrics/MethodLength
462
+ # rubocop:enable Metrics/AbcSize
463
+ end
464
+ end
465
+ end
466
+ # WinRM
@@ -0,0 +1,64 @@
1
+ # Copyright 2016 Shawn Neal <sneal@sneal.net>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require_relative 'transport'
16
+
17
+ module WinRM
18
+ module HTTP
19
+ # Factory for creating a HTTP transport that can be used for WinRM SOAP calls.
20
+ class TransportFactory
21
+ # Creates a new WinRM HTTP transport using the specified connection options.
22
+ # @param connection_opts [ConnectionOpts|Hash] The connection ConnectionOpts.
23
+ # @return [HttpTransport] A transport instance for making WinRM calls.
24
+ def create_transport(connection_opts)
25
+ transport = connection_opts[:transport]
26
+ validate_transport!(transport)
27
+ send "init_#{transport}_transport", connection_opts
28
+ end
29
+
30
+ private
31
+
32
+ def init_negotiate_transport(opts)
33
+ HTTP::HttpNegotiate.new(opts[:endpoint], opts[:user], opts[:password], opts)
34
+ end
35
+
36
+ def init_kerberos_transport(opts)
37
+ HTTP::HttpGSSAPI.new(opts[:endpoint], opts[:realm], opts, opts[:service])
38
+ end
39
+
40
+ def init_plaintext_transport(opts)
41
+ HTTP::HttpPlaintext.new(opts[:endpoint], opts[:user], opts[:password], opts)
42
+ end
43
+
44
+ def init_ssl_transport(opts)
45
+ if opts[:basic_auth_only]
46
+ HTTP::BasicAuthSSL.new(opts[:endpoint], opts[:user], opts[:password], opts)
47
+ elsif opts[:client_cert]
48
+ HTTP::ClientCertAuthSSL.new(opts[:endpoint], opts[:client_cert],
49
+ opts[:client_key], opts[:key_pass], opts)
50
+ else
51
+ HTTP::HttpNegotiate.new(opts[:endpoint], opts[:user], opts[:password], opts)
52
+ end
53
+ end
54
+
55
+ def validate_transport!(transport)
56
+ valid = private_methods
57
+ .select { |m| m.to_s.start_with?('init_') && m.to_s.end_with?('_transport') }
58
+ .map { |tm| tm.to_s.split('_')[1] }
59
+
60
+ raise WinRM::InvalidTransportError.new(transport, valid) unless valid.include?(transport.to_s)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,58 @@
1
+ # Copyright 2014 Max Lincoln <max@devopsy.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module WinRM
16
+ # This class holds raw output and has convenience methods to parse.
17
+ class Output
18
+ def initialize
19
+ @data = []
20
+ end
21
+
22
+ # @return [Integer] exitcode returned from command
23
+ attr_reader :exitcode
24
+
25
+ # @return [String] Aggregated stdout and stderr streams
26
+ def output
27
+ @data.flat_map do |line|
28
+ [line[:stdout], line[:stderr]]
29
+ end.compact.join
30
+ end
31
+
32
+ # @return [String] stdout stream output
33
+ def stdout
34
+ @data.map do |line|
35
+ line[:stdout]
36
+ end.compact.join
37
+ end
38
+
39
+ # @return [String] stderr stream output
40
+ def stderr
41
+ @data.map do |line|
42
+ line[:stderr]
43
+ end.compact.join
44
+ end
45
+
46
+ # Sets the exitcode
47
+ def exitcode=(code)
48
+ raise WinRM::InvalidExitCode unless code.is_a? Integer
49
+
50
+ @exitcode = code
51
+ end
52
+
53
+ # Appends stream data to the output
54
+ def <<(data)
55
+ @data << data
56
+ end
57
+ end
58
+ end