winrm 0.0.6 → 1.0.0rc1

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.
data/README CHANGED
@@ -7,47 +7,68 @@ not limitted to, running batch scripts, powershell scripts and fetching WMI
7
7
  variables. For more information on WinRM, please visit Microsoft's WinRM
8
8
  site: http://msdn.microsoft.com/en-us/library/aa384426(v=VS.85).aspx
9
9
 
10
- * This version does not currently support encryption of messages via
11
- Kerberos so it is highly recommended to use SSL when configuring WinRM.
12
- 'winrm quickconfig -transport:https'
13
-
14
-
15
10
  My Info:
16
11
  BLOG: http://distributed-frostbite.blogspot.com/
17
12
  Add me in LinkedIn: http://www.linkedin.com/in/danwanek
18
13
  Find me on irc.freenode.net in #ruby-lang (zenChild)
19
14
 
20
15
  --------------------------------------------------------------------------
16
+ Version 1.0.0rc1
17
+
18
+ This is a release candidate of the new version 1 code for Ruby WinRM. It
19
+ is almost a complete re-write from the previous version to support some
20
+ of the following major feature additions:
21
+
22
+ 1. GSSAPI support: This is the default way that Windows authenticates and
23
+ secures WinRM messages. In order for this to work the computer you are
24
+ connecting to must be a part of an Active Directory domain and you must
25
+ have local credentials via kinit. GSSAPI support is dependent on the
26
+ gssapi gem which only supports the MIT Kerberos libraries at this time.
27
+
28
+ If you are using this method there is no longer a need to change the
29
+ WinRM service authentication settings. You can simply do a
30
+ 'winrm quickconfig' on your server or enable WinRM via group policy and
31
+ everything should be working.
32
+
33
+ 2. Multi-Instance support: The SOAP back-end has been completely gutted
34
+ and is now using some of the Savon core libraries for parsing and
35
+ building packets. Moving away from Handsoap allows multiple instances
36
+ to be created because the SOAP backend is no longer a Singleton type
37
+ class.
38
+
39
+ --------------------------------------------------------------------------
40
+ !!!SHOUTS OUT!!!
41
+ --------------------------------------------------------------------------
42
+ Thanks to Seth Chisamore (https://github.com/schisamo) of Opscode for his input and patches.
21
43
  --------------------------------------------------------------------------
22
- TO USE:
23
- require 'winrm'
24
- # See REQUIRED GEMS below
25
44
 
26
- gem install -r winrm
27
45
 
28
- REQUIRED GEMS: (These should automatically install with the winrm gem)
29
- # Handsoap
30
- gem install -r handsoap
31
- # Nokogiri XML Parser
32
- gem install -r nokogiri
33
- # HttpClient
34
- gem install -r httpclient
35
- # NTLM Library
36
- gem install -r rubyntlm
37
- #UUID Library
38
- gem install -r uuid
46
+ INSTALL:
47
+ gem install -r winrm
48
+ .. on the server 'winrm quickconfig' as admin
39
49
 
50
+ USE:
51
+ require 'winrm'
40
52
 
41
53
  EXAMPLE:
42
54
  require 'winrm'
43
- WinRM::WinRM.endpoint = 'https://mywinrmhost:5986/wsman'
44
- WinRM::WinRM.set_auth('user','password')
45
- winrm = WinRM::WinRM.instance
46
- outputps = winrm.powershell 'script.ps1'
47
- outputcmd = winrm.cmd 'ipconfig'
48
-
49
- To specify manually where your CA certificate store is use this:
50
- WinRM::WinRM.set_ca_trust_path('/etc/ssl/certs')
55
+ endpoint = http://mywinrmhost:5985/wsman
56
+ krb5_realm = 'EXAMPLE.COM'
57
+ winrm = WinRM::WinRMWebService.new(endpoint, :kerberos, :realm => krb5_realm)
58
+ winrm.cmd('ipconfig /all') do |stdout, stderr|
59
+ STDOUT.print stdout
60
+ STDERR.print stderr
61
+ end
62
+
63
+
64
+ CONNECTION TYPES:
65
+
66
+ BASIC:
67
+ WinRM::WinRMWebService.new(endpoint, :plaintext, :user => myuser, :pass => mypass)
68
+ BASIC + SSL:
69
+ WinRM::WinRMWebService.new(endpoint, :ssl, :user => myuser, :pass => mypass)
70
+ KERBEROS:
71
+ WinRM::WinRMWebService.new(endpoint, :kerberos, :realm => 'MYREALM.COM')
51
72
 
52
73
  --------------------------------------------------------------------------
53
74
  DISCLAIMER: If you see something that could be done better or would like
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.6
1
+ 1.0.0rc1
data/lib/winrm.rb CHANGED
@@ -19,81 +19,8 @@
19
19
  #############################################################################
20
20
 
21
21
  # We only what one instance of this class so include Singleton
22
- require 'singleton'
23
22
  require 'date'
24
- require 'base64'
25
- require 'uuid'
26
- require 'kconv'
23
+ require 'kconv' if(RUBY_VERSION.start_with? '1.9') # bug in rubyntlm with ruby 1.9.x
27
24
 
28
- # Class Extensions
29
- require 'extensions/string'
30
-
31
- # Load the backend SOAP infrastructure. Today this is Handsoap.
32
- require 'soap/soap_provider'
33
-
34
-
35
- module WinRM
36
- class WinRM
37
- include Singleton
38
-
39
- attr_reader :winrm
40
-
41
- # Set the endpoint for WinRM Web Services.
42
- # @param [String] endpoint The URL of the endpoint.
43
- # https://myserver:5986/wsman
44
- # @param [Integer] version The SOAP version to use. This defaults to 1
45
- # and you should not need to pass this parameter.
46
- def self.endpoint=(endpoint, version = 2)
47
- @@endpoint = endpoint
48
- SOAP::WinRMWebService.endpoint(:uri => endpoint, :version => version)
49
- end
50
-
51
- # Fetch the current endpoint
52
- def self.endpoint
53
- @@endpoint
54
- end
55
-
56
- # Set the SOAP username and password.
57
- # @param [String] user The user name
58
- # @param [String] pass The password
59
- def self.set_auth(user,pass)
60
- @@user = user
61
- SOAP::WinRMWebService.set_auth(user,pass)
62
- end
63
-
64
- def self.set_ca_trust_path(path)
65
- SOAP::WinRMWebService.set_ca_trust_path(path)
66
- end
67
-
68
- # Set the http driver that the SOAP back-end will use.
69
- # @param [Symbol] driver The HTTP driver. Available drivers:
70
- # :curb, :net_http, :http_client(Default)
71
- def self.set_http_driver(driver)
72
- Handsoap.http_driver = driver
73
- end
74
-
75
- def initialize
76
- @winrm = SOAP::WinRMWebService.new
77
- end
78
-
79
- # Run a CMD command
80
- # @see WinRM::SOAP::WinRMWebService#run_cmd
81
- def cmd(command)
82
- @winrm.run_cmd(command)
83
- end
84
-
85
- # Run a Powershell script
86
- # @see WinRM::SOAP::WinRMWebService#run_powershell_script
87
- def powershell(script_file)
88
- @winrm.run_powershell_script(script_file)
89
- end
90
-
91
- # Run a WQL Query
92
- # @see WinRM::SOAP::WinRMWebService#run_wql
93
- # @see http://msdn.microsoft.com/en-us/library/aa394606(VS.85).aspx
94
- def wql(wql)
95
- @winrm.run_wql(wql)
96
- end
97
-
98
- end # class WinRM
99
- end
25
+ require 'winrm/helpers/iso8601_duration'
26
+ require 'winrm/soap_provider'
@@ -19,14 +19,15 @@
19
19
  #############################################################################
20
20
 
21
21
  module WinRM
22
- module SOAP
23
- # Generic WinRM SOAP Error
24
- class WinRMWebServiceError < StandardError
25
- end
22
+ # Generic WinRM SOAP Error
23
+ class WinRMWebServiceError < StandardError
24
+ end
26
25
 
27
- # Authorization Error
28
- class WinRMAuthorizationError < StandardError
29
- end
30
- end # SOAP
26
+ # Authorization Error
27
+ class WinRMAuthorizationError < StandardError
28
+ end
29
+
30
+ # A Fault returned in the SOAP response. The XML node is a WSManFault
31
+ class WinRMWSManFault < StandardError; end
31
32
  end # WinRM
32
-
33
+
@@ -0,0 +1,37 @@
1
+ # Format an ISO8601 Duration
2
+ module Iso8601Duration
3
+
4
+ # Convert the number of seconds to an ISO8601 duration format
5
+ # @see http://tools.ietf.org/html/rfc2445#section-4.3.6
6
+ # @param [Fixnum] seconds The amount of seconds for this duration
7
+ def self.sec_to_dur(seconds)
8
+ seconds = seconds.to_i
9
+ iso_str = "P"
10
+ if(seconds > 604800) # more than a week
11
+ weeks = seconds / 604800
12
+ seconds -= (604800 * weeks)
13
+ iso_str << "#{weeks}W"
14
+ end
15
+ if(seconds > 86400) # more than a day
16
+ days = seconds / 86400
17
+ seconds -= (86400 * days)
18
+ iso_str << "#{days}D"
19
+ end
20
+ if(seconds > 0)
21
+ iso_str << "T"
22
+ if(seconds > 3600) # more than an hour
23
+ hours = seconds / 3600
24
+ seconds -= (3600 * hours)
25
+ iso_str << "#{hours}H"
26
+ end
27
+ if(seconds > 60) # more than a minute
28
+ minutes = seconds / 60
29
+ seconds -= (60 * minutes)
30
+ iso_str << "#{minutes}M"
31
+ end
32
+ iso_str << "#{seconds}S"
33
+ end
34
+
35
+ iso_str
36
+ end
37
+ end
@@ -0,0 +1,173 @@
1
+ module WinRM
2
+ module HTTP
3
+
4
+ # A generic HTTP transport that utilized HTTPClient to send messages back and forth.
5
+ # This backend will maintain state for every WinRMWebService instance that is instatiated so it
6
+ # is possible to use GSSAPI with Keep-Alive.
7
+ class HttpTransport
8
+
9
+ attr_reader :endpoint
10
+
11
+ def initialize(endpoint)
12
+ @endpoint = endpoint.is_a?(String) ? URI.parse(endpoint) : endpoint
13
+ @httpcli = HTTPClient.new(:agent_name => 'Ruby WinRM Client')
14
+ end
15
+
16
+ def send_request(message)
17
+ hdr = {'Content-Type' => 'application/soap+xml;charset=UTF-8', 'Content-Length' => message.length}
18
+ resp = @httpcli.post(@endpoint, message, hdr)
19
+ if(resp.status == 200)
20
+ # Version 1.1 of WinRM adds the namespaces in the document instead of the envelope so we have to
21
+ # add them ourselves here. This should have no affect version 2.
22
+ doc = Nokogiri::XML(resp.body.content)
23
+ doc.collect_namespaces.each_pair do |k,v|
24
+ doc.root.add_namespace((k.split(/:/).last),v) unless doc.namespaces.has_key?(k)
25
+ end
26
+ return doc
27
+ else
28
+ puts "RESPONSE was #{resp.status}"
29
+ # TODO: raise error
30
+ end
31
+ end
32
+
33
+ # This will remove Negotiate authentication for plaintext and SSL because it supercedes Basic
34
+ # when it shouldn't for these types of transports.
35
+ def basic_auth_only!
36
+ auths = @httpcli.www_auth.instance_variable_get('@authenticator')
37
+ auths.delete_if {|i| i.scheme !~ /basic/i}
38
+ end
39
+ end
40
+
41
+ class HttpPlaintext < HttpTransport
42
+ def initialize(endpoint, user, pass)
43
+ super(endpoint)
44
+ @httpcli.set_auth(nil, user, pass)
45
+ basic_auth_only!
46
+ end
47
+ end
48
+
49
+ # Uses SSL to secure the transport
50
+ class HttpSSL < HttpTransport
51
+ def initialize(endpoint, user, pass, ca_trust_path = nil)
52
+ super(endpoint)
53
+ @httpcli.set_auth(endpoint, user, pass)
54
+ @httpcli.ssl_config.set_trust_ca(ca_trust_path) unless ca_trust_path.nil?
55
+ basic_auth_only!
56
+ end
57
+ end
58
+
59
+ # Uses Kerberos/GSSAPI to authenticate and encrypt messages
60
+ class HttpGSSAPI < HttpTransport
61
+ # @param [String,URI] endpoint the WinRM webservice endpoint
62
+ # @param [String] realm the Kerberos realm we are authenticating to
63
+ # @param [String<optional>] service the service name, default is HTTP
64
+ # @param [String<optional>] keytab the path to a keytab file if you are using one
65
+ def initialize(endpoint, realm, service = nil, keytab = nil)
66
+ super(endpoint)
67
+ service ||= 'HTTP'
68
+ @service = "#{service}/#{@endpoint.host}@#{realm}"
69
+ init_krb
70
+ end
71
+
72
+ def set_auth(user,pass)
73
+ # raise Error
74
+ end
75
+
76
+ def send_request(msg)
77
+ original_length = msg.length
78
+ emsg = winrm_encrypt(msg)
79
+ hdr = {
80
+ "Connection" => "Keep-Alive",
81
+ "Content-Type" => "multipart/encrypted;protocol=\"application/HTTP-Kerberos-session-encrypted\";boundary=\"Encrypted Boundary\""
82
+ }
83
+
84
+ body = <<-EOF
85
+ --Encrypted Boundary\r
86
+ Content-Type: application/HTTP-Kerberos-session-encrypted\r
87
+ OriginalContent: type=application/soap+xml;charset=UTF-8;Length=#{original_length}\r
88
+ --Encrypted Boundary\r
89
+ Content-Type: application/octet-stream\r
90
+ #{emsg}--Encrypted Boundary\r
91
+ EOF
92
+
93
+ r = @httpcli.post(@endpoint, body, hdr)
94
+
95
+ winrm_decrypt(r.body.content)
96
+ end
97
+
98
+
99
+ private
100
+
101
+
102
+ def init_krb
103
+ @gsscli = GSSAPI::Simple.new(@endpoint.host, @service)
104
+ token = @gsscli.init_context
105
+ auth = Base64.strict_encode64 token
106
+
107
+ hdr = {"Authorization" => "Kerberos #{auth}",
108
+ "Connection" => "Keep-Alive",
109
+ "Content-Type" => "application/soap+xml;charset=UTF-8"
110
+ }
111
+ r = @httpcli.post(@endpoint, '', hdr)
112
+ itok = r.header["WWW-Authenticate"].pop
113
+ itok = itok.split.last
114
+ itok = Base64.strict_decode64(itok)
115
+ @gsscli.init_context(itok)
116
+ end
117
+
118
+ # @return [String] the encrypted request string
119
+ def winrm_encrypt(str)
120
+ iov_cnt = 2
121
+ iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)
122
+
123
+ iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
124
+ iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
125
+
126
+ iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1)))
127
+ iov1[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA)
128
+ iov1[:buffer].value = str
129
+
130
+ conf_state = FFI::MemoryPointer.new :uint32
131
+ min_stat = FFI::MemoryPointer.new :uint32
132
+
133
+ maj_stat = GSSAPI::LibGSSAPI.gss_wrap_iov(min_stat, @gsscli.context, 1, 0, conf_state, iov, iov_cnt)
134
+
135
+ token = [iov0[:buffer].length].pack('L')
136
+ token += iov0[:buffer].value
137
+ token += iov1[:buffer].value
138
+ end
139
+
140
+
141
+ # @return [String] the unencrypted response string
142
+ def winrm_decrypt(str)
143
+ iov_cnt = 2
144
+ iov = FFI::MemoryPointer.new(GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * iov_cnt)
145
+
146
+ iov0 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address))
147
+ iov0[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_HEADER | GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_FLAG_ALLOCATE)
148
+
149
+ iov1 = GSSAPI::LibGSSAPI::GssIOVBufferDesc.new(FFI::Pointer.new(iov.address + (GSSAPI::LibGSSAPI::GssIOVBufferDesc.size * 1)))
150
+ iov1[:type] = (GSSAPI::LibGSSAPI::GSS_IOV_BUFFER_TYPE_DATA)
151
+
152
+ str.force_encoding('BINARY')
153
+ str.sub!(/^.*Content-Type: application\/octet-stream\r\n(.*)--Encrypted.*$/m, '\1')
154
+
155
+ len = str.unpack("L").first
156
+ iov_data = str.unpack("LA#{len}A*")
157
+ iov0[:buffer].value = iov_data[1]
158
+ iov1[:buffer].value = iov_data[2]
159
+
160
+ min_stat = FFI::MemoryPointer.new :uint32
161
+ conf_state = FFI::MemoryPointer.new :uint32
162
+ conf_state.write_int(1)
163
+ qop_state = FFI::MemoryPointer.new :uint32
164
+ qop_state.write_int(0)
165
+
166
+ maj_stat = GSSAPI::LibGSSAPI.gss_unwrap_iov(min_stat, @gsscli.context, conf_state, qop_state, iov, iov_cnt)
167
+
168
+ Nokogiri::XML(iov1[:buffer].value)
169
+ end
170
+
171
+ end
172
+ end
173
+ end # WinRM
@@ -0,0 +1,43 @@
1
+ #############################################################################
2
+ # Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
3
+ #
4
+ #
5
+ # This file is part of WinRM.
6
+ #
7
+ # WinRM is free software: you can redistribute it and/or
8
+ # modify it under the terms of the GNU General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or (at
10
+ # your option) any later version.
11
+ #
12
+ # WinRM is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15
+ # Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License along
18
+ # with WinRM. If not, see <http://www.gnu.org/licenses/>.
19
+ #############################################################################
20
+
21
+ require 'httpclient'
22
+ require 'savon/soap/xml'
23
+ require 'uuid'
24
+ require 'gssapi'
25
+ require 'base64'
26
+ require 'nokogiri'
27
+
28
+ module WinRM
29
+ NS_SOAP_ENV ='s' # http://www.w3.org/2003/05/soap-envelope
30
+ NS_ADDRESSING ='a' # http://schemas.xmlsoap.org/ws/2004/08/addressing
31
+ NS_CIMBINDING ='b' # http://schemas.dmtf.org/wbem/wsman/1/cimbinding.xsd
32
+ NS_ENUM ='n' # http://schemas.xmlsoap.org/ws/2004/09/enumeration
33
+ NS_TRANSFER ='x' # http://schemas.xmlsoap.org/ws/2004/09/transfer
34
+ NS_WSMAN_DMTF ='w' # http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd
35
+ NS_WSMAN_MSFT ='p' # http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd
36
+ NS_SCHEMA_INST ='xsi' # http://www.w3.org/2001/XMLSchema-instance
37
+ NS_WIN_SHELL ='rsp' # http://schemas.microsoft.com/wbem/wsman/1/windows/shell
38
+ NS_WSMAN_FAULT = 'f' # http://schemas.microsoft.com/wbem/wsman/1/wsmanfault
39
+ end
40
+
41
+ require 'winrm/exceptions/exceptions'
42
+ require 'winrm/winrm_service'
43
+ require 'winrm/http/transport'