chef-winrm 2.3.10

Sign up to get free protection for your applications and to get access to all the features.
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