knife-azure 3.0.0 → 4.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 +4 -4
- data/lib/azure/custom_errors.rb +1 -1
- data/lib/azure/resource_management/ARM_deployment_template.rb +5 -5
- data/lib/azure/resource_management/ARM_interface.rb +8 -12
- data/lib/azure/resource_management/windows_credentials.rb +7 -8
- data/lib/chef/knife/azurerm_server_create.rb +2 -2
- data/lib/chef/knife/azurerm_server_delete.rb +1 -1
- data/lib/chef/knife/bootstrap/bootstrapper.rb +10 -11
- data/lib/chef/knife/bootstrap_azurerm.rb +1 -1
- data/lib/chef/knife/helpers/azurerm_base.rb +17 -19
- data/lib/knife-azure/version.rb +1 -1
- metadata +30 -43
- data/lib/azure/service_management/ASM_interface.rb +0 -310
- data/lib/azure/service_management/ag.rb +0 -99
- data/lib/azure/service_management/certificate.rb +0 -235
- data/lib/azure/service_management/connection.rb +0 -102
- data/lib/azure/service_management/deploy.rb +0 -221
- data/lib/azure/service_management/disk.rb +0 -68
- data/lib/azure/service_management/host.rb +0 -184
- data/lib/azure/service_management/image.rb +0 -94
- data/lib/azure/service_management/loadbalancer.rb +0 -78
- data/lib/azure/service_management/rest.rb +0 -125
- data/lib/azure/service_management/role.rb +0 -717
- data/lib/azure/service_management/storageaccount.rb +0 -127
- data/lib/azure/service_management/utility.rb +0 -40
- data/lib/azure/service_management/vnet.rb +0 -134
- data/lib/chef/knife/azure_ag_create.rb +0 -73
- data/lib/chef/knife/azure_ag_list.rb +0 -35
- data/lib/chef/knife/azure_image_list.rb +0 -56
- data/lib/chef/knife/azure_internal-lb_create.rb +0 -74
- data/lib/chef/knife/azure_internal-lb_list.rb +0 -35
- data/lib/chef/knife/azure_server_create.rb +0 -531
- data/lib/chef/knife/azure_server_delete.rb +0 -136
- data/lib/chef/knife/azure_server_list.rb +0 -38
- data/lib/chef/knife/azure_server_show.rb +0 -41
- data/lib/chef/knife/azure_vnet_create.rb +0 -74
- data/lib/chef/knife/azure_vnet_list.rb +0 -35
- data/lib/chef/knife/bootstrap_azure.rb +0 -191
- data/lib/chef/knife/helpers/azure_base.rb +0 -394
@@ -1,310 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Copyright:: Copyright (c) Chef Software Inc.
|
3
|
-
# License:: Apache License, Version 2.0
|
4
|
-
#
|
5
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
-
# you may not use this file except in compliance with the License.
|
7
|
-
# You may obtain a copy of the License at
|
8
|
-
#
|
9
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
-
#
|
11
|
-
# Unless required by applicable law or agreed to in writing, software
|
12
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
-
# See the License for the specific language governing permissions and
|
15
|
-
# limitations under the License.
|
16
|
-
#
|
17
|
-
|
18
|
-
require_relative "../azure_interface"
|
19
|
-
require_relative "rest"
|
20
|
-
require_relative "connection"
|
21
|
-
|
22
|
-
module Azure
|
23
|
-
class ServiceManagement
|
24
|
-
class ASMInterface < AzureInterface
|
25
|
-
include AzureAPI
|
26
|
-
|
27
|
-
attr_accessor :connection
|
28
|
-
|
29
|
-
def initialize(params = {})
|
30
|
-
@rest = Rest.new(params)
|
31
|
-
@connection = Azure::ServiceManagement::Connection.new(@rest)
|
32
|
-
super
|
33
|
-
end
|
34
|
-
|
35
|
-
def list_images
|
36
|
-
connection.images.all
|
37
|
-
end
|
38
|
-
|
39
|
-
def list_servers
|
40
|
-
servers = connection.roles.all
|
41
|
-
cols = ["DNS Name", "VM Name", "Status", "IP Address", "SSH Port", "WinRM Port", "RDP Port"]
|
42
|
-
rows = []
|
43
|
-
servers.each do |server|
|
44
|
-
rows << server.hostedservicename.to_s + ".cloudapp.net" # Info about the DNS name at http://msdn.microsoft.com/en-us/library/ee460806.aspx
|
45
|
-
rows << server.name.to_s
|
46
|
-
rows << begin
|
47
|
-
state = server.status.to_s.downcase
|
48
|
-
case state
|
49
|
-
when "shutting-down", "terminated", "stopping", "stopped"
|
50
|
-
ui.color(state, :red)
|
51
|
-
when "pending"
|
52
|
-
ui.color(state, :yellow)
|
53
|
-
else
|
54
|
-
ui.color("ready", :green)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
rows << server.publicipaddress.to_s
|
58
|
-
rows << server.sshport.to_s
|
59
|
-
rows << server.winrmport.to_s
|
60
|
-
ports = server.tcpports
|
61
|
-
rows << rdp_port(ports)
|
62
|
-
end
|
63
|
-
display_list(ui, cols, rows)
|
64
|
-
end
|
65
|
-
|
66
|
-
def rdp_port(arr_ports)
|
67
|
-
unless arr_ports
|
68
|
-
return ""
|
69
|
-
end
|
70
|
-
|
71
|
-
if arr_ports.length > 0
|
72
|
-
arr_ports.each do |port|
|
73
|
-
if port["Name"] == "Remote Desktop"
|
74
|
-
return port["PublicPort"]
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
""
|
79
|
-
end
|
80
|
-
|
81
|
-
def find_server(params = {})
|
82
|
-
server = connection.roles.find(params[:name], params = { azure_dns_name: params[:azure_dns_name] })
|
83
|
-
end
|
84
|
-
|
85
|
-
def delete_server(params = {})
|
86
|
-
server = find_server({ name: params[:name], azure_dns_name: params[:azure_dns_name] })
|
87
|
-
|
88
|
-
unless server
|
89
|
-
puts "\n"
|
90
|
-
ui.error("Server #{params[:name]} does not exist")
|
91
|
-
exit!
|
92
|
-
end
|
93
|
-
|
94
|
-
puts "\n"
|
95
|
-
msg_pair(ui, "DNS Name", server.hostedservicename + ".cloudapp.net")
|
96
|
-
msg_pair(ui, "VM Name", server.name)
|
97
|
-
msg_pair(ui, "Size", server.size)
|
98
|
-
msg_pair(ui, "Public Ip Address", server.publicipaddress)
|
99
|
-
puts "\n"
|
100
|
-
|
101
|
-
begin
|
102
|
-
ui.confirm("Do you really want to delete this server")
|
103
|
-
rescue SystemExit # Need to handle this as confirming with N/n raises SystemExit exception
|
104
|
-
server = nil # Cleanup is implicitly performed in other cloud plugins
|
105
|
-
exit!
|
106
|
-
end
|
107
|
-
|
108
|
-
params[:azure_dns_name] = server.hostedservicename
|
109
|
-
|
110
|
-
connection.roles.delete(params)
|
111
|
-
|
112
|
-
puts "\n"
|
113
|
-
ui.warn("Deleted server #{server.name}")
|
114
|
-
end
|
115
|
-
|
116
|
-
def show_server(name)
|
117
|
-
role = connection.roles.find name
|
118
|
-
|
119
|
-
puts ""
|
120
|
-
if role
|
121
|
-
details = []
|
122
|
-
details << ui.color("Role name", :bold, :cyan)
|
123
|
-
details << role.name
|
124
|
-
details << ui.color("Status", :bold, :cyan)
|
125
|
-
details << role.status
|
126
|
-
details << ui.color("Size", :bold, :cyan)
|
127
|
-
details << role.size
|
128
|
-
details << ui.color("Hosted service name", :bold, :cyan)
|
129
|
-
details << role.hostedservicename
|
130
|
-
details << ui.color("Deployment name", :bold, :cyan)
|
131
|
-
details << role.deployname
|
132
|
-
details << ui.color("Host name", :bold, :cyan)
|
133
|
-
details << role.hostname
|
134
|
-
unless role.sshport.nil?
|
135
|
-
details << ui.color("SSH port", :bold, :cyan)
|
136
|
-
details << role.sshport
|
137
|
-
end
|
138
|
-
unless role.winrmport.nil?
|
139
|
-
details << ui.color("WinRM port", :bold, :cyan)
|
140
|
-
details << role.winrmport
|
141
|
-
end
|
142
|
-
details << ui.color("Public IP", :bold, :cyan)
|
143
|
-
details << role.publicipaddress.to_s
|
144
|
-
|
145
|
-
unless role.thumbprint.empty?
|
146
|
-
details << ui.color("Thumbprint", :bold, :cyan)
|
147
|
-
details << role.thumbprint
|
148
|
-
end
|
149
|
-
puts ui.list(details, :columns_across, 2)
|
150
|
-
if role.tcpports.length > 0 || role.udpports.length > 0
|
151
|
-
details.clear
|
152
|
-
details << ui.color("Ports open", :bold, :cyan)
|
153
|
-
details << ui.color("Local port", :bold, :cyan)
|
154
|
-
details << ui.color("IP", :bold, :cyan)
|
155
|
-
details << ui.color("Public port", :bold, :cyan)
|
156
|
-
if role.tcpports.length > 0
|
157
|
-
role.tcpports.each do |port|
|
158
|
-
details << "tcp"
|
159
|
-
details << port["LocalPort"]
|
160
|
-
details << port["Vip"]
|
161
|
-
details << port["PublicPort"]
|
162
|
-
end
|
163
|
-
end
|
164
|
-
if role.udpports.length > 0
|
165
|
-
role.udpports.each do |port|
|
166
|
-
details << "udp"
|
167
|
-
details << port["LocalPort"]
|
168
|
-
details << port["Vip"]
|
169
|
-
details << port["PublicPort"]
|
170
|
-
end
|
171
|
-
end
|
172
|
-
puts ui.list(details, :columns_across, 4)
|
173
|
-
end
|
174
|
-
else
|
175
|
-
puts "No VM found"
|
176
|
-
end
|
177
|
-
|
178
|
-
rescue => error
|
179
|
-
puts "#{error.class} and #{error.message}"
|
180
|
-
end
|
181
|
-
|
182
|
-
def list_internal_lb
|
183
|
-
lbs = connection.lbs.all
|
184
|
-
cols = %w{Name Service Subnet VIP}
|
185
|
-
rows = []
|
186
|
-
lbs.each do |lb|
|
187
|
-
cols.each { |col| rows << lb.send(col.downcase).to_s }
|
188
|
-
end
|
189
|
-
display_list(ui, cols, rows)
|
190
|
-
end
|
191
|
-
|
192
|
-
def create_internal_lb(params = {})
|
193
|
-
connection.lbs.create(params)
|
194
|
-
end
|
195
|
-
|
196
|
-
def list_vnets
|
197
|
-
vnets = connection.vnets.all
|
198
|
-
cols = ["Name", "Affinity Group", "State"]
|
199
|
-
rows = []
|
200
|
-
vnets.each do |vnet|
|
201
|
-
%w{name affinity_group state}.each { |col| rows << vnet.send(col).to_s }
|
202
|
-
end
|
203
|
-
display_list(ui, cols, rows)
|
204
|
-
end
|
205
|
-
|
206
|
-
def create_vnet(params = {})
|
207
|
-
connection.vnets.create(params)
|
208
|
-
end
|
209
|
-
|
210
|
-
def list_affinity_groups
|
211
|
-
affinity_groups = connection.ags.all
|
212
|
-
cols = %w{Name Location Description}
|
213
|
-
rows = []
|
214
|
-
affinity_groups.each do |affinity_group|
|
215
|
-
cols.each { |col| rows << affinity_group.send(col.downcase).to_s }
|
216
|
-
end
|
217
|
-
display_list(ui, cols, rows)
|
218
|
-
end
|
219
|
-
|
220
|
-
def create_affinity_group(params = {})
|
221
|
-
connection.ags.create(params)
|
222
|
-
end
|
223
|
-
|
224
|
-
def create_server(params = {})
|
225
|
-
remove_hosted_service_on_failure = params[:azure_dns_name]
|
226
|
-
if connection.hosts.exists?(params[:azure_dns_name])
|
227
|
-
remove_hosted_service_on_failure = nil
|
228
|
-
end
|
229
|
-
|
230
|
-
# If Storage Account is not specified, check if the geographic location has one to re-use
|
231
|
-
if not params[:azure_storage_account]
|
232
|
-
storage_accts = connection.storageaccounts.all
|
233
|
-
storage = storage_accts.find { |storage_acct| storage_acct.location.to_s == params[:azure_service_location] }
|
234
|
-
unless storage
|
235
|
-
params[:azure_storage_account] = [strip_non_ascii(params[:azure_vm_name]), random_string].join.downcase
|
236
|
-
remove_storage_service_on_failure = params[:azure_storage_account]
|
237
|
-
else
|
238
|
-
remove_storage_service_on_failure = nil
|
239
|
-
params[:azure_storage_account] = storage.name.to_s
|
240
|
-
end
|
241
|
-
else
|
242
|
-
if connection.storageaccounts.exists?(params[:azure_storage_account])
|
243
|
-
remove_storage_service_on_failure = nil
|
244
|
-
else
|
245
|
-
remove_storage_service_on_failure = params[:azure_storage_account]
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
begin
|
250
|
-
connection.deploys.create(params)
|
251
|
-
rescue Exception => e
|
252
|
-
Chef::Log.error("Failed to create the server -- exception being rescued: #{e}")
|
253
|
-
backtrace_message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
254
|
-
Chef::Log.debug("#{backtrace_message}")
|
255
|
-
cleanup_and_exit(remove_hosted_service_on_failure, remove_storage_service_on_failure)
|
256
|
-
end
|
257
|
-
end
|
258
|
-
|
259
|
-
def cleanup_and_exit(remove_hosted_service_on_failure, remove_storage_service_on_failure)
|
260
|
-
Chef::Log.warn("Cleaning up resources...")
|
261
|
-
|
262
|
-
if remove_hosted_service_on_failure
|
263
|
-
ret_val = connection.hosts.delete(remove_hosted_service_on_failure)
|
264
|
-
ret_val.content.empty? ? Chef::Log.warn("Deleted created DNS: #{remove_hosted_service_on_failure}.") : Chef::Log.warn("Deletion failed for created DNS:#{remove_hosted_service_on_failure}. " + ret_val.text)
|
265
|
-
end
|
266
|
-
|
267
|
-
if remove_storage_service_on_failure
|
268
|
-
ret_val = connection.storageaccounts.delete(remove_storage_service_on_failure)
|
269
|
-
ret_val.content.empty? ? Chef::Log.warn("Deleted created Storage Account: #{remove_storage_service_on_failure}.") : Chef::Log.warn("Deletion failed for created Storage Account: #{remove_storage_service_on_failure}. " + ret_val.text)
|
270
|
-
end
|
271
|
-
exit 1
|
272
|
-
end
|
273
|
-
|
274
|
-
def get_role_server(dns_name, vm_name)
|
275
|
-
deploy = connection.deploys.queryDeploy(dns_name)
|
276
|
-
deploy.find_role(vm_name)
|
277
|
-
end
|
278
|
-
|
279
|
-
def get_extension(name, publisher)
|
280
|
-
connection.query_azure("resourceextensions/#{publisher}/#{name}")
|
281
|
-
end
|
282
|
-
|
283
|
-
def deployment_name(dns_name)
|
284
|
-
connection.deploys.get_deploy_name_for_hostedservice(dns_name)
|
285
|
-
end
|
286
|
-
|
287
|
-
def deployment(path)
|
288
|
-
connection.query_azure(path)
|
289
|
-
end
|
290
|
-
|
291
|
-
def valid_image?(name)
|
292
|
-
connection.images.exists?(name)
|
293
|
-
end
|
294
|
-
|
295
|
-
def vm_image?(name)
|
296
|
-
connection.images.is_vm_image(name)
|
297
|
-
end
|
298
|
-
|
299
|
-
def add_extension(name, params = {})
|
300
|
-
ui.info "Started with Chef Extension deployment on the server #{name}..."
|
301
|
-
connection.roles.update(name, params)
|
302
|
-
ui.info "\nSuccessfully deployed Chef Extension on the server #{name}."
|
303
|
-
rescue Exception => e
|
304
|
-
Chef::Log.error("Failed to add extension to the server -- exception being rescued: #{e}")
|
305
|
-
backtrace_message = "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
|
306
|
-
Chef::Log.debug("#{backtrace_message}")
|
307
|
-
end
|
308
|
-
end
|
309
|
-
end
|
310
|
-
end
|
@@ -1,99 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Jeff Mendoza (jeffmendoza@live.com)
|
3
|
-
# Copyright:: Copyright (c) Chef Software Inc.
|
4
|
-
# License:: Apache License, Version 2.0
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#
|
18
|
-
|
19
|
-
module Azure
|
20
|
-
class AGs
|
21
|
-
def initialize(connection)
|
22
|
-
@connection = connection
|
23
|
-
end
|
24
|
-
|
25
|
-
def load
|
26
|
-
@ags ||= begin
|
27
|
-
@ags = {}
|
28
|
-
response = @connection.query_azure("affinitygroups",
|
29
|
-
"get",
|
30
|
-
"",
|
31
|
-
"",
|
32
|
-
true,
|
33
|
-
false)
|
34
|
-
response.css("AffinityGroup").each do |ag|
|
35
|
-
item = AG.new(@connection).parse(ag)
|
36
|
-
@ags[item.name] = item
|
37
|
-
end
|
38
|
-
@ags
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def all
|
43
|
-
load.values
|
44
|
-
end
|
45
|
-
|
46
|
-
def exists?(name)
|
47
|
-
load.key?(name)
|
48
|
-
end
|
49
|
-
|
50
|
-
def find(name)
|
51
|
-
load[name]
|
52
|
-
end
|
53
|
-
|
54
|
-
def create(params)
|
55
|
-
ag = AG.new(@connection)
|
56
|
-
ag.create(params)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
module Azure
|
62
|
-
class AG
|
63
|
-
attr_accessor :name, :label, :description, :location
|
64
|
-
|
65
|
-
def initialize(connection)
|
66
|
-
@connection = connection
|
67
|
-
end
|
68
|
-
|
69
|
-
def parse(image)
|
70
|
-
@name = image.at_css("Name").content
|
71
|
-
@label = image.at_css("Label").content
|
72
|
-
@description = image.at_css("Description").content if
|
73
|
-
image.at_css("Description")
|
74
|
-
@location = image.at_css("Location").content if image.at_css("Location")
|
75
|
-
self
|
76
|
-
end
|
77
|
-
|
78
|
-
def create(params)
|
79
|
-
builder = Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
|
80
|
-
xml.CreateAffinityGroup(
|
81
|
-
xmlns: "http://schemas.microsoft.com/windowsazure"
|
82
|
-
) do
|
83
|
-
xml.Name params[:azure_ag_name]
|
84
|
-
xml.Label Base64.strict_encode64(params[:azure_ag_name])
|
85
|
-
unless params[:azure_ag_desc].nil?
|
86
|
-
xml.Description params[:azure_ag_desc]
|
87
|
-
end
|
88
|
-
xml.Location params[:azure_location]
|
89
|
-
end
|
90
|
-
end
|
91
|
-
@connection.query_azure("affinitygroups",
|
92
|
-
"post",
|
93
|
-
builder.to_xml,
|
94
|
-
"",
|
95
|
-
true,
|
96
|
-
false)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
@@ -1,235 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Mukta Aphale (mukta.aphale@clogeny.com)
|
3
|
-
# Copyright:: Copyright (c) Chef Software Inc.
|
4
|
-
# License:: Apache License, Version 2.0
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#
|
18
|
-
|
19
|
-
module Azure
|
20
|
-
class Certificates
|
21
|
-
def initialize(connection)
|
22
|
-
@connection = connection
|
23
|
-
end
|
24
|
-
|
25
|
-
def create(params)
|
26
|
-
certificate = Certificate.new(@connection)
|
27
|
-
certificate.create(params)
|
28
|
-
end
|
29
|
-
|
30
|
-
def add(certificate_data, certificate_password, certificate_format, dns_name)
|
31
|
-
certificate = Certificate.new(@connection)
|
32
|
-
certificate.add_certificate certificate_data, certificate_password, certificate_format, dns_name
|
33
|
-
end
|
34
|
-
|
35
|
-
def create_ssl_certificate(azure_dns_name)
|
36
|
-
cert_params = { output_file: "winrm", key_length: 2048, cert_validity: 24,
|
37
|
-
azure_dns_name: azure_dns_name }
|
38
|
-
certificate = Certificate.new(@connection)
|
39
|
-
thumbprint = certificate.create_ssl_certificate(cert_params)
|
40
|
-
end
|
41
|
-
|
42
|
-
def get_certificate(dns_name, fingerprint)
|
43
|
-
certificate = Certificate.new(@connection)
|
44
|
-
certificate.get_certificate(dns_name, fingerprint)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
module Azure
|
50
|
-
class Certificate
|
51
|
-
attr_accessor :connection
|
52
|
-
attr_accessor :cert_data, :fingerprint, :certificate_version
|
53
|
-
def initialize(connection)
|
54
|
-
@connection = connection
|
55
|
-
@certificate_version = 2 # cf. RFC 5280 - to make it a "v3" certificate
|
56
|
-
end
|
57
|
-
|
58
|
-
def create(params)
|
59
|
-
# If RSA private key has been specified, then generate an x 509 certificate from the
|
60
|
-
# public part of the key
|
61
|
-
@cert_data = generate_public_key_certificate_data({ ssh_key: params[:ssh_identity_file],
|
62
|
-
ssh_key_passphrase: params[:identity_file_passphrase] })
|
63
|
-
add_certificate @cert_data, "knifeazure", "pfx", params[:azure_dns_name]
|
64
|
-
|
65
|
-
# Return the fingerprint to be used while adding role
|
66
|
-
@fingerprint
|
67
|
-
end
|
68
|
-
|
69
|
-
def generate_public_key_certificate_data(params)
|
70
|
-
# Generate OpenSSL RSA key from the mentioned ssh key path (and passphrase)
|
71
|
-
key = OpenSSL::PKey::RSA.new(File.read(params[:ssh_key]), params[:ssh_key_passphrase])
|
72
|
-
# Generate X 509 certificate
|
73
|
-
ca = OpenSSL::X509::Certificate.new
|
74
|
-
ca.version = @certificate_version
|
75
|
-
ca.serial = Random.rand(65534) + 1 # 2 digit byte range random number for better security aspect
|
76
|
-
ca.subject = OpenSSL::X509::Name.parse "/DC=org/DC=knife-plugin/CN=Opscode CA"
|
77
|
-
ca.issuer = ca.subject # root CA's are "self-signed"
|
78
|
-
ca.public_key = key.public_key # Assign the ssh-key's public part to the certificate
|
79
|
-
ca.not_before = Time.now
|
80
|
-
ca.not_after = ca.not_before + 2 * 365 * 24 * 60 * 60 # 2 years validity
|
81
|
-
ef = OpenSSL::X509::ExtensionFactory.new
|
82
|
-
ef.subject_certificate = ca
|
83
|
-
ef.issuer_certificate = ca
|
84
|
-
ca.add_extension(ef.create_extension("basicConstraints", "CA:TRUE", true))
|
85
|
-
ca.add_extension(ef.create_extension("keyUsage", "keyCertSign, cRLSign", true))
|
86
|
-
ca.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
|
87
|
-
ca.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false))
|
88
|
-
ca.sign(key, OpenSSL::Digest::SHA256.new)
|
89
|
-
# Generate the SHA1 fingerprint of the der format of the X 509 certificate
|
90
|
-
@fingerprint = OpenSSL::Digest::SHA1.new(ca.to_der)
|
91
|
-
# Create the pfx format of the certificate
|
92
|
-
pfx = OpenSSL::PKCS12.create("knifeazure", "knife-azure-pfx", key, ca)
|
93
|
-
# Encode the pfx format - upload this certificate
|
94
|
-
Base64.strict_encode64(pfx.to_der)
|
95
|
-
end
|
96
|
-
|
97
|
-
def add_certificate(certificate_data, certificate_password, certificate_format, dns_name)
|
98
|
-
# Generate XML to call the API
|
99
|
-
# Add certificate to the hosted service
|
100
|
-
builder = Nokogiri::XML::Builder.new do |xml|
|
101
|
-
xml.CertificateFile("xmlns" => "http://schemas.microsoft.com/windowsazure") do
|
102
|
-
xml.Data certificate_data
|
103
|
-
xml.CertificateFormat certificate_format
|
104
|
-
xml.Password certificate_password
|
105
|
-
end
|
106
|
-
end
|
107
|
-
# Windows Azure API call
|
108
|
-
@connection.query_azure("hostedservices/#{dns_name}/certificates", "post", builder.to_xml)
|
109
|
-
|
110
|
-
# Check if certificate is available else raise error
|
111
|
-
for attempt in 0..4
|
112
|
-
Chef::Log.info "Waiting to get certificate ..."
|
113
|
-
res = get_certificate(dns_name, @fingerprint)
|
114
|
-
break unless res.empty?
|
115
|
-
if attempt == 4
|
116
|
-
raise "The certificate with thumbprint #{fingerprint} was not found."
|
117
|
-
else
|
118
|
-
sleep 5
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def get_certificate(dns_name, fingerprint)
|
124
|
-
@connection.query_azure("hostedservices/#{dns_name}/certificates/sha1-#{fingerprint}", "get").search("Certificate")
|
125
|
-
end
|
126
|
-
|
127
|
-
######## SSL certificate generation for knife-azure ssl bootstrap ######
|
128
|
-
def create_ssl_certificate(cert_params)
|
129
|
-
file_path = cert_params[:output_file].sub(/\.(\w+)$/, "")
|
130
|
-
path = prompt_for_file_path
|
131
|
-
file_path = File.join(path, file_path) unless path.empty?
|
132
|
-
cert_params[:domain] = prompt_for_domain
|
133
|
-
|
134
|
-
rsa_key = generate_keypair cert_params[:key_length]
|
135
|
-
cert = generate_certificate(rsa_key, cert_params)
|
136
|
-
write_certificate_to_file cert, file_path, rsa_key, cert_params
|
137
|
-
puts "*" * 70
|
138
|
-
puts "Generated Certificates:"
|
139
|
-
puts "- #{file_path}.pfx - PKCS12 format keypair. Contains both the public and private keys, usually used on the server."
|
140
|
-
puts "- #{file_path}.b64 - Base64 encoded PKCS12 keypair. Contains both the public and private keys, for upload to the Azure REST API."
|
141
|
-
puts "- #{file_path}.pem - Base64 encoded public certificate only. Required by the client to connect to the server."
|
142
|
-
puts "Certificate Thumbprint: #{@thumbprint.to_s.upcase}"
|
143
|
-
puts "*" * 70
|
144
|
-
|
145
|
-
config[:ca_trust_file] = file_path + ".pem" if config[:ca_trust_file].nil?
|
146
|
-
cert_data = File.read (file_path + ".b64")
|
147
|
-
add_certificate cert_data, @winrm_cert_passphrase, "pfx", cert_params[:azure_dns_name]
|
148
|
-
@thumbprint
|
149
|
-
end
|
150
|
-
|
151
|
-
def generate_keypair(key_length)
|
152
|
-
OpenSSL::PKey::RSA.new(key_length.to_i)
|
153
|
-
end
|
154
|
-
|
155
|
-
def prompt_for_passphrase
|
156
|
-
passphrase = ""
|
157
|
-
begin
|
158
|
-
print "Passphrases do not match. Try again.\n" unless passphrase.empty?
|
159
|
-
print "Enter certificate passphrase (empty for no passphrase):"
|
160
|
-
passphrase = STDIN.gets
|
161
|
-
return passphrase.strip if passphrase == "\n"
|
162
|
-
|
163
|
-
print "Enter same passphrase again:"
|
164
|
-
confirm_passphrase = STDIN.gets
|
165
|
-
end until passphrase == confirm_passphrase
|
166
|
-
passphrase.strip
|
167
|
-
end
|
168
|
-
|
169
|
-
def prompt_for_file_path
|
170
|
-
file_path = ""
|
171
|
-
counter = 0
|
172
|
-
begin
|
173
|
-
print "Invalid location! \n" unless file_path.empty?
|
174
|
-
print 'Enter the file path for certificates e.g. C:\Windows (empty for current location):'
|
175
|
-
file_path = STDIN.gets
|
176
|
-
stripped_file_path = file_path.strip
|
177
|
-
return stripped_file_path if file_path == "\n"
|
178
|
-
|
179
|
-
counter += 1
|
180
|
-
exit(1) if counter == 3
|
181
|
-
end until File.directory?(stripped_file_path)
|
182
|
-
stripped_file_path
|
183
|
-
end
|
184
|
-
|
185
|
-
def prompt_for_domain
|
186
|
-
counter = 0
|
187
|
-
begin
|
188
|
-
print "Enter the domain (mandatory):"
|
189
|
-
domain = STDIN.gets
|
190
|
-
domain = domain.strip
|
191
|
-
counter += 1
|
192
|
-
exit(1) if counter == 3
|
193
|
-
end until !domain.empty?
|
194
|
-
domain
|
195
|
-
end
|
196
|
-
|
197
|
-
def generate_certificate(rsa_key, cert_params)
|
198
|
-
@hostname = "*"
|
199
|
-
if cert_params[:domain]
|
200
|
-
@hostname = "*." + cert_params[:domain]
|
201
|
-
end
|
202
|
-
|
203
|
-
# Create a self-signed X509 certificate from the rsa_key (unencrypted)
|
204
|
-
cert = OpenSSL::X509::Certificate.new
|
205
|
-
cert.version = 2
|
206
|
-
cert.serial = Random.rand(65534) + 1 # 2 digit byte range random number for better security aspect
|
207
|
-
|
208
|
-
cert.subject = OpenSSL::X509::Name.parse "/CN=#{@hostname}"
|
209
|
-
cert.issuer = cert.subject
|
210
|
-
cert.public_key = rsa_key.public_key
|
211
|
-
cert.not_before = Time.now
|
212
|
-
cert.not_after = cert.not_before + 2 * 365 * cert_params[:cert_validity].to_i * 60 * 60 # 2 years validity
|
213
|
-
ef = OpenSSL::X509::ExtensionFactory.new
|
214
|
-
ef.subject_certificate = cert
|
215
|
-
ef.issuer_certificate = cert
|
216
|
-
cert.add_extension(ef.create_extension("subjectKeyIdentifier", "hash", false))
|
217
|
-
cert.add_extension(ef.create_extension("authorityKeyIdentifier", "keyid:always", false))
|
218
|
-
cert.add_extension(ef.create_extension("extendedKeyUsage", "1.3.6.1.5.5.7.3.1", false))
|
219
|
-
cert.sign(rsa_key, OpenSSL::Digest::SHA1.new)
|
220
|
-
@thumbprint = OpenSSL::Digest::SHA1.new(cert.to_der)
|
221
|
-
cert
|
222
|
-
end
|
223
|
-
|
224
|
-
def write_certificate_to_file(cert, file_path, rsa_key, cert_params)
|
225
|
-
File.open(file_path + ".pem", "wb") { |f| f.print cert.to_pem }
|
226
|
-
@winrm_cert_passphrase = prompt_for_passphrase unless @winrm_cert_passphrase
|
227
|
-
pfx = OpenSSL::PKCS12.create("#{cert_params[:winrm_cert_passphrase]}", "winrmcert", rsa_key, cert)
|
228
|
-
File.open(file_path + ".pfx", "wb") { |f| f.print pfx.to_der }
|
229
|
-
File.open(file_path + ".b64", "wb") { |f| f.print Base64.strict_encode64(pfx.to_der) }
|
230
|
-
end
|
231
|
-
|
232
|
-
########## SSL certificate generation ends ###########
|
233
|
-
|
234
|
-
end
|
235
|
-
end
|