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.
- checksums.yaml +7 -0
- data/LICENSE +202 -0
- data/README.md +277 -0
- data/bin/rwinrm +90 -0
- data/lib/winrm/connection.rb +84 -0
- data/lib/winrm/connection_opts.rb +92 -0
- data/lib/winrm/exceptions.rb +88 -0
- data/lib/winrm/http/response_handler.rb +127 -0
- data/lib/winrm/http/transport.rb +466 -0
- data/lib/winrm/http/transport_factory.rb +64 -0
- data/lib/winrm/output.rb +58 -0
- data/lib/winrm/psrp/create_pipeline.xml.erb +167 -0
- data/lib/winrm/psrp/fragment.rb +68 -0
- data/lib/winrm/psrp/init_runspace_pool.xml.erb +224 -0
- data/lib/winrm/psrp/message.rb +128 -0
- data/lib/winrm/psrp/message_data/base.rb +47 -0
- data/lib/winrm/psrp/message_data/error_record.rb +68 -0
- data/lib/winrm/psrp/message_data/pipeline_host_call.rb +30 -0
- data/lib/winrm/psrp/message_data/pipeline_output.rb +48 -0
- data/lib/winrm/psrp/message_data/pipeline_state.rb +38 -0
- data/lib/winrm/psrp/message_data/runspacepool_host_call.rb +30 -0
- data/lib/winrm/psrp/message_data/runspacepool_state.rb +37 -0
- data/lib/winrm/psrp/message_data/session_capability.rb +34 -0
- data/lib/winrm/psrp/message_data.rb +40 -0
- data/lib/winrm/psrp/message_defragmenter.rb +62 -0
- data/lib/winrm/psrp/message_factory.rb +86 -0
- data/lib/winrm/psrp/message_fragmenter.rb +58 -0
- data/lib/winrm/psrp/powershell_output_decoder.rb +142 -0
- data/lib/winrm/psrp/receive_response_reader.rb +95 -0
- data/lib/winrm/psrp/session_capability.xml.erb +7 -0
- data/lib/winrm/psrp/uuid.rb +39 -0
- data/lib/winrm/shells/base.rb +192 -0
- data/lib/winrm/shells/cmd.rb +59 -0
- data/lib/winrm/shells/power_shell.rb +202 -0
- data/lib/winrm/shells/retryable.rb +44 -0
- data/lib/winrm/shells/shell_factory.rb +56 -0
- data/lib/winrm/version.rb +5 -0
- data/lib/winrm/wsmv/base.rb +57 -0
- data/lib/winrm/wsmv/cleanup_command.rb +60 -0
- data/lib/winrm/wsmv/close_shell.rb +49 -0
- data/lib/winrm/wsmv/command.rb +100 -0
- data/lib/winrm/wsmv/command_output.rb +75 -0
- data/lib/winrm/wsmv/command_output_decoder.rb +54 -0
- data/lib/winrm/wsmv/configuration.rb +44 -0
- data/lib/winrm/wsmv/create_pipeline.rb +64 -0
- data/lib/winrm/wsmv/create_shell.rb +115 -0
- data/lib/winrm/wsmv/header.rb +213 -0
- data/lib/winrm/wsmv/init_runspace_pool.rb +96 -0
- data/lib/winrm/wsmv/iso8601_duration.rb +58 -0
- data/lib/winrm/wsmv/keep_alive.rb +66 -0
- data/lib/winrm/wsmv/receive_response_reader.rb +128 -0
- data/lib/winrm/wsmv/send_data.rb +66 -0
- data/lib/winrm/wsmv/soap.rb +49 -0
- data/lib/winrm/wsmv/wql_pull.rb +54 -0
- data/lib/winrm/wsmv/wql_query.rb +98 -0
- data/lib/winrm/wsmv/write_stdin.rb +86 -0
- data/lib/winrm.rb +37 -0
- 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
|
data/lib/winrm/output.rb
ADDED
@@ -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
|