winrm 0.0.6 → 1.0.0rc1

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