knife-vcenter 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/{LICENSE.txt → LICENSE} +0 -0
- data/lib/base.rb +8 -3
- data/lib/chef/knife/cloud/vcenter_service.rb +117 -81
- data/lib/chef/knife/cloud/vcenter_service_helpers.rb +15 -3
- data/lib/chef/knife/cloud/vcenter_service_options.rb +18 -17
- data/lib/chef/knife/vcenter_cluster_list.rb +19 -13
- data/lib/chef/knife/vcenter_datacenter_list.rb +19 -13
- data/lib/chef/knife/vcenter_host_list.rb +28 -19
- data/lib/chef/knife/vcenter_vm_clone.rb +39 -28
- data/lib/chef/knife/vcenter_vm_create.rb +26 -21
- data/lib/chef/knife/vcenter_vm_delete.rb +15 -10
- data/lib/chef/knife/vcenter_vm_list.rb +31 -21
- data/lib/chef/knife/vcenter_vm_show.rb +12 -13
- data/lib/knife-vcenter/version.rb +4 -2
- data/lib/lookup_service_helper.rb +421 -427
- data/lib/sso.rb +213 -218
- data/lib/support/clone_vm.rb +4 -4
- data/spec/spec_helper.rb +2 -2
- data/spec/unit/vcenter_vm_list_spec.rb +21 -22
- metadata +6 -58
- data/.github/ISSUE_TEMPLATE.md +0 -22
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -14
- data/.gitignore +0 -21
- data/.rubocop.yml +0 -18
- data/.travis.yml +0 -16
- data/CHANGELOG.md +0 -34
- data/Gemfile +0 -3
- data/README.md +0 -219
- data/Rakefile +0 -20
- data/knife-vcenter.gemspec +0 -37
data/lib/sso.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# Copyright 2014-2017 VMware, Inc. All Rights Reserved.
|
2
2
|
# SPDX-License-Identifier: MIT
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
4
|
+
require "savon"
|
5
|
+
require "nokogiri"
|
6
|
+
require "date"
|
7
|
+
require "securerandom"
|
8
8
|
|
9
9
|
# A little utility library for VMware SSO.
|
10
10
|
# For now, this is not a general purpose library that covers all
|
@@ -12,258 +12,253 @@ require 'securerandom'
|
|
12
12
|
# Specifically, the support is limited to the following:
|
13
13
|
# * request bearer token.
|
14
14
|
module SSO
|
15
|
+
# The XML date format.
|
16
|
+
DATE_FORMAT = "%FT%T.%LZ".freeze
|
17
|
+
|
18
|
+
# The XML namespaces that are required: SOAP, WSDL, et al.
|
19
|
+
NAMESPACES = {
|
20
|
+
"xmlns:S" => "http://schemas.xmlsoap.org/soap/envelope/",
|
21
|
+
"xmlns:wst" => "http://docs.oasis-open.org/ws-sx/ws-trust/200512",
|
22
|
+
"xmlns:u" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd",
|
23
|
+
"xmlns:x" => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd",
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
# Provides the connection details for the SSO service.
|
27
|
+
class Connection
|
28
|
+
attr_accessor :sso_url, :wsdl_url, :username, :password
|
29
|
+
|
30
|
+
# Creates a new instance.
|
31
|
+
def initialize(sso_url, wsdl_url = nil)
|
32
|
+
self.sso_url = sso_url
|
33
|
+
self.wsdl_url = wsdl_url || "#{sso_url}?wsdl"
|
34
|
+
end
|
15
35
|
|
16
|
-
#
|
17
|
-
|
36
|
+
# Login with the given credentials.
|
37
|
+
# Note: this does not invoke a login action, but rather stores the
|
38
|
+
# credentials for use later.
|
39
|
+
def login(username, password)
|
40
|
+
self.username = username
|
41
|
+
self.password = password
|
42
|
+
self # enable builder pattern
|
43
|
+
end
|
18
44
|
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
45
|
+
# Gets (or creates) the Savon client instance.
|
46
|
+
def client
|
47
|
+
# construct and init the client proxy
|
48
|
+
@client ||= Savon.client do |globals|
|
49
|
+
# see: http://savonrb.com/version2/globals.html
|
50
|
+
globals.wsdl wsdl_url
|
51
|
+
globals.endpoint sso_url
|
52
|
+
|
53
|
+
globals.strip_namespaces false
|
54
|
+
globals.env_namespace :S
|
55
|
+
|
56
|
+
# set like this so https connection does not fail
|
57
|
+
# TODO: find an acceptable solution for production
|
58
|
+
globals.ssl_verify_mode :none
|
59
|
+
|
60
|
+
# dev/debug settings
|
61
|
+
# globals.pretty_print_xml ENV['DEBUG_SOAP']
|
62
|
+
# globals.log ENV['DEBUG_SOAP']
|
63
|
+
end
|
64
|
+
end
|
26
65
|
|
27
|
-
#
|
28
|
-
|
66
|
+
# Invokes the request bearer token operation.
|
67
|
+
# @return [SamlToken]
|
68
|
+
def request_bearer_token
|
69
|
+
rst = RequestSecurityToken.new(client, username, password)
|
70
|
+
rst.invoke
|
71
|
+
rst.saml_token
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# @abstract Base class for invocable service calls.
|
76
|
+
class SoapInvocable
|
77
|
+
attr_reader :operation, :client, :response
|
78
|
+
|
79
|
+
# Constructs a new instance.
|
80
|
+
# @param operation [Symbol] the SOAP operation name (in Symbol form)
|
81
|
+
# @param client [Savon::Client] the client
|
82
|
+
def initialize(operation, client)
|
83
|
+
@operation = operation
|
84
|
+
@client = client
|
85
|
+
end
|
29
86
|
|
30
|
-
|
87
|
+
# Invokes the service call represented by this type.
|
88
|
+
def invoke
|
89
|
+
request = request_xml.to_s
|
90
|
+
puts "request = #{request}" if ENV["DEBUG"]
|
91
|
+
@response = client.call(operation, xml: request)
|
92
|
+
puts "response = #{response}" if ENV["DEBUG"]
|
93
|
+
self # for chaining with new
|
94
|
+
end
|
31
95
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
96
|
+
# Builds the request XML content.
|
97
|
+
def request_xml
|
98
|
+
builder = Builder::XmlMarkup.new
|
99
|
+
builder.instruct!(:xml, encoding: "UTF-8")
|
37
100
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
self.password = password
|
44
|
-
self # enable builder pattern
|
101
|
+
builder.tag!("S:Envelope", NAMESPACES) do |envelope|
|
102
|
+
if has_header?
|
103
|
+
envelope.tag!("S:Header") do |header|
|
104
|
+
header_xml(header)
|
105
|
+
end
|
45
106
|
end
|
46
|
-
|
47
|
-
|
48
|
-
def client
|
49
|
-
# construct and init the client proxy
|
50
|
-
@client ||= Savon.client do |globals|
|
51
|
-
# see: http://savonrb.com/version2/globals.html
|
52
|
-
globals.wsdl wsdl_url
|
53
|
-
globals.endpoint sso_url
|
54
|
-
|
55
|
-
globals.strip_namespaces false
|
56
|
-
globals.env_namespace :S
|
57
|
-
|
58
|
-
# set like this so https connection does not fail
|
59
|
-
# TODO: find an acceptable solution for production
|
60
|
-
globals.ssl_verify_mode :none
|
61
|
-
|
62
|
-
# dev/debug settings
|
63
|
-
# globals.pretty_print_xml ENV['DEBUG_SOAP']
|
64
|
-
# globals.log ENV['DEBUG_SOAP']
|
65
|
-
end
|
107
|
+
envelope.tag!("S:Body") do |body|
|
108
|
+
body_xml(body)
|
66
109
|
end
|
110
|
+
end
|
111
|
+
builder.target!
|
112
|
+
end
|
67
113
|
|
68
|
-
|
69
|
-
|
70
|
-
def request_bearer_token()
|
71
|
-
rst = RequestSecurityToken.new(client, username, password)
|
72
|
-
rst.invoke()
|
73
|
-
rst.saml_token
|
74
|
-
end
|
114
|
+
def has_header?
|
115
|
+
true
|
75
116
|
end
|
76
117
|
|
77
|
-
#
|
78
|
-
|
118
|
+
# Builds the header portion of the SOAP request.
|
119
|
+
# Specific service operations must override this method.
|
120
|
+
def header_xml(_header)
|
121
|
+
raise "abstract method not implemented!"
|
122
|
+
end
|
79
123
|
|
80
|
-
|
124
|
+
# Builds the body portion of the SOAP request.
|
125
|
+
# Specific service operations must override this method.
|
126
|
+
def body_xml(_body)
|
127
|
+
raise "abstract method not implemented!"
|
128
|
+
end
|
81
129
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
def initialize(operation, client)
|
86
|
-
@operation = operation
|
87
|
-
@client = client
|
88
|
-
end
|
130
|
+
# Gets the response XML content.
|
131
|
+
def response_xml
|
132
|
+
raise "illegal state: response not set yet" if response.nil?
|
89
133
|
|
90
|
-
|
91
|
-
|
92
|
-
request = request_xml.to_s
|
93
|
-
puts "request = #{request}" if ENV['DEBUG']
|
94
|
-
@response = client.call(operation, xml:request)
|
95
|
-
puts "response = #{response}" if ENV['DEBUG']
|
96
|
-
self # for chaining with new
|
97
|
-
end
|
134
|
+
@response_xml ||= Nokogiri::XML(response.to_xml)
|
135
|
+
end
|
98
136
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
builder.tag!("S:Envelope", NAMESPACES) do |envelope|
|
105
|
-
if has_header?
|
106
|
-
envelope.tag!("S:Header") do |header|
|
107
|
-
header_xml(header)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
envelope.tag!("S:Body") do |body|
|
111
|
-
body_xml(body)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
builder.target!
|
115
|
-
end
|
137
|
+
def response_hash
|
138
|
+
@response_hash ||= response.to_hash
|
139
|
+
end
|
140
|
+
end
|
116
141
|
|
117
|
-
|
118
|
-
|
119
|
-
|
142
|
+
# Encapsulates an issue operation that requests a security token
|
143
|
+
# from the SSO service.
|
144
|
+
class RequestSecurityToken < SoapInvocable
|
145
|
+
attr_accessor :request_type, :delegatable
|
120
146
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
raise 'abstract method not implemented!'
|
125
|
-
end
|
147
|
+
# Constructs a new instance.
|
148
|
+
def initialize(client, username, password, hours = 2)
|
149
|
+
super(:issue, client)
|
126
150
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
raise 'abstract method not implemented!'
|
131
|
-
end
|
151
|
+
@username = username
|
152
|
+
@password = password
|
153
|
+
@hours = hours
|
132
154
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
end
|
138
|
-
|
139
|
-
def response_hash
|
140
|
-
@response_hash ||= response.to_hash
|
141
|
-
end
|
155
|
+
# TODO: these things should be configurable, so we can get
|
156
|
+
# non-delegatable tokens, HoK tokens, etc.
|
157
|
+
@request_type = "http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue"
|
158
|
+
@delegatable = true
|
142
159
|
end
|
143
160
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
attr_accessor :request_type, :delegatable
|
149
|
-
|
150
|
-
# Constructs a new instance.
|
151
|
-
def initialize(client, username, password, hours=2)
|
152
|
-
super(:issue, client)
|
153
|
-
|
154
|
-
@username = username
|
155
|
-
@password = password
|
156
|
-
@hours = hours
|
161
|
+
def now
|
162
|
+
@now ||= Time.now.utc.to_datetime
|
163
|
+
end
|
157
164
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
@delegatable = true
|
162
|
-
end
|
165
|
+
def created
|
166
|
+
@created ||= now.strftime(DATE_FORMAT)
|
167
|
+
end
|
163
168
|
|
164
|
-
|
165
|
-
|
166
|
-
|
169
|
+
def future
|
170
|
+
@future ||= now + (2 / 24.0) # days (for DateTime math)
|
171
|
+
end
|
167
172
|
|
168
|
-
|
169
|
-
|
170
|
-
|
173
|
+
def expires
|
174
|
+
@expires ||= future.strftime(DATE_FORMAT)
|
175
|
+
end
|
171
176
|
|
172
|
-
|
173
|
-
|
177
|
+
# Builds the header XML for the SOAP request.
|
178
|
+
def header_xml(header)
|
179
|
+
id = "uuid-" + SecureRandom.uuid
|
180
|
+
|
181
|
+
# header.tag!("x:Security", "x:mustUnderstand" => "1") do |security|
|
182
|
+
header.tag!("x:Security") do |security|
|
183
|
+
security.tag!("u:Timestamp", "u:Id" => "_0") do |timestamp|
|
184
|
+
timestamp.tag!("u:Created") do |element|
|
185
|
+
element << created
|
186
|
+
end
|
187
|
+
timestamp.tag!("u:Expires") do |element|
|
188
|
+
element << expires
|
189
|
+
end
|
174
190
|
end
|
175
|
-
|
176
|
-
|
177
|
-
|
191
|
+
security.tag!("x:UsernameToken", "u:Id" => id) do |utoken|
|
192
|
+
utoken.tag!("x:Username") do |element|
|
193
|
+
element << @username
|
194
|
+
end
|
195
|
+
utoken.tag!("x:Password") do |element|
|
196
|
+
element << @password
|
197
|
+
end
|
178
198
|
end
|
199
|
+
end
|
200
|
+
end
|
179
201
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
header.tag!("x:Security") do |security|
|
186
|
-
security.tag!("u:Timestamp", "u:Id" => "_0") do |timestamp|
|
187
|
-
timestamp.tag!("u:Created") do |element|
|
188
|
-
element << created
|
189
|
-
end
|
190
|
-
timestamp.tag!("u:Expires") do |element|
|
191
|
-
element << expires
|
192
|
-
end
|
193
|
-
end
|
194
|
-
security.tag!("x:UsernameToken", "u:Id" => id) do |utoken|
|
195
|
-
utoken.tag!("x:Username") do |element|
|
196
|
-
element << @username
|
197
|
-
end
|
198
|
-
utoken.tag!("x:Password") do |element|
|
199
|
-
element << @password
|
200
|
-
end
|
201
|
-
end
|
202
|
-
end
|
202
|
+
# Builds the body XML for the SOAP request.
|
203
|
+
def body_xml(body)
|
204
|
+
body.tag!("wst:RequestSecurityToken") do |rst|
|
205
|
+
rst.tag!("wst:RequestType") do |element|
|
206
|
+
element << request_type
|
203
207
|
end
|
204
|
-
|
205
|
-
|
206
|
-
def body_xml(body)
|
207
|
-
body.tag!("wst:RequestSecurityToken") do |rst|
|
208
|
-
rst.tag!("wst:RequestType") do |element|
|
209
|
-
element << request_type
|
210
|
-
end
|
211
|
-
rst.tag!("wst:Delegatable") do |element|
|
212
|
-
element << delegatable.to_s
|
213
|
-
end
|
214
|
-
=begin
|
215
|
-
#TODO: we don't seem to need this, but I'm leaving this
|
216
|
-
#here for now as a reminder.
|
217
|
-
rst.tag!("wst:Lifetime") do |lifetime|
|
218
|
-
lifetime.tag!("u:Created") do |element|
|
219
|
-
element << created
|
220
|
-
end
|
221
|
-
lifetime.tag!("u:Expires") do |element|
|
222
|
-
element << expires
|
223
|
-
end
|
224
|
-
end
|
225
|
-
=end
|
226
|
-
end
|
208
|
+
rst.tag!("wst:Delegatable") do |element|
|
209
|
+
element << delegatable.to_s
|
227
210
|
end
|
211
|
+
# #TODO: we don't seem to need this, but I'm leaving this
|
212
|
+
# #here for now as a reminder.
|
213
|
+
# rst.tag!("wst:Lifetime") do |lifetime|
|
214
|
+
# lifetime.tag!("u:Created") do |element|
|
215
|
+
# element << created
|
216
|
+
# end
|
217
|
+
# lifetime.tag!("u:Expires") do |element|
|
218
|
+
# element << expires
|
219
|
+
# end
|
220
|
+
# end
|
221
|
+
end
|
222
|
+
end
|
228
223
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
end
|
224
|
+
# Gets the saml_token from the SOAP response body.
|
225
|
+
# @return [SamlToken] the requested SAML token
|
226
|
+
def saml_token
|
227
|
+
assertion = response_xml.at_xpath("//saml2:Assertion",
|
228
|
+
"saml2" => "urn:oasis:names:tc:SAML:2.0:assertion")
|
229
|
+
SamlToken.new(assertion)
|
236
230
|
end
|
231
|
+
end
|
237
232
|
|
238
|
-
|
239
|
-
|
240
|
-
|
233
|
+
# Holds a SAML token.
|
234
|
+
class SamlToken
|
235
|
+
attr_reader :xml
|
241
236
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
237
|
+
# Creates a new instance.
|
238
|
+
def initialize(xml)
|
239
|
+
@xml = xml
|
240
|
+
end
|
246
241
|
|
247
|
-
|
242
|
+
# TODO: add some getters for interesting content
|
248
243
|
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
end
|
244
|
+
def to_s
|
245
|
+
esc_token = xml.to_xml(indent: 0, encoding: "UTF-8")
|
246
|
+
esc_token = esc_token.delete("\n")
|
247
|
+
esc_token
|
254
248
|
end
|
249
|
+
end
|
255
250
|
end
|
256
251
|
|
257
252
|
# main: quick self tester
|
258
|
-
if
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
253
|
+
if $PROGRAM_NAME == __FILE__
|
254
|
+
cloudvm_ip = ARGV[0]
|
255
|
+
cloudvm_ip ||= "10.20.17.0"
|
256
|
+
# cloudvm_ip ||= "10.67.245.207"
|
257
|
+
sso_url = "https://#{cloudvm_ip}/sts/STSService/vsphere.local"
|
258
|
+
wsdl_url = "#{sso_url}?wsdl"
|
259
|
+
sso = SSO::Connection.new(sso_url, wsdl_url)
|
260
|
+
# sso.login("administrator@vsphere.local", "Admin!23")
|
261
|
+
sso.login("root", "vmware")
|
262
|
+
token = sso.request_bearer_token
|
263
|
+
puts token.to_s
|
269
264
|
end
|