rbvmomi 1.8.2 → 2.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/LICENSE +1 -1
- data/README.md +114 -0
- data/{bin → exe}/rbvmomish +5 -5
- data/lib/rbvmomi.rb +11 -7
- data/lib/rbvmomi/basic_types.rb +9 -1
- data/lib/rbvmomi/connection.rb +11 -9
- data/lib/rbvmomi/deserialization.rb +4 -3
- data/lib/rbvmomi/fault.rb +3 -1
- data/lib/rbvmomi/{trollop.rb → optimist.rb} +10 -8
- data/lib/rbvmomi/pbm.rb +3 -1
- data/lib/rbvmomi/sms.rb +3 -1
- data/lib/rbvmomi/sms/SmsStorageManager.rb +3 -0
- data/lib/rbvmomi/sso.rb +313 -0
- data/lib/rbvmomi/trivial_soap.rb +19 -11
- data/lib/rbvmomi/type_loader.rb +4 -2
- data/lib/rbvmomi/utils/admission_control.rb +22 -19
- data/lib/rbvmomi/utils/deploy.rb +18 -14
- data/lib/rbvmomi/utils/leases.rb +4 -1
- data/lib/rbvmomi/utils/perfdump.rb +4 -1
- data/lib/rbvmomi/version.rb +6 -0
- data/lib/rbvmomi/vim.rb +41 -12
- data/lib/rbvmomi/vim/ComputeResource.rb +3 -0
- data/lib/rbvmomi/vim/Datacenter.rb +8 -0
- data/lib/rbvmomi/vim/Datastore.rb +5 -1
- data/lib/rbvmomi/vim/DynamicTypeMgrAllTypeInfo.rb +3 -0
- data/lib/rbvmomi/vim/DynamicTypeMgrDataTypeInfo.rb +4 -1
- data/lib/rbvmomi/vim/DynamicTypeMgrManagedTypeInfo.rb +15 -7
- data/lib/rbvmomi/vim/Folder.rb +19 -12
- data/lib/rbvmomi/vim/HostSystem.rb +5 -2
- data/lib/rbvmomi/vim/ManagedEntity.rb +3 -0
- data/lib/rbvmomi/vim/ManagedObject.rb +4 -1
- data/lib/rbvmomi/vim/ObjectContent.rb +3 -0
- data/lib/rbvmomi/vim/ObjectUpdate.rb +3 -0
- data/lib/rbvmomi/vim/OvfManager.rb +6 -2
- data/lib/rbvmomi/vim/PerfCounterInfo.rb +3 -1
- data/lib/rbvmomi/vim/PerformanceManager.rb +3 -0
- data/lib/rbvmomi/vim/PropertyCollector.rb +3 -0
- data/lib/rbvmomi/vim/ReflectManagedMethodExecuter.rb +5 -2
- data/lib/rbvmomi/vim/ResourcePool.rb +3 -0
- data/lib/rbvmomi/vim/ServiceInstance.rb +3 -0
- data/lib/rbvmomi/vim/Task.rb +3 -0
- data/lib/rbvmomi/vim/VirtualMachine.rb +13 -12
- data/vmodl.db +0 -0
- metadata +100 -63
- data/.yardopts +0 -6
- data/README.rdoc +0 -78
- data/Rakefile +0 -45
- data/VERSION +0 -1
- data/devel/analyze-vim-declarations.rb +0 -213
- data/devel/analyze-xml.rb +0 -46
- data/devel/benchmark.rb +0 -117
- data/devel/collisions.rb +0 -18
- data/devel/merge-internal-vmodl.rb +0 -59
- data/devel/merge-manual-vmodl.rb +0 -32
- data/examples/annotate.rb +0 -54
- data/examples/cached_ovf_deploy.rb +0 -120
- data/examples/clone_vm.rb +0 -84
- data/examples/create_vm-1.9.rb +0 -93
- data/examples/create_vm.rb +0 -93
- data/examples/extraConfig.rb +0 -54
- data/examples/lease_tool.rb +0 -102
- data/examples/logbundle.rb +0 -63
- data/examples/logtail.rb +0 -60
- data/examples/nfs_datastore.rb +0 -95
- data/examples/power.rb +0 -59
- data/examples/readme-1.rb +0 -35
- data/examples/readme-2.rb +0 -51
- data/examples/run.sh +0 -41
- data/examples/screenshot.rb +0 -48
- data/examples/vdf.rb +0 -81
- data/examples/vm_drs_behavior.rb +0 -76
- data/test/test_deserialization.rb +0 -383
- data/test/test_emit_request.rb +0 -128
- data/test/test_exceptions.rb +0 -14
- data/test/test_helper.rb +0 -14
- data/test/test_misc.rb +0 -24
- data/test/test_parse_response.rb +0 -69
- data/test/test_serialization.rb +0 -311
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
Y2U1MmQ2YTM2NzNiOTI1NzYyMzhkYmZmODdkMGVhNWE5OTBlMzY5Nw==
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fa94b53805d6066da9dab0c887099c1fbda5f3bbf053b828d24fa3532761276f
|
4
|
+
data.tar.gz: a1061d9d03c38eda8a17061e6fb6ffc8ce7053f4d14c237201e5690cd8e77ee6
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
MjM5MmZjNTMwZTg1MDUwZjE0MWI2ODVhYTdmN2IyOWUyNTYzODY0MDMzMzg5
|
11
|
-
ODU3YzY0Y2MzMjg5MzM1MTdiZWI0MGJlMjM1ODIzNWMxYjFiZTI=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
OTZiMDI4MzA5ODcwOTAwN2I1MDNiOTBlNWNjYWI2OTdhNWQzYWI0M2M1YzI5
|
14
|
-
ZWZiMDVjNTRhNTg5MTU0ZmE5Yzg5YTY5NDcwNWZmMDIxMTY1ZGUwZGZkNzUw
|
15
|
-
NTBhOGJjN2ZmYzI0MjVlZDBkM2UxZjkyNDU3YzY2NGZjNTkzOWU=
|
6
|
+
metadata.gz: '082a81fbb750449c92511db26dd03e4df2de5602f9091c1b035c70a7e4389ce40d4b8a2df9600f49f6feb9f96239811165f2b0b0df5c6b9ef49f0a3b98a95d26'
|
7
|
+
data.tar.gz: 0a1cceb5a1c08fb369ef0629ca9aa824c42192f9625d1f0ebc2baf0b912cf76b7d47f4088450dd1b4520f0f58a8ec815070912733fff6d4fbb8b4739580cd44c
|
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2010 VMware, Inc. All Rights Reserved.
|
1
|
+
Copyright (c) 2010-2017 VMware, Inc. All Rights Reserved.
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
4
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# RbVmomi
|
2
|
+
|
3
|
+
[<img src="https://badge.fury.io/rb/rbvmomi.svg" alt="gem-version">](https://rubygems.org/gems/rbvmomi)
|
4
|
+
[<img src="https://travis-ci.org/vmware/rbvmomi.svg?branch=master" alt="travis-ci">](http://travis-ci.org/vmware/rbvmomi)
|
5
|
+
[<img src="https://badges.gitter.im/vmware/rbvmomi.svg">](https://gitter.im/vmware/rbvmomi)
|
6
|
+
|
7
|
+
This is a community-supported, open source project at VMware. It is built and
|
8
|
+
maintained by programmers like you!
|
9
|
+
|
10
|
+
## Introduction
|
11
|
+
|
12
|
+
RbVmomi is a Ruby interface to the vSphere API. Like the Perl and Java SDKs,
|
13
|
+
you can use it to manage ESX and vCenter servers. The current release
|
14
|
+
supports the vSphere 6.5 API. RbVmomi specific documentation is
|
15
|
+
[online](http://rdoc.info/github/vmware/rbvmomi/master/frames) and is meant to
|
16
|
+
be used alongside the official [documentation](http://pubs.vmware.com/vsphere-65/index.jsp#com.vmware.wssdk.apiref.doc/right-pane.html).
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
gem install rbvmomi
|
21
|
+
|
22
|
+
### Support for older Ruby versions
|
23
|
+
|
24
|
+
RbVmomi supports Ruby 1.8.7 and higher, but certain dependencies may need
|
25
|
+
pinning to older versions to get a compatible set of gems.
|
26
|
+
|
27
|
+
On Ruby 1.8.7:
|
28
|
+
|
29
|
+
* use `nokogiri` 1.5.x (Gemfile: `gem 'nokogiri', '< 1.6'`)
|
30
|
+
|
31
|
+
|
32
|
+
On both Ruby 1.9 and 1.8.7:
|
33
|
+
|
34
|
+
* use `json` 1.x (Gemfile: `gem 'json', '< 2'`)
|
35
|
+
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
A simple example of turning on a VM:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'rbvmomi'
|
43
|
+
|
44
|
+
vim = RbVmomi::VIM.connect(host: 'foo', user: 'bar', password: 'baz')
|
45
|
+
dc = vim.serviceInstance.find_datacenter('my_datacenter') || fail('datacenter not found')
|
46
|
+
vm = dc.find_vm('my_vm') || fail('VM not found')
|
47
|
+
vm.PowerOnVM_Task.wait_for_completion
|
48
|
+
```
|
49
|
+
|
50
|
+
This code uses several RbVmomi extensions to the vSphere API for concision.
|
51
|
+
The expanded snippet below uses only standard API calls and should be familiar
|
52
|
+
to users of the Java SDK:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
require 'rbvmomi'
|
56
|
+
|
57
|
+
vim = RbVmomi::VIM.connect(host: 'foo', user: 'bar', password: 'baz')
|
58
|
+
root_folder = vim.serviceInstance.content.rootFolder
|
59
|
+
dc = root_folder.childEntity.grep(RbVmomi::VIM::Datacenter).find { |x| x.name == 'mydatacenter' } || fail('datacenter not found')
|
60
|
+
vm = dc.vmFolder.childEntity.grep(RbVmomi::VIM::VirtualMachine).find { |x| x.name == 'my_vm' } || fail('VM not found')
|
61
|
+
task = vm.PowerOnVM_Task
|
62
|
+
filter = vim.propertyCollector.CreateFilter(
|
63
|
+
spec: {
|
64
|
+
propSet: [{ type: 'Task', all: false, pathSet: ['info.state']}],
|
65
|
+
objectSet: [{ obj: task }]
|
66
|
+
},
|
67
|
+
partialUpdates: false
|
68
|
+
)
|
69
|
+
ver = ''
|
70
|
+
loop do
|
71
|
+
result = vim.propertyCollector.WaitForUpdates(version: ver)
|
72
|
+
ver = result.version
|
73
|
+
break if ['success', 'error'].member?(task.info.state)
|
74
|
+
end
|
75
|
+
filter.DestroyPropertyFilter
|
76
|
+
raise(task.info.error) if task.info.state == 'error'
|
77
|
+
```
|
78
|
+
|
79
|
+
As you can see, the extensions RbVmomi adds can dramatically decrease the code
|
80
|
+
needed to perform simple tasks while still letting you use the full power of
|
81
|
+
the API when necessary. RbVmomi extensions are often more efficient than a
|
82
|
+
naive implementation; for example, the find_vm method on VIM::Datacenter used
|
83
|
+
in the first example uses the SearchIndex for fast lookups.
|
84
|
+
|
85
|
+
A few important points:
|
86
|
+
|
87
|
+
* All class, method, parameter, and property names match the official [documentation](http://pubs.vmware.com/vsphere-65/index.jsp#com.vmware.wssdk.apiref.doc/right-pane.html).
|
88
|
+
* Properties are exposed as accessor methods.
|
89
|
+
* Data object types can usually be inferred from context, so you may use a hash instead.
|
90
|
+
* Enumeration values are simply strings.
|
91
|
+
* Example code is included in the examples/ directory.
|
92
|
+
* A set of helper methods for Optimist is included to speed up development of
|
93
|
+
command line apps. See the included examples for usage.
|
94
|
+
* If you don't have trusted SSL certificates installed on the host you're
|
95
|
+
connecting to, you'll get an `OpenSSL::SSL::SSLError` "certificate verify
|
96
|
+
failed". You can work around this by using the `:insecure` option to
|
97
|
+
`RbVmomi::VIM.connect`.
|
98
|
+
* This is a side project of a VMware employee and is entirely unsupported by
|
99
|
+
VMware.
|
100
|
+
|
101
|
+
|
102
|
+
Built-in extensions are under `lib/rbvmomi/vim/`. You are encouraged to reopen
|
103
|
+
VIM classes in your applications and add extensions of your own. If you write
|
104
|
+
something generally useful please open a [pull request](https://github.com/vmware/rbvmomi/pulls) so it can be merged back in
|
105
|
+
|
106
|
+
## Development
|
107
|
+
|
108
|
+
Open an issue on the [issues page](https://github.com/vmware/rbvmomi/issues)
|
109
|
+
or fork the project on [GitHub](https://github.com/vmware/rbvmomi) and send a
|
110
|
+
[pull request](https://github.com/vmware/rbvmomi/pulls).
|
111
|
+
|
112
|
+
## Support
|
113
|
+
|
114
|
+
You can chat on [Gitter](https://gitter.im/vmware/rbvmomi) or join the [VMware {code} Slack team](https://vmwarecode.slack.com/) and join the [#rbvmomi channel](https://vmwarecode.slack.com/messages/rbvmomi).
|
data/{bin → exe}/rbvmomish
RENAMED
@@ -2,14 +2,14 @@
|
|
2
2
|
# TODO keepalive
|
3
3
|
# TODO rc file
|
4
4
|
# TODO proxy support?
|
5
|
-
require '
|
5
|
+
require 'optimist'
|
6
6
|
require 'readline'
|
7
7
|
require 'rbvmomi'
|
8
|
-
require 'rbvmomi/
|
8
|
+
require 'rbvmomi/optimist'
|
9
9
|
|
10
10
|
VIM = RbVmomi::VIM
|
11
11
|
|
12
|
-
opts =
|
12
|
+
opts = Optimist.options do
|
13
13
|
banner <<-EOS
|
14
14
|
vSphere API console.
|
15
15
|
|
@@ -35,7 +35,7 @@ VIM connection options:
|
|
35
35
|
Other options:
|
36
36
|
EOS
|
37
37
|
|
38
|
-
$
|
38
|
+
$optimist = self
|
39
39
|
end
|
40
40
|
|
41
41
|
begin
|
@@ -102,7 +102,7 @@ def si
|
|
102
102
|
end
|
103
103
|
|
104
104
|
def help
|
105
|
-
$
|
105
|
+
$optimist.educate
|
106
106
|
:no_result
|
107
107
|
end
|
108
108
|
|
data/lib/rbvmomi.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
-
# Copyright (c) 2010 VMware, Inc. All Rights Reserved.
|
2
|
-
|
1
|
+
# Copyright (c) 2010-2019 VMware, Inc. All Rights Reserved.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
3
|
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
VIM.connect
|
4
|
+
# RbVmomi is a Ruby interface to the vSphere management interface
|
5
|
+
module RbVmomi
|
6
|
+
# @private
|
7
|
+
# @deprecated Use +RbVmomi::VIM.connect+.
|
8
|
+
def self.connect(opts)
|
9
|
+
VIM.connect opts
|
10
|
+
end
|
8
11
|
end
|
9
12
|
|
10
|
-
end
|
11
13
|
require 'rbvmomi/connection'
|
14
|
+
require 'rbvmomi/sso'
|
15
|
+
require 'rbvmomi/version'
|
12
16
|
require 'rbvmomi/vim'
|
data/lib/rbvmomi/basic_types.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
# Copyright (c)
|
1
|
+
# Copyright (c) 2011-2017 VMware, Inc. All Rights Reserved.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
2
4
|
require 'pp'
|
3
5
|
require 'set'
|
4
6
|
|
@@ -158,6 +160,12 @@ class DataObject < ObjectWithProperties
|
|
158
160
|
q.text ')'
|
159
161
|
end
|
160
162
|
|
163
|
+
def to_json(*args)
|
164
|
+
h = self.props
|
165
|
+
m = h.merge({ JSON.create_id => self.class.name })
|
166
|
+
m.to_json(*args)
|
167
|
+
end
|
168
|
+
|
161
169
|
init
|
162
170
|
end
|
163
171
|
|
data/lib/rbvmomi/connection.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
# Copyright (c)
|
1
|
+
# Copyright (c) 2011-2017 VMware, Inc. All Rights Reserved.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
2
4
|
require 'time'
|
3
5
|
require 'date'
|
4
6
|
require 'rbvmomi/trivial_soap'
|
@@ -21,7 +23,7 @@ class Connection < TrivialSoap
|
|
21
23
|
attr_reader :profile_summary
|
22
24
|
attr_accessor :profiling
|
23
25
|
attr_reader :deserializer
|
24
|
-
|
26
|
+
|
25
27
|
def initialize opts
|
26
28
|
@ns = opts[:ns] or fail "no namespace specified"
|
27
29
|
@rev = opts[:rev] or fail "no revision specified"
|
@@ -30,7 +32,7 @@ class Connection < TrivialSoap
|
|
30
32
|
@profiling = false
|
31
33
|
super opts
|
32
34
|
end
|
33
|
-
|
35
|
+
|
34
36
|
def reset_profiling
|
35
37
|
@profile = {}
|
36
38
|
@profile_summary = {:network_latency => 0, :request_emit => 0, :response_parse => 0, :num_calls => 0}
|
@@ -88,7 +90,7 @@ class Connection < TrivialSoap
|
|
88
90
|
|
89
91
|
t3 = Time.now
|
90
92
|
out = parse_response resp, desc['result']
|
91
|
-
|
93
|
+
|
92
94
|
if @profiling
|
93
95
|
t4 = Time.now
|
94
96
|
@profile[method] ||= []
|
@@ -96,8 +98,8 @@ class Connection < TrivialSoap
|
|
96
98
|
:network_latency => (t3 - t2),
|
97
99
|
:request_emit => t2 - t1,
|
98
100
|
:response_parse => t4 - t3,
|
99
|
-
:params => params,
|
100
|
-
:obj => this,
|
101
|
+
:params => params,
|
102
|
+
:obj => this,
|
101
103
|
:backtrace => caller,
|
102
104
|
:request_size => body.length,
|
103
105
|
:response_size => resp_size,
|
@@ -108,7 +110,7 @@ class Connection < TrivialSoap
|
|
108
110
|
@profile_summary[:request_emit] += profile_info[:request_emit]
|
109
111
|
@profile_summary[:num_calls] += 1
|
110
112
|
end
|
111
|
-
|
113
|
+
|
112
114
|
out
|
113
115
|
end
|
114
116
|
|
@@ -137,7 +139,7 @@ class Connection < TrivialSoap
|
|
137
139
|
when BasicTypes::DataObject
|
138
140
|
if expected and not expected >= o.class and not expected == BasicTypes::AnyType
|
139
141
|
fail "expected #{expected.wsdl_name} for '#{name}', got #{o.class.wsdl_name} for field #{name.inspect}"
|
140
|
-
end
|
142
|
+
end
|
141
143
|
xml.tag! name, attrs.merge("xsi:type" => o.class.wsdl_name) do
|
142
144
|
o.class.full_props_desc.each do |desc|
|
143
145
|
if o.props.member? desc['name'].to_sym
|
@@ -221,7 +223,7 @@ class Connection < TrivialSoap
|
|
221
223
|
def type name
|
222
224
|
self.class.type name
|
223
225
|
end
|
224
|
-
|
226
|
+
|
225
227
|
def instanceUuid
|
226
228
|
nil
|
227
229
|
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
# Copyright (c) 2011 VMware, Inc. All Rights Reserved.
|
1
|
+
# Copyright (c) 2011-2017 VMware, Inc. All Rights Reserved.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
2
4
|
require 'time'
|
3
5
|
|
4
6
|
module RbVmomi
|
@@ -99,7 +101,6 @@ class NewDeserializer
|
|
99
101
|
obj = klass.new nil
|
100
102
|
props = obj.props
|
101
103
|
children = node.children.select(&:element?)
|
102
|
-
n = children.size
|
103
104
|
i = 0
|
104
105
|
|
105
106
|
klass.full_props_desc.each do |desc|
|
@@ -150,7 +151,7 @@ class NewDeserializer
|
|
150
151
|
h[child.name] = child.content
|
151
152
|
end
|
152
153
|
[h['key'], h['value']]
|
153
|
-
end
|
154
|
+
end
|
154
155
|
end
|
155
156
|
|
156
157
|
class OldDeserializer
|
data/lib/rbvmomi/fault.rb
CHANGED
@@ -1,17 +1,19 @@
|
|
1
|
-
# Copyright (c)
|
2
|
-
|
1
|
+
# Copyright (c) 2010-2017 VMware, Inc. All Rights Reserved.
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
3
|
|
4
|
-
|
5
|
-
# @see http://trollop.rubyforge.org/
|
6
|
-
# @see Trollop::Parser
|
7
|
-
module Trollop
|
4
|
+
require 'optimist'
|
8
5
|
|
9
|
-
# Convenience methods for
|
6
|
+
# Convenience methods for Optimist, Ruby's premier option parser.
|
7
|
+
# @see http://optimist.rubyforge.org/
|
8
|
+
# @see Optimist::Parser
|
9
|
+
module Optimist
|
10
|
+
|
11
|
+
# Convenience methods for Optimist, Ruby's premier option parser.
|
10
12
|
#
|
11
13
|
# See the examples directory for sample code.
|
12
14
|
# Descriptions are of the form:
|
13
15
|
# <key>: <options> <environment variable> (<default>)
|
14
|
-
# @see http://
|
16
|
+
# @see http://optimist.rubyforge.org/
|
15
17
|
class Parser
|
16
18
|
# Options used by VIM.connect
|
17
19
|
#
|
data/lib/rbvmomi/pbm.rb
CHANGED
data/lib/rbvmomi/sms.rb
CHANGED
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
|