knife-azure 3.0.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|