knife-azure 4.0.0 → 5.0.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.
@@ -1,7 +1,7 @@
1
1
  #
2
2
  # Author:: Aliasgar Batterywala (aliasgar.batterywala@clogeny.com)
3
3
  #
4
- # Copyright:: Copyright (c) Chef Software Inc.
4
+ # Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
5
5
  # License:: Apache License, Version 2.0
6
6
  #
7
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -39,7 +39,7 @@ class Chef
39
39
  require "json" unless defined?(JSON)
40
40
 
41
41
  if Chef::Platform.windows?
42
- require_relative "../../azure/resource_management/windows_credentials"
42
+ require_relative "../../../azure/resource_management/windows_credentials"
43
43
  include Azure::ARM::WindowsCredentials
44
44
  end
45
45
  end
@@ -48,12 +48,18 @@ class Chef
48
48
  short: "-r RESOURCE_GROUP_NAME",
49
49
  long: "--azure-resource-group-name RESOURCE_GROUP_NAME",
50
50
  description: "The Resource Group name."
51
+
52
+ option :azure_environment,
53
+ long: "--azure-environment ENVIRONMENT",
54
+ description: "Optional. The Azure cloud to target: AzureCloud (default), " \
55
+ "AzureUSGovernment, AzureChinaCloud, or AzureGermanCloud.",
56
+ default: "AzureCloud"
51
57
  end
52
58
  end
53
59
 
54
60
  def service
55
61
  details = authentication_details
56
- details.update(azure_subscription_id: config[:azure_subscription_id])
62
+ details.update(azure_subscription_id: config[:azure_subscription_id], azure_environment: config[:azure_environment])
57
63
  @service ||= begin
58
64
  require_relative "../../../azure/resource_management/ARM_interface"
59
65
  service = Azure::ResourceManagement::ARMInterface.new(details)
@@ -98,13 +104,38 @@ class Chef
98
104
 
99
105
  def get_azure_cli_version
100
106
  if @azure_version != ""
101
- get_version = shell_out!("azure -v || az -v | grep azure-cli", returns: [0]).stdout
107
+ get_version = azure_cli_raw_version_output
102
108
  @azure_version = get_version.gsub(/[^0-9.]/, "")
103
109
  end
104
110
  @azure_prefix = @azure_version.to_i < 2 ? "azure" : "az"
105
111
  @azure_version
106
112
  end
107
113
 
114
+ # Detects whether the deprecated xplat "azure" CLI or the modern "az"
115
+ # CLI is installed and returns its raw version output. Filtering for
116
+ # the "azure-cli" line is done in Ruby (rather than via `grep`, and
117
+ # without relying on unix-style shell chaining like `||`/`|`) so this
118
+ # works identically on Windows, where `grep` isn't available and
119
+ # Mixlib::ShellOut runs commands through cmd.exe.
120
+ def azure_cli_raw_version_output
121
+ begin
122
+ result = shell_out("azure -v")
123
+ return result.stdout if result.exitstatus.zero?
124
+ rescue Errno::ENOENT
125
+ # The deprecated "azure" xplat CLI isn't installed on this system's PATH,
126
+ # which is expected/fine for the vast majority of users today. Fall
127
+ # through and try the modern "az" CLI instead.
128
+ end
129
+
130
+ begin
131
+ result = shell_out!("az -v")
132
+ rescue Errno::ENOENT
133
+ raise "Could not find either the 'azure' (deprecated) or the 'az' Azure CLI on this system's PATH. " \
134
+ "Please install the Azure CLI: https://learn.microsoft.com/cli/azure/install-azure-cli"
135
+ end
136
+ result.stdout.each_line.find { |line| line.include?("azure-cli") } || result.stdout
137
+ end
138
+
108
139
  def token_details_for_windows
109
140
  if is_old_xplat?
110
141
  token_details_from_WCM
@@ -176,8 +207,13 @@ class Chef
176
207
  require "base64" unless defined?(Base64)
177
208
  require "openssl" unless defined?(OpenSSL)
178
209
  require "uri" unless defined?(URI)
210
+ retried_with_legacy_provider = false
179
211
  begin
180
- doc = Nokogiri::XML(File.open(find_file(filename)))
212
+ # Use the block form of File.open so the file handle is always closed
213
+ # after parsing, rather than left open for the GC to close later. This
214
+ # matters on Windows, where an open handle prevents a Tempfile-based
215
+ # fixture (used in specs) from being unlinked.
216
+ doc = File.open(find_file(filename)) { |file| Nokogiri::XML(file) }
181
217
  profile = doc.at_css("PublishProfile")
182
218
  subscription = profile.at_css("Subscription")
183
219
  # check given PublishSettings XML file format.Currently PublishSettings file have two different XML format
@@ -192,12 +228,105 @@ class Chef
192
228
  end
193
229
  config[:azure_mgmt_cert] = management_cert.certificate.to_pem + management_cert.key.to_pem
194
230
  config[:azure_subscription_id] = doc.at_css("Subscription").attribute("Id").value
231
+ rescue OpenSSL::PKCS12::PKCS12Error => error
232
+ # Older Azure publish settings files use PKCS12 certificates encrypted with
233
+ # the legacy RC2-40-CBC cipher, which OpenSSL 3.x disables by default. On
234
+ # OpenSSL 3.x the raised error usually doesn't mention "RC2-40-CBC" at all -
235
+ # it's typically the generic "PKCS12_parse: unsupported" - so treat any
236
+ # "unsupported"-style PKCS12Error as a candidate for the legacy-cipher retry,
237
+ # not just messages that explicitly say RC2-40-CBC. Only load OpenSSL's
238
+ # "legacy" provider (widening the process-wide crypto surface) if we
239
+ # actually hit one of these failures, and only retry once.
240
+ if !retried_with_legacy_provider && legacy_cipher_error?(error) && load_openssl_legacy_provider
241
+ retried_with_legacy_provider = true
242
+ retry
243
+ end
244
+
245
+ if legacy_cipher_error?(error)
246
+ ui.error("Cannot parse certificate: #{error.message}")
247
+ if retried_with_legacy_provider
248
+ # We already successfully loaded OpenSSL's legacy provider and retried,
249
+ # but parsing still failed with what looks like a legacy-cipher error.
250
+ # The cipher isn't "unavailable" in this case -- something else about
251
+ # the certificate is unparseable -- so don't tell the user to
252
+ # regenerate it with a "more recent cipher" as if the provider were
253
+ # still missing.
254
+ ui.error("The PKCS12 certificate could not be parsed even after enabling OpenSSL's " \
255
+ "legacy provider (used for ciphers such as RC2-40-CBC). The file may be corrupt " \
256
+ "or use a cipher that isn't supported even by the legacy provider.")
257
+ else
258
+ ui.error("The PKCS12 certificate may use the legacy RC2-40-CBC cipher, which is unavailable " \
259
+ "in the current OpenSSL configuration. Please regenerate the publish settings file " \
260
+ "with a more recent cipher.")
261
+ end
262
+ else
263
+ ui.error("Error parsing PKCS12 certificate: #{error.message}")
264
+ end
265
+ exit 1
195
266
  rescue => error
196
267
  puts "#{error.class} and #{error.message}"
197
268
  exit 1
198
269
  end
199
270
  end
200
271
 
272
+ # Returns true if the given OpenSSL::PKCS12::PKCS12Error looks like it was
273
+ # caused by a legacy/deprecated cipher (such as RC2-40-CBC) being disabled by
274
+ # default on OpenSSL 3.x. On OpenSSL 3.x this commonly surfaces as a generic
275
+ # "unsupported" error rather than one that names RC2-40-CBC explicitly.
276
+ def legacy_cipher_error?(error)
277
+ error.message =~ /RC2-40-CBC/i || error.message =~ /unsupported/i
278
+ end
279
+
280
+ # Attempts to load OpenSSL's "legacy" provider (needed to decrypt PKCS12 files
281
+ # using deprecated ciphers such as RC2-40-CBC). Returns true if the provider was
282
+ # loaded successfully, false otherwise (e.g. it isn't available on this system).
283
+ def load_openssl_legacy_provider
284
+ if defined?(OpenSSL::Provider)
285
+ OpenSSL::Provider.load("legacy")
286
+ OpenSSL::Provider.load("default")
287
+ return true
288
+ end
289
+
290
+ # `OpenSSL::Provider` was only added to the "openssl" Ruby gem in version 3.0,
291
+ # which ships as a default gem starting with Ruby 3.2. On supported Ruby 3.1
292
+ # installations (bundled openssl gem < 3.0) there's no Ruby API to load an
293
+ # OpenSSL 3.x provider, even though the underlying libcrypto may itself be
294
+ # OpenSSL 3.x and support providers. Fall back to calling libcrypto's
295
+ # OSSL_PROVIDER_load directly via FFI (already a runtime dependency of this
296
+ # gem) so legacy PKCS12 files can still be decrypted on Ruby 3.1.
297
+ load_openssl_legacy_provider_via_ffi
298
+ rescue StandardError, LoadError
299
+ false
300
+ end
301
+
302
+ # Loads the OpenSSL "legacy" and "default" providers by calling libcrypto's
303
+ # OSSL_PROVIDER_load function directly through FFI. This only works when the
304
+ # linked libcrypto is OpenSSL 3.x (the function doesn't exist on OpenSSL 1.1.1
305
+ # or LibreSSL); any failure to locate the library/symbol is treated as the
306
+ # provider simply being unavailable.
307
+ def load_openssl_legacy_provider_via_ffi
308
+ require "ffi" unless defined?(FFI)
309
+
310
+ # Bind to OSSL_PROVIDER_load in the *current process's* already-loaded
311
+ # symbol table (FFI::Library::CURRENT_PROCESS) rather than dlopen-ing "ssl"/"crypto"
312
+ # again. Re-loading libcrypto as a second, separate mapping alongside the
313
+ # one Ruby's own "openssl" extension already loaded can make OpenSSL 3.x
314
+ # detect what it considers an unsafe double-load and abort the whole
315
+ # process (observed as "libcrypto in an unsafe way" on macOS); attaching to
316
+ # the existing in-process symbols avoids that entirely.
317
+ provider_loader = Module.new do
318
+ extend FFI::Library
319
+ ffi_lib FFI::Library::CURRENT_PROCESS
320
+ attach_function :OSSL_PROVIDER_load, %i{pointer string}, :pointer
321
+ end
322
+
323
+ legacy = provider_loader.OSSL_PROVIDER_load(nil, "legacy")
324
+ default = provider_loader.OSSL_PROVIDER_load(nil, "default")
325
+ !legacy.null? && !default.null?
326
+ rescue StandardError, LoadError
327
+ false
328
+ end
329
+
201
330
  def find_file(name)
202
331
  name = ::File.expand_path(name)
203
332
  config_dir = Chef::Knife.chef_config_dir
@@ -275,10 +404,10 @@ class Chef
275
404
  end
276
405
 
277
406
  if !is_image_windows?
278
- if (config[:azure_vm_name].match(/^(?=.*[a-zA-Z-])([a-zA-z0-9-]{1,64})$/)).nil?
407
+ if (config[:azure_vm_name].match(/^(?=.*[a-zA-Z-])([a-zA-Z0-9-]{1,64})$/)).nil?
279
408
  raise ArgumentError, "VM name can only contain alphanumeric and hyphen(-) characters and maximum length cannot exceed 64 characters."
280
409
  end
281
- elsif (config[:azure_vm_name].match(/^(?=.*[a-zA-Z-])([a-zA-z0-9-]{1,15})$/)).nil?
410
+ elsif (config[:azure_vm_name].match(/^(?=.*[a-zA-Z-])([a-zA-Z0-9-]{1,15})$/)).nil?
282
411
  raise ArgumentError, "VM name can only contain alphanumeric and hyphen(-) characters and maximum length cannot exceed 15 characters."
283
412
  end
284
413
 
@@ -302,6 +431,38 @@ class Chef
302
431
  end
303
432
  end
304
433
 
434
+ if config[:azure_storage_account_type]
435
+ # --azure-storage-account-type now sets the managed OS disk's SKU rather than a
436
+ # storage account's replication type; the old storage-account-only replication
437
+ # values (Standard_ZRS, Standard_GRS, Standard_RAGRS) are not valid managed disk
438
+ # SKUs and would otherwise be sent through to Azure and fail remotely.
439
+ legacy_storage_account_types = %w{Standard_ZRS Standard_GRS Standard_RAGRS}
440
+ valid_managed_disk_types = %w{Standard_LRS StandardSSD_LRS Premium_LRS StandardSSD_ZRS Premium_ZRS}
441
+ if legacy_storage_account_types.include?(config[:azure_storage_account_type])
442
+ raise ArgumentError, "'#{config[:azure_storage_account_type]}' is a storage-account replication type " \
443
+ "that is no longer valid for --azure-storage-account-type, since VMs now use managed disks. " \
444
+ "Please use one of the managed disk SKUs instead: #{valid_managed_disk_types.join(", ")}."
445
+ elsif !valid_managed_disk_types.include?(config[:azure_storage_account_type])
446
+ raise ArgumentError, "Invalid value '#{config[:azure_storage_account_type]}' for --azure-storage-account-type. " \
447
+ "Use one of the following managed disk SKUs: #{valid_managed_disk_types.join(", ")}."
448
+ end
449
+ end
450
+
451
+ if config[:azure_availability_set]
452
+ # Azure availability set SKUs are immutable once created. The deployment
453
+ # template always creates/updates the set as "Aligned" (required for VMs with
454
+ # managed disks), so reusing the name of a pre-existing legacy "Classic" set
455
+ # (e.g. from an older unmanaged-disk deployment) would fail remotely with a
456
+ # cryptic ARM error. Detect that case here and fail fast with guidance instead.
457
+ existing_sku = service.existing_availability_set_sku(config[:azure_resource_group_name], config[:azure_availability_set])
458
+ if existing_sku != :not_found && existing_sku != "Aligned"
459
+ raise ArgumentError, "The availability set '#{config[:azure_availability_set]}' already exists as a " \
460
+ "legacy 'Classic' (unmanaged-disk) availability set. Azure availability set SKUs are immutable, so " \
461
+ "it cannot be converted to 'Aligned' for use with managed disks. Please choose a different, unused " \
462
+ "name for --azure-availability-set, or delete and recreate the existing availability set before reusing it."
463
+ end
464
+ end
465
+
305
466
  config[:ohai_hints] = format_ohai_hints(config[:ohai_hints])
306
467
  validate_ohai_hints unless config[:ohai_hints].casecmp("default").zero?
307
468
  end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright:: Copyright (c) Chef Software Inc.
2
+ # Copyright:: Copyright (c) 2012-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
3
3
  # License:: Apache License, Version 2.0
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,7 +17,7 @@
17
17
 
18
18
  module Knife
19
19
  module Azure
20
- VERSION = "4.0.0".freeze
20
+ VERSION = "5.0.1".freeze
21
21
  MAJOR, MINOR, TINY = VERSION.split(".")
22
22
  end
23
23
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-azure
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 5.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Barry Davis
8
8
  - Chirag Jog
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-06-03 00:00:00.000000000 Z
12
+ date: 2026-10-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: knife
@@ -17,108 +17,56 @@ dependencies:
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: '0'
21
- type: :runtime
22
- prerelease: false
23
- version_requirements: !ruby/object:Gem::Requirement
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- version: '0'
28
- - !ruby/object:Gem::Dependency
29
- name: nokogiri
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - ">="
33
- - !ruby/object:Gem::Version
34
- version: 1.5.5
35
- type: :runtime
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - ">="
40
- - !ruby/object:Gem::Version
41
- version: 1.5.5
42
- - !ruby/object:Gem::Dependency
43
- name: azure_mgmt_resources
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - "~>"
47
- - !ruby/object:Gem::Version
48
- version: '0.17'
49
- - - ">="
50
- - !ruby/object:Gem::Version
51
- version: 0.17.2
20
+ version: '18.10'
52
21
  type: :runtime
53
22
  prerelease: false
54
23
  version_requirements: !ruby/object:Gem::Requirement
55
24
  requirements:
56
- - - "~>"
57
- - !ruby/object:Gem::Version
58
- version: '0.17'
59
25
  - - ">="
60
26
  - !ruby/object:Gem::Version
61
- version: 0.17.2
27
+ version: '18.10'
62
28
  - !ruby/object:Gem::Dependency
63
- name: azure_mgmt_compute
29
+ name: abbrev
64
30
  requirement: !ruby/object:Gem::Requirement
65
31
  requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '0.18'
69
32
  - - ">="
70
33
  - !ruby/object:Gem::Version
71
- version: 0.18.3
34
+ version: '0'
72
35
  type: :runtime
73
36
  prerelease: false
74
37
  version_requirements: !ruby/object:Gem::Requirement
75
38
  requirements:
76
- - - "~>"
77
- - !ruby/object:Gem::Version
78
- version: '0.18'
79
39
  - - ">="
80
40
  - !ruby/object:Gem::Version
81
- version: 0.18.3
41
+ version: '0'
82
42
  - !ruby/object:Gem::Dependency
83
- name: azure_mgmt_storage
43
+ name: chef
84
44
  requirement: !ruby/object:Gem::Requirement
85
45
  requirements:
86
- - - "~>"
87
- - !ruby/object:Gem::Version
88
- version: '0.20'
89
46
  - - ">="
90
47
  - !ruby/object:Gem::Version
91
- version: 0.20.0
48
+ version: '18.10'
92
49
  type: :runtime
93
50
  prerelease: false
94
51
  version_requirements: !ruby/object:Gem::Requirement
95
52
  requirements:
96
- - - "~>"
97
- - !ruby/object:Gem::Version
98
- version: '0.20'
99
53
  - - ">="
100
54
  - !ruby/object:Gem::Version
101
- version: 0.20.0
55
+ version: '18.10'
102
56
  - !ruby/object:Gem::Dependency
103
- name: azure_mgmt_network
57
+ name: nokogiri
104
58
  requirement: !ruby/object:Gem::Requirement
105
59
  requirements:
106
- - - "~>"
107
- - !ruby/object:Gem::Version
108
- version: '0.18'
109
60
  - - ">="
110
61
  - !ruby/object:Gem::Version
111
- version: 0.18.2
62
+ version: 1.5.5
112
63
  type: :runtime
113
64
  prerelease: false
114
65
  version_requirements: !ruby/object:Gem::Requirement
115
66
  requirements:
116
- - - "~>"
117
- - !ruby/object:Gem::Version
118
- version: '0.18'
119
67
  - - ">="
120
68
  - !ruby/object:Gem::Version
121
- version: 0.18.2
69
+ version: 1.5.5
122
70
  - !ruby/object:Gem::Dependency
123
71
  name: listen
124
72
  requirement: !ruby/object:Gem::Requirement
@@ -189,6 +137,13 @@ files:
189
137
  - lib/azure/helpers.rb
190
138
  - lib/azure/resource_management/ARM_deployment_template.rb
191
139
  - lib/azure/resource_management/ARM_interface.rb
140
+ - lib/azure/resource_management/rest/arm_client.rb
141
+ - lib/azure/resource_management/rest/environments.rb
142
+ - lib/azure/resource_management/rest/errors.rb
143
+ - lib/azure/resource_management/rest/http.rb
144
+ - lib/azure/resource_management/rest/rest_object.rb
145
+ - lib/azure/resource_management/rest/secure_connection.rb
146
+ - lib/azure/resource_management/rest/token_provider.rb
192
147
  - lib/azure/resource_management/vnet_config.rb
193
148
  - lib/azure/resource_management/windows_credentials.rb
194
149
  - lib/chef/knife/azurerm_server_create.rb
@@ -204,7 +159,7 @@ homepage: https://github.com/chef/knife-azure
204
159
  licenses:
205
160
  - Apache 2.0
206
161
  metadata: {}
207
- post_install_message:
162
+ post_install_message:
208
163
  rdoc_options: []
209
164
  require_paths:
210
165
  - lib
@@ -212,15 +167,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
212
167
  requirements:
213
168
  - - ">="
214
169
  - !ruby/object:Gem::Version
215
- version: '2.7'
170
+ version: '3.1'
216
171
  required_rubygems_version: !ruby/object:Gem::Requirement
217
172
  requirements:
218
173
  - - ">="
219
174
  - !ruby/object:Gem::Version
220
175
  version: '0'
221
176
  requirements: []
222
- rubygems_version: 3.1.4
223
- signing_key:
177
+ rubygems_version: 3.3.27
178
+ signing_key:
224
179
  specification_version: 4
225
180
  summary: A plugin to the Chef Infra knife tool for creating instances on the Microsoft
226
181
  Azure platform