rbvmomi2 3.0.0
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 +19 -0
- data/README.md +114 -0
- data/exe/rbvmomish +138 -0
- data/lib/rbvmomi/basic_types.rb +383 -0
- data/lib/rbvmomi/connection.rb +272 -0
- data/lib/rbvmomi/deserialization.rb +249 -0
- data/lib/rbvmomi/fault.rb +19 -0
- data/lib/rbvmomi/optimist.rb +72 -0
- data/lib/rbvmomi/pbm.rb +68 -0
- data/lib/rbvmomi/sms/SmsStorageManager.rb +10 -0
- data/lib/rbvmomi/sms.rb +63 -0
- data/lib/rbvmomi/sso.rb +313 -0
- data/lib/rbvmomi/trivial_soap.rb +122 -0
- data/lib/rbvmomi/type_loader.rb +138 -0
- data/lib/rbvmomi/utils/admission_control.rb +401 -0
- data/lib/rbvmomi/utils/deploy.rb +318 -0
- data/lib/rbvmomi/utils/leases.rb +145 -0
- data/lib/rbvmomi/utils/perfdump.rb +631 -0
- data/lib/rbvmomi/version.rb +6 -0
- data/lib/rbvmomi/vim/ComputeResource.rb +54 -0
- data/lib/rbvmomi/vim/Datacenter.rb +25 -0
- data/lib/rbvmomi/vim/Datastore.rb +72 -0
- data/lib/rbvmomi/vim/DynamicTypeMgrAllTypeInfo.rb +78 -0
- data/lib/rbvmomi/vim/DynamicTypeMgrDataTypeInfo.rb +23 -0
- data/lib/rbvmomi/vim/DynamicTypeMgrManagedTypeInfo.rb +54 -0
- data/lib/rbvmomi/vim/Folder.rb +214 -0
- data/lib/rbvmomi/vim/HostSystem.rb +177 -0
- data/lib/rbvmomi/vim/ManagedEntity.rb +60 -0
- data/lib/rbvmomi/vim/ManagedObject.rb +63 -0
- data/lib/rbvmomi/vim/ObjectContent.rb +26 -0
- data/lib/rbvmomi/vim/ObjectUpdate.rb +26 -0
- data/lib/rbvmomi/vim/OvfManager.rb +204 -0
- data/lib/rbvmomi/vim/PerfCounterInfo.rb +28 -0
- data/lib/rbvmomi/vim/PerformanceManager.rb +113 -0
- data/lib/rbvmomi/vim/PropertyCollector.rb +28 -0
- data/lib/rbvmomi/vim/ReflectManagedMethodExecuter.rb +33 -0
- data/lib/rbvmomi/vim/ResourcePool.rb +58 -0
- data/lib/rbvmomi/vim/ServiceInstance.rb +58 -0
- data/lib/rbvmomi/vim/Task.rb +68 -0
- data/lib/rbvmomi/vim/VirtualMachine.rb +75 -0
- data/lib/rbvmomi/vim.rb +157 -0
- data/lib/rbvmomi.rb +16 -0
- data/lib/rbvmomi2.rb +3 -0
- data/vmodl.db +0 -0
- metadata +214 -0
data/lib/rbvmomi/sms.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# Copyright (c) 2013-2017 VMware, Inc. All Rights Reserved.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
4
|
+
require_relative '../rbvmomi'
|
5
|
+
module RbVmomi
|
6
|
+
|
7
|
+
# A connection to one vSphere SMS endpoint.
|
8
|
+
# @see #serviceInstance
|
9
|
+
class SMS < Connection
|
10
|
+
# Connect to a vSphere SMS endpoint
|
11
|
+
#
|
12
|
+
# @param [VIM] Connection to main vSphere API endpoint
|
13
|
+
# @param [Hash] opts The options hash.
|
14
|
+
# @option opts [String] :host Host to connect to.
|
15
|
+
# @option opts [Numeric] :port (443) Port to connect to.
|
16
|
+
# @option opts [Boolean] :ssl (true) Whether to use SSL.
|
17
|
+
# @option opts [Boolean] :insecure (false) If true, ignore SSL certificate errors.
|
18
|
+
# @option opts [String] :path (/sms/sdk) SDK endpoint path.
|
19
|
+
# @option opts [Boolean] :debug (false) If true, print SOAP traffic to stderr.
|
20
|
+
def self.connect vim, opts = {}
|
21
|
+
fail unless opts.is_a? Hash
|
22
|
+
opts[:host] = vim.host
|
23
|
+
opts[:ssl] = true unless opts.member? :ssl or opts[:"no-ssl"]
|
24
|
+
opts[:insecure] ||= true
|
25
|
+
opts[:port] ||= (opts[:ssl] ? 443 : 80)
|
26
|
+
opts[:path] ||= '/sms/sdk'
|
27
|
+
opts[:ns] ||= 'urn:sms'
|
28
|
+
rev_given = opts[:rev] != nil
|
29
|
+
opts[:rev] = '4.0' unless rev_given
|
30
|
+
opts[:debug] = (!ENV['RBVMOMI_DEBUG'].empty? rescue false) unless opts.member? :debug
|
31
|
+
|
32
|
+
new(opts).tap do |sms|
|
33
|
+
sms.vcSessionCookie = vim.cookie.split('"')[1]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def vcSessionCookie= cookie
|
38
|
+
@vcSessionCookie = cookie
|
39
|
+
end
|
40
|
+
|
41
|
+
def rev= x
|
42
|
+
super
|
43
|
+
@serviceContent = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return the ServiceInstance
|
47
|
+
#
|
48
|
+
# The ServiceInstance is the root of the vSphere inventory.
|
49
|
+
def serviceInstance
|
50
|
+
@serviceInstance ||= VIM::SmsServiceInstance self, 'ServiceInstance'
|
51
|
+
end
|
52
|
+
|
53
|
+
# @private
|
54
|
+
def pretty_print pp
|
55
|
+
pp.text "SMS(#{@opts[:host]})"
|
56
|
+
end
|
57
|
+
|
58
|
+
add_extension_dir File.join(File.dirname(__FILE__), "sms")
|
59
|
+
load_vmodl(ENV['VMODL'] || File.join(File.dirname(__FILE__), "../../vmodl.db"))
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
data/lib/rbvmomi/sso.rb
ADDED
@@ -0,0 +1,313 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'net/https'
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'openssl'
|
5
|
+
require 'securerandom'
|
6
|
+
require 'time'
|
7
|
+
|
8
|
+
module RbVmomi
|
9
|
+
# Provides access to vCenter Single Sign-On
|
10
|
+
class SSO
|
11
|
+
BST_PROFILE = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3'.freeze
|
12
|
+
C14N_CLASS = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
13
|
+
C14N_METHOD = 'http://www.w3.org/2001/10/xml-exc-c14n#'.freeze
|
14
|
+
DIGEST_METHOD = 'http://www.w3.org/2001/04/xmlenc#sha512'.freeze
|
15
|
+
ENCODING_METHOD = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary'.freeze
|
16
|
+
SIGNATURE_METHOD = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'.freeze
|
17
|
+
STS_PATH = '/sts/STSService'.freeze
|
18
|
+
TOKEN_TYPE = 'urn:oasis:names:tc:SAML:2.0:assertion'.freeze
|
19
|
+
TOKEN_PROFILE = 'http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV2.0'.freeze
|
20
|
+
NAMESPACES = {
|
21
|
+
:ds => 'http://www.w3.org/2000/09/xmldsig#',
|
22
|
+
:soap => 'http://schemas.xmlsoap.org/soap/envelope/',
|
23
|
+
:wsse => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd',
|
24
|
+
:wsse11 => 'http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd',
|
25
|
+
:wst => 'http://docs.oasis-open.org/ws-sx/ws-trust/200512',
|
26
|
+
:wsu => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
|
27
|
+
}.freeze
|
28
|
+
|
29
|
+
attr_reader :assertion,
|
30
|
+
:assertion_id,
|
31
|
+
:certificate,
|
32
|
+
:host,
|
33
|
+
:user,
|
34
|
+
:password,
|
35
|
+
:path,
|
36
|
+
:port,
|
37
|
+
:private_key
|
38
|
+
|
39
|
+
# Creates an instance of an SSO object
|
40
|
+
#
|
41
|
+
# @param [Hash] opts the options to create the object with
|
42
|
+
# @option opts [String] :host the host to connect to
|
43
|
+
# @option opts [Fixnum] :port (443) the port to connect to
|
44
|
+
# @option opts [String] :path the path to call
|
45
|
+
# @option opts [String] :user the user to authenticate with
|
46
|
+
# @option opts [String] :password the password to authenticate with
|
47
|
+
# @option opts [String] :private_key the private key to use
|
48
|
+
# @option opts [String] :certificate the certificate to use
|
49
|
+
# @option opts [Boolean] :insecure (false) whether to connect insecurely
|
50
|
+
def initialize(opts = {})
|
51
|
+
@host = opts[:host]
|
52
|
+
@insecure = opts.fetch(:insecure, false)
|
53
|
+
@password = opts[:password]
|
54
|
+
@path = opts.fetch(:path, STS_PATH)
|
55
|
+
@port = opts.fetch(:port, 443)
|
56
|
+
@user = opts[:user]
|
57
|
+
|
58
|
+
load_x509(opts[:private_key], opts[:certificate])
|
59
|
+
end
|
60
|
+
|
61
|
+
def request_token
|
62
|
+
req = sso_call(hok_token_request)
|
63
|
+
|
64
|
+
unless req.is_a?(Net::HTTPSuccess)
|
65
|
+
resp = Nokogiri::XML(req.body)
|
66
|
+
resp.remove_namespaces!
|
67
|
+
raise(resp.at_xpath('//Envelope/Body/Fault/faultstring/text()'))
|
68
|
+
end
|
69
|
+
|
70
|
+
extract_assertion(req.body)
|
71
|
+
end
|
72
|
+
|
73
|
+
def sign_request(request)
|
74
|
+
raise('Need SAML2 assertion') unless @assertion
|
75
|
+
raise('No SAML2 assertion ID') unless @assertion_id
|
76
|
+
|
77
|
+
request_id = generate_id
|
78
|
+
timestamp_id = generate_id
|
79
|
+
|
80
|
+
request = request.is_a?(String) ? Nokogiri::XML(request) : request
|
81
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
82
|
+
xml[:soap].Header(Hash[NAMESPACES.map { |ns, uri| ["xmlns:#{ns}", uri] }]) do
|
83
|
+
xml[:wsse].Security do
|
84
|
+
wsu_timestamp(xml, timestamp_id)
|
85
|
+
ds_signature(xml, request_id, timestamp_id) do |x|
|
86
|
+
x[:wsse].SecurityTokenReference('wsse11:TokenType' => TOKEN_PROFILE) do
|
87
|
+
x[:wsse].KeyIdentifier(
|
88
|
+
@assertion_id,
|
89
|
+
'ValueType' => 'http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID'
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# To avoid Nokogiri mangling the token, we replace it as a string
|
98
|
+
# later on. Figure out a way around this.
|
99
|
+
builder.doc.at_xpath('//soap:Header/wsse:Security/wsu:Timestamp').add_previous_sibling(Nokogiri::XML::Text.new('SAML_ASSERTION_PLACEHOLDER', builder.doc))
|
100
|
+
|
101
|
+
request.at_xpath('//soap:Envelope', NAMESPACES).tap do |e|
|
102
|
+
NAMESPACES.each do |ns, uri|
|
103
|
+
e.add_namespace(ns.to_s, uri)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
request.xpath('//soap:Envelope/soap:Body').each do |body|
|
107
|
+
body.add_previous_sibling(builder.doc.root)
|
108
|
+
body.add_namespace('wsu', NAMESPACES[:wsu])
|
109
|
+
body['wsu:Id'] = request_id
|
110
|
+
end
|
111
|
+
|
112
|
+
signed = sign(request)
|
113
|
+
signed.gsub!('SAML_ASSERTION_PLACEHOLDER', @assertion.to_xml(:indent => 0, :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML).strip)
|
114
|
+
|
115
|
+
signed
|
116
|
+
end
|
117
|
+
|
118
|
+
# We default to Issue, since that's all we currently need.
|
119
|
+
def sso_call(body)
|
120
|
+
sso_url = URI::HTTPS.build(:host => @host, :port => @port, :path => @path)
|
121
|
+
http = Net::HTTP.new(sso_url.host, sso_url.port)
|
122
|
+
http.use_ssl = true
|
123
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @insecure
|
124
|
+
|
125
|
+
req = Net::HTTP::Post.new(sso_url.request_uri)
|
126
|
+
req.add_field('Accept', 'text/xml, multipart/related')
|
127
|
+
req.add_field('User-Agent', "VMware/RbVmomi #{RbVmomi::VERSION}")
|
128
|
+
req.add_field('SOAPAction', 'http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue')
|
129
|
+
req.content_type = 'text/xml; charset="UTF-8"'
|
130
|
+
req.body = body
|
131
|
+
|
132
|
+
http.request(req)
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def hok_token_request
|
138
|
+
request_id = generate_id
|
139
|
+
security_token_id = generate_id
|
140
|
+
signature_id = generate_id
|
141
|
+
timestamp_id = generate_id
|
142
|
+
|
143
|
+
datum = Time.now.utc
|
144
|
+
created_at = datum.iso8601
|
145
|
+
token_expires_at = (datum + 1800).iso8601
|
146
|
+
|
147
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
148
|
+
xml[:soap].Envelope(Hash[NAMESPACES.map { |ns, uri| ["xmlns:#{ns}", uri] }]) do
|
149
|
+
xml[:soap].Header do
|
150
|
+
xml[:wsse].Security do
|
151
|
+
wsu_timestamp(xml, timestamp_id, datum)
|
152
|
+
wsse_username_token(xml)
|
153
|
+
wsse_binary_security_token(xml, security_token_id)
|
154
|
+
ds_signature(xml, request_id, timestamp_id, signature_id) do |x|
|
155
|
+
x[:wsse].SecurityTokenReference do
|
156
|
+
x[:wsse].Reference(
|
157
|
+
'URI' => "##{security_token_id}",
|
158
|
+
'ValueType' => BST_PROFILE
|
159
|
+
)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
xml[:soap].Body('wsu:Id' => request_id) do
|
165
|
+
xml[:wst].RequestSecurityToken do
|
166
|
+
xml[:wst].TokenType(TOKEN_TYPE)
|
167
|
+
xml[:wst].RequestType('http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue')
|
168
|
+
xml[:wst].Lifetime do
|
169
|
+
xml[:wsu].Created(created_at)
|
170
|
+
xml[:wsu].Expires(token_expires_at)
|
171
|
+
end
|
172
|
+
xml[:wst].Renewing('Allow' => 'false', 'OK' => 'false')
|
173
|
+
xml[:wst].KeyType('http://docs.oasis-open.org/ws-sx/ws-trust/200512/PublicKey')
|
174
|
+
xml[:wst].SignatureAlgorithm(SIGNATURE_METHOD)
|
175
|
+
xml[:wst].Delegatable('false')
|
176
|
+
end
|
177
|
+
xml[:wst].UseKey('Sig' => signature_id)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
sign(builder.doc)
|
183
|
+
end
|
184
|
+
|
185
|
+
def extract_assertion(sso_response)
|
186
|
+
sso_response = Nokogiri::XML(sso_response) if sso_response.is_a?(String)
|
187
|
+
namespaces = sso_response.collect_namespaces
|
188
|
+
|
189
|
+
# Doesn't matter that usually there's more than one NS with the same
|
190
|
+
# URI - either will work for XPath. We just don't want to hardcode
|
191
|
+
# xmlns:saml2.
|
192
|
+
token_ns = namespaces.find { |_, uri| uri == TOKEN_TYPE }.first.gsub(/^xmlns:/, '')
|
193
|
+
|
194
|
+
@assertion = sso_response.at_xpath("//#{token_ns}:Assertion", namespaces)
|
195
|
+
@assertion_id = @assertion.at_xpath("//#{token_ns}:Assertion/@ID", namespaces).value
|
196
|
+
end
|
197
|
+
|
198
|
+
def sign(doc)
|
199
|
+
signature_digest_references = doc.xpath('/soap:Envelope/soap:Header/wsse:Security/ds:Signature/ds:SignedInfo/ds:Reference/@URI', doc.collect_namespaces).map { |a| a.value.sub(/^#/, '') }
|
200
|
+
signature_digest_references.each do |ref|
|
201
|
+
data = doc.at_xpath("//*[@wsu:Id='#{ref}']", doc.collect_namespaces)
|
202
|
+
digest = Base64.strict_encode64(Digest::SHA2.new(512).digest(data.canonicalize(C14N_CLASS)))
|
203
|
+
digest_tag = doc.at_xpath("/soap:Envelope/soap:Header/wsse:Security/ds:Signature/ds:SignedInfo/ds:Reference[@URI='##{ref}']/ds:DigestValue", doc.collect_namespaces)
|
204
|
+
digest_tag.add_child(Nokogiri::XML::Text.new(digest, doc))
|
205
|
+
end
|
206
|
+
|
207
|
+
signed_info = doc.at_xpath('/soap:Envelope/soap:Header/wsse:Security/ds:Signature/ds:SignedInfo', doc.collect_namespaces)
|
208
|
+
signature = Base64.strict_encode64(@private_key.sign(OpenSSL::Digest::SHA512.new, signed_info.canonicalize(C14N_CLASS)))
|
209
|
+
signature_value_tag = doc.at_xpath('/soap:Envelope/soap:Header/wsse:Security/ds:Signature/ds:SignatureValue', doc.collect_namespaces)
|
210
|
+
signature_value_tag.add_child(Nokogiri::XML::Text.new(signature, doc))
|
211
|
+
|
212
|
+
doc.to_xml(:indent => 0, :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML).strip
|
213
|
+
end
|
214
|
+
|
215
|
+
def load_x509(private_key, certificate)
|
216
|
+
@private_key = private_key ? private_key : OpenSSL::PKey::RSA.new(2048)
|
217
|
+
if @private_key.is_a? String
|
218
|
+
@private_key = OpenSSL::PKey::RSA.new(@private_key)
|
219
|
+
end
|
220
|
+
|
221
|
+
@certificate = certificate
|
222
|
+
if @certificate && !private_key
|
223
|
+
raise(ArgumentError, "Can't generate private key from a certificate")
|
224
|
+
end
|
225
|
+
|
226
|
+
if @certificate.is_a? String
|
227
|
+
@certificate = OpenSSL::X509::Certificate.new(@certificate)
|
228
|
+
end
|
229
|
+
# If only a private key is specified, we will generate a certificate.
|
230
|
+
unless @certificate
|
231
|
+
timestamp = Time.now.utc
|
232
|
+
@certificate = OpenSSL::X509::Certificate.new
|
233
|
+
@certificate.not_before = timestamp
|
234
|
+
@certificate.not_after = timestamp + 3600 # 3600 is 1 hour
|
235
|
+
@certificate.subject = OpenSSL::X509::Name.new([
|
236
|
+
%w[O VMware],
|
237
|
+
%w[OU RbVmomi],
|
238
|
+
%W[CN #{@user}]
|
239
|
+
])
|
240
|
+
@certificate.issuer = @certificate.subject
|
241
|
+
@certificate.serial = rand(2**160)
|
242
|
+
@certificate.public_key = @private_key.public_key
|
243
|
+
@certificate.sign(@private_key, OpenSSL::Digest::SHA512.new)
|
244
|
+
end
|
245
|
+
|
246
|
+
true
|
247
|
+
end
|
248
|
+
|
249
|
+
def ds_signature(xml, request_id, timestamp_id, id = nil)
|
250
|
+
signature_id = {}
|
251
|
+
signature_id['Id'] = id if id
|
252
|
+
xml[:ds].Signature(signature_id) do
|
253
|
+
ds_signed_info(xml, request_id, timestamp_id)
|
254
|
+
xml[:ds].SignatureValue
|
255
|
+
xml[:ds].KeyInfo do
|
256
|
+
yield xml
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def ds_signed_info(xml, request_id, timestamp_id)
|
262
|
+
xml[:ds].SignedInfo do
|
263
|
+
xml[:ds].CanonicalizationMethod('Algorithm' => C14N_METHOD)
|
264
|
+
xml[:ds].SignatureMethod('Algorithm' => SIGNATURE_METHOD)
|
265
|
+
xml[:ds].Reference('URI' => "##{request_id}") do
|
266
|
+
xml[:ds].Transforms do
|
267
|
+
xml[:ds].Transform('Algorithm' => C14N_METHOD)
|
268
|
+
end
|
269
|
+
xml[:ds].DigestMethod('Algorithm' => DIGEST_METHOD)
|
270
|
+
xml[:ds].DigestValue
|
271
|
+
end
|
272
|
+
xml[:ds].Reference('URI' => "##{timestamp_id}") do
|
273
|
+
xml[:ds].Transforms do
|
274
|
+
xml[:ds].Transform('Algorithm' => C14N_METHOD)
|
275
|
+
end
|
276
|
+
xml[:ds].DigestMethod('Algorithm' => DIGEST_METHOD)
|
277
|
+
xml[:ds].DigestValue
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def wsu_timestamp(xml, id, datum = nil)
|
283
|
+
datum ||= Time.now.utc
|
284
|
+
created_at = datum.iso8601
|
285
|
+
expires_at = (datum + 600).iso8601
|
286
|
+
|
287
|
+
xml[:wsu].Timestamp('wsu:Id' => id) do
|
288
|
+
xml[:wsu].Created(created_at)
|
289
|
+
xml[:wsu].Expires(expires_at)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
def wsse_username_token(xml)
|
294
|
+
xml[:wsse].UsernameToken do
|
295
|
+
xml[:wsse].Username(@user)
|
296
|
+
xml[:wsse].Password(@password)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def wsse_binary_security_token(xml, id)
|
301
|
+
xml[:wsse].BinarySecurityToken(
|
302
|
+
Base64.strict_encode64(@certificate.to_der),
|
303
|
+
'EncodingType' => ENCODING_METHOD,
|
304
|
+
'ValueType' => BST_PROFILE,
|
305
|
+
'wsu:Id' => id
|
306
|
+
)
|
307
|
+
end
|
308
|
+
|
309
|
+
def generate_id
|
310
|
+
"_#{SecureRandom.uuid}"
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# Copyright (c) 2011-2017 VMware, Inc. All Rights Reserved.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'builder'
|
6
|
+
require 'nokogiri'
|
7
|
+
require 'net/http'
|
8
|
+
require 'pp'
|
9
|
+
|
10
|
+
class RbVmomi::TrivialSoap
|
11
|
+
attr_accessor :debug, :cookie, :operation_id
|
12
|
+
attr_reader :http
|
13
|
+
|
14
|
+
def initialize opts
|
15
|
+
fail unless opts.is_a? Hash
|
16
|
+
@opts = opts
|
17
|
+
return unless @opts[:host] # for testcases
|
18
|
+
@debug = @opts[:debug]
|
19
|
+
@cookie = @opts[:cookie]
|
20
|
+
@sso = @opts[:sso]
|
21
|
+
@operation_id = @opts[:operation_id]
|
22
|
+
@lock = Mutex.new
|
23
|
+
@http = nil
|
24
|
+
restart_http
|
25
|
+
end
|
26
|
+
|
27
|
+
def host
|
28
|
+
@opts[:host]
|
29
|
+
end
|
30
|
+
|
31
|
+
def close
|
32
|
+
@http.finish rescue IOError
|
33
|
+
end
|
34
|
+
|
35
|
+
def restart_http
|
36
|
+
begin
|
37
|
+
@http.finish if @http
|
38
|
+
rescue Exception => ex
|
39
|
+
puts "WARNING: Ignoring exception: #{ex.message}"
|
40
|
+
puts ex.backtrace.join("\n")
|
41
|
+
end
|
42
|
+
@http = Net::HTTP.new(@opts[:host], @opts[:port], @opts[:proxyHost], @opts[:proxyPort])
|
43
|
+
if @opts[:ssl]
|
44
|
+
require 'net/https'
|
45
|
+
@http.use_ssl = true
|
46
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @opts[:insecure]
|
47
|
+
@http.ca_file = @opts[:ca_file] if @opts[:ca_file]
|
48
|
+
@http.cert = OpenSSL::X509::Certificate.new(@opts[:cert]) if @opts[:cert]
|
49
|
+
@http.key = OpenSSL::PKey::RSA.new(@opts[:key]) if @opts[:key]
|
50
|
+
end
|
51
|
+
@http.set_debug_output(STDERR) if $DEBUG
|
52
|
+
@http.read_timeout = @opts[:read_timeout] || 1000000
|
53
|
+
@http.open_timeout = @opts[:open_timeout] || 60
|
54
|
+
def @http.on_connect
|
55
|
+
@socket.io.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
56
|
+
end
|
57
|
+
@http.start
|
58
|
+
end
|
59
|
+
|
60
|
+
def soap_envelope
|
61
|
+
xsd = 'http://www.w3.org/2001/XMLSchema'
|
62
|
+
env = 'http://schemas.xmlsoap.org/soap/envelope/'
|
63
|
+
xsi = 'http://www.w3.org/2001/XMLSchema-instance'
|
64
|
+
xml = Builder::XmlMarkup.new :indent => 0
|
65
|
+
xml.tag!('env:Envelope', 'xmlns:xsd' => xsd, 'xmlns:env' => env, 'xmlns:xsi' => xsi) do
|
66
|
+
if @vcSessionCookie || @operation_id
|
67
|
+
xml.tag!('env:Header') do
|
68
|
+
xml.tag!('vcSessionCookie', @vcSessionCookie) if @vcSessionCookie
|
69
|
+
xml.tag!('operationID', @operation_id) if @operation_id
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
xml.tag!('env:Body') do
|
74
|
+
yield xml if block_given?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
xml
|
78
|
+
end
|
79
|
+
|
80
|
+
def request action, body
|
81
|
+
headers = { 'content-type' => 'text/xml; charset=utf-8', 'SOAPAction' => action }
|
82
|
+
headers['cookie'] = @cookie if @cookie
|
83
|
+
|
84
|
+
if @debug
|
85
|
+
$stderr.puts "Request:"
|
86
|
+
$stderr.puts body
|
87
|
+
$stderr.puts
|
88
|
+
end
|
89
|
+
|
90
|
+
if @cookie.nil? && @sso
|
91
|
+
@sso.request_token unless @sso.assertion_id
|
92
|
+
body = @sso.sign_request(body)
|
93
|
+
end
|
94
|
+
|
95
|
+
start_time = Time.now
|
96
|
+
response = @lock.synchronize do
|
97
|
+
begin
|
98
|
+
@http.request_post(@opts[:path], body, headers)
|
99
|
+
rescue Exception
|
100
|
+
restart_http
|
101
|
+
raise
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end_time = Time.now
|
105
|
+
|
106
|
+
if response.is_a? Net::HTTPServiceUnavailable
|
107
|
+
raise "Got HTTP 503: Service unavailable"
|
108
|
+
end
|
109
|
+
|
110
|
+
self.cookie = response['set-cookie'] if response.key? 'set-cookie'
|
111
|
+
|
112
|
+
nk = Nokogiri(response.body)
|
113
|
+
|
114
|
+
if @debug
|
115
|
+
$stderr.puts "Response (in #{'%.3f' % (end_time - start_time)} s)"
|
116
|
+
$stderr.puts nk
|
117
|
+
$stderr.puts
|
118
|
+
end
|
119
|
+
|
120
|
+
[nk.xpath('//soapenv:Body/*').select(&:element?).first, response.body.size]
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# Copyright (c) 2011-2017 VMware, Inc. All Rights Reserved.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
4
|
+
require 'set'
|
5
|
+
require 'monitor'
|
6
|
+
|
7
|
+
module RbVmomi
|
8
|
+
|
9
|
+
class TypeLoader
|
10
|
+
def initialize fn, extension_dirs, namespace
|
11
|
+
@extension_dirs = extension_dirs
|
12
|
+
@namespace = namespace
|
13
|
+
@lock = Monitor.new
|
14
|
+
@db = {}
|
15
|
+
@id2wsdl = {}
|
16
|
+
@loaded = {}
|
17
|
+
add_types Hash[BasicTypes::BUILTIN.map { |k| [k,nil] }]
|
18
|
+
vmodl_database = File.open(fn, 'r') { |io| Marshal.load io }
|
19
|
+
vmodl_database.reject! { |k,v| k =~ /^_/ }
|
20
|
+
add_types vmodl_database
|
21
|
+
preload
|
22
|
+
end
|
23
|
+
|
24
|
+
def preload
|
25
|
+
names = (@namespace.constants + Object.constants).map(&:to_s).uniq.
|
26
|
+
select { |x| has? x }
|
27
|
+
names.each { |x| get(x) }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Reload all extensions for loaded VMODL types
|
31
|
+
def reload_extensions
|
32
|
+
@extension_dirs.each do |path|
|
33
|
+
reload_extensions_dir path
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Reload all extensions for loaded VMODL types from the given directory
|
38
|
+
def reload_extensions_dir path
|
39
|
+
loaded = Set.new(typenames.select { |x| @namespace.const_defined? x })
|
40
|
+
Dir.open(path) do |dir|
|
41
|
+
dir.each do |file|
|
42
|
+
next unless file =~ /\.rb$/
|
43
|
+
next unless loaded.member? $`
|
44
|
+
file_path = File.join(dir, file)
|
45
|
+
load file_path
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def has? name
|
51
|
+
fail unless name.is_a? String
|
52
|
+
|
53
|
+
@db.member?(name) or BasicTypes::BUILTIN.member?(name)
|
54
|
+
end
|
55
|
+
|
56
|
+
def get name
|
57
|
+
fail "name '#{name}' is #{name.class} expecting String" unless name.is_a? String
|
58
|
+
|
59
|
+
first_char = name[0].chr
|
60
|
+
if first_char.downcase == first_char
|
61
|
+
name = "%s%s" % [first_char.upcase, name[1..-1]]
|
62
|
+
end
|
63
|
+
|
64
|
+
return @loaded[name] if @loaded.member? name
|
65
|
+
@lock.synchronize do
|
66
|
+
return @loaded[name] if @loaded.member? name
|
67
|
+
klass = make_type(name)
|
68
|
+
@namespace.const_set name, klass
|
69
|
+
load_extension name
|
70
|
+
@loaded[name] = klass
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_types types
|
75
|
+
@lock.synchronize do
|
76
|
+
@db.merge! types
|
77
|
+
@db = Hash[@db.map do |name, value|
|
78
|
+
if value
|
79
|
+
value['wsdl_name'] ||= name
|
80
|
+
end
|
81
|
+
first_char = name[0].chr
|
82
|
+
if first_char.downcase == first_char
|
83
|
+
name = "%s%s" % [first_char.upcase, name[1..-1]]
|
84
|
+
end
|
85
|
+
[name, value]
|
86
|
+
end]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def typenames
|
91
|
+
@db.keys
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def load_extension name
|
97
|
+
@extension_dirs.map { |x| File.join(x, "#{name}.rb") }.
|
98
|
+
select { |x| File.exist? x }.
|
99
|
+
each { |x| load x }
|
100
|
+
end
|
101
|
+
|
102
|
+
def make_type name
|
103
|
+
name = name.to_s
|
104
|
+
return BasicTypes.const_get(name) if BasicTypes::BUILTIN.member? name
|
105
|
+
desc = @db[name] or fail "unknown VMODL type #{name}"
|
106
|
+
case desc['kind']
|
107
|
+
when 'data' then make_data_type name, desc
|
108
|
+
when 'managed' then make_managed_type name, desc
|
109
|
+
when 'enum' then make_enum_type name, desc
|
110
|
+
else fail desc.inspect
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def make_data_type name, desc
|
115
|
+
superclass = get desc['wsdl_base']
|
116
|
+
Class.new(superclass).tap do |klass|
|
117
|
+
klass.init name, desc['props']
|
118
|
+
klass.wsdl_name = desc['wsdl_name']
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def make_managed_type name, desc
|
123
|
+
superclass = get desc['wsdl_base']
|
124
|
+
Class.new(superclass).tap do |klass|
|
125
|
+
klass.init name, desc['props'], desc['methods']
|
126
|
+
klass.wsdl_name = desc['wsdl_name']
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def make_enum_type name, desc
|
131
|
+
Class.new(BasicTypes::Enum).tap do |klass|
|
132
|
+
klass.init name, desc['values']
|
133
|
+
klass.wsdl_name = desc['wsdl_name']
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|