rbvmomi 1.8.2 → 2.4.1
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 +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
|