chef 18.10.17 → 18.11.11

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +15 -7
  3. data/README.md +0 -1
  4. data/chef-universal-mingw-ucrt.gemspec +4 -2
  5. data/chef.gemspec +15 -12
  6. data/lib/chef/compliance/runner.rb +19 -1
  7. data/lib/chef/dsl/rest_resource.rb +223 -16
  8. data/lib/chef/formatters/error_description.rb +8 -1
  9. data/lib/chef/http/authenticator.rb +17 -4
  10. data/lib/chef/mixin/template.rb +24 -2
  11. data/lib/chef/monkey_patches/reline-windows.rb +116 -0
  12. data/lib/chef/provider/file/content.rb +3 -2
  13. data/lib/chef/provider/file.rb +1 -1
  14. data/lib/chef/provider/ifconfig/redhat.rb +1 -1
  15. data/lib/chef/provider/package/dnf/dnf_helper.py +355 -65
  16. data/lib/chef/provider/package/dnf/python_helper.rb +7 -4
  17. data/lib/chef/provider/package/dnf.rb +25 -6
  18. data/lib/chef/provider/package/yum.rb +1 -1
  19. data/lib/chef/resource/_rest_resource.rb +4 -2
  20. data/lib/chef/resource/archive_file.rb +52 -18
  21. data/lib/chef/resource/chef_client_config.rb +185 -19
  22. data/lib/chef/resource/chef_vault_secret.rb +1 -1
  23. data/lib/chef/resource/chocolatey_installer.rb +31 -13
  24. data/lib/chef/resource/chocolatey_package.rb +1 -1
  25. data/lib/chef/resource/habitat/habitat_package.rb +1 -1
  26. data/lib/chef/resource/habitat/habitat_sup.rb +2 -2
  27. data/lib/chef/resource/habitat_config.rb +1 -1
  28. data/lib/chef/resource/habitat_service.rb +1 -1
  29. data/lib/chef/resource/openssl_ec_private_key.rb +1 -1
  30. data/lib/chef/resource/openssl_ec_public_key.rb +2 -2
  31. data/lib/chef/resource/openssl_rsa_private_key.rb +1 -1
  32. data/lib/chef/resource/openssl_rsa_public_key.rb +2 -2
  33. data/lib/chef/resource/openssl_x509_certificate.rb +1 -1
  34. data/lib/chef/resource/openssl_x509_request.rb +1 -1
  35. data/lib/chef/resource/powershell_package_source.rb +1 -1
  36. data/lib/chef/resource/rhsm_register.rb +1 -1
  37. data/lib/chef/resource/windows_ad_join.rb +1 -1
  38. data/lib/chef/resource/windows_certificate.rb +1 -1
  39. data/lib/chef/resource/windows_service.rb +1 -1
  40. data/lib/chef/resource/windows_task.rb +1 -1
  41. data/lib/chef/resource/yum_package.rb +1 -1
  42. data/lib/chef/resource/yum_repository.rb +2 -2
  43. data/lib/chef/shell.rb +7 -0
  44. data/lib/chef/version.rb +1 -1
  45. metadata +59 -34
@@ -1,5 +1,8 @@
1
+ #!/usr/bin/env python3
1
2
  #
2
- # Copyright:: Copyright (c) Chef Software Inc.
3
+ # Copyright:: Copyright (c) 2009-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
4
+ # Copyright:: Copyright (c) 2026 Meta Platforms, Inc.
5
+ # Copyright:: Copyright (c) 2026 Phil Dibowitz
3
6
  # License:: Apache License, Version 2.0
4
7
  #
5
8
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -41,10 +44,10 @@ class Chef
41
44
 
42
45
  def dnf_command
43
46
  # platform-python is used for system tools on RHEL 8 and is installed under /usr/libexec
47
+ py_cmd = "try:\n import libdnf5\nexcept ImportError:\n import dnf"
44
48
  @dnf_command ||= begin
45
- cmd = which("platform-python", "python", "python3", "python2", "python2.7", extra_path: "/usr/libexec") do |f|
46
- shell_out("#{f} -c 'import dnf'").exitstatus == 0
47
- end
49
+ executables = where("platform-python", "python", "python3", "python2", "python2.7", extra_path: "/usr/libexec")
50
+ cmd = executables.find { |f| shell_out("#{f} -c '#{py_cmd}'").exitstatus == 0 }
48
51
  raise Chef::Exceptions::Package, "cannot find dnf libraries, you may need to use yum_package" unless cmd
49
52
 
50
53
  "#{cmd} #{DNF_HELPER}"
@@ -1,5 +1,7 @@
1
1
  #
2
- # Copyright:: Copyright (c) Chef Software Inc.
2
+ # Copyright:: Copyright (c) 2009-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
3
+ # Copyright:: Copyright (c) 2026 Meta Platforms, Inc.
4
+ # Copyright:: Copyright (c) 2026 Phil Dibowitz
3
5
  # License:: Apache License, Version 2.0
4
6
  #
5
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -62,6 +64,13 @@ class Chef
62
64
  @python_helper ||= PythonHelper.instance
63
65
  end
64
66
 
67
+ def dnf5?
68
+ @dnf5 ||= begin
69
+ dnf_version = shell_out!("dnf --version").stdout
70
+ dnf_version =~ /dnf5/i
71
+ end
72
+ end
73
+
65
74
  def load_current_resource
66
75
  flushcache if new_resource.flush_cache[:before]
67
76
 
@@ -138,14 +147,19 @@ class Chef
138
147
  # NB: the dnf_package provider manages individual single packages, please do not submit issues or PRs to try to add wildcard
139
148
  # support to lock / unlock. The best solution is to write an execute resource which does a not_if `dnf versionlock | grep '^pattern`` kind of approach
140
149
  def lock_package(names, versions)
141
- dnf("-d0", "-e0", "-y", options, "versionlock", "add", resolved_package_lock_names(names))
150
+ default_opts = dnf5? ? [] : %w{-d0 -e0}
151
+ dnf(default_opts, options, "versionlock", "add", resolved_package_lock_names(names))
142
152
  end
143
153
 
144
154
  # NB: the dnf_package provider manages individual single packages, please do not submit issues or PRs to try to add wildcard
145
155
  # support to lock / unlock. The best solution is to write an execute resource which does a only_if `dnf versionlock | grep '^pattern`` kind of approach
146
156
  def unlock_package(names, versions)
147
- # dnf versionlock delete on rhel6 needs the glob nonsense in the following command
148
- dnf("-d0", "-e0", "-y", options, "versionlock", "delete", resolved_package_lock_names(names).map { |n| "*:#{n}-*" })
157
+ if dnf5?
158
+ dnf("-y", options, "versionlock", "delete", resolved_package_lock_names(names))
159
+ else
160
+ # dnf versionlock delete on rhel6 needs the glob nonsense in the following command
161
+ dnf("-d0", "-e0", "-y", options, "versionlock", "delete", resolved_package_lock_names(names).map { |n| "*:#{n}-*" })
162
+ end
149
163
  end
150
164
 
151
165
  private
@@ -167,8 +181,13 @@ class Chef
167
181
  @locked_packages ||=
168
182
  begin
169
183
  locked = dnf("versionlock", "list")
170
- locked.stdout.each_line.map do |line|
171
- line.sub(/-[^-]*-[^-]*$/, "").split(":").last.strip
184
+ if dnf5?
185
+ locked.stdout.each_line.select { |x| x.start_with?("Package name:") }
186
+ .map { |line| line.split(": ").last.strip }
187
+ else
188
+ locked.stdout.each_line.map do |line|
189
+ line.sub(/-[^-]*-[^-]*$/, "").split(":").last.strip
190
+ end
172
191
  end
173
192
  end
174
193
  end
@@ -38,7 +38,7 @@ class Chef
38
38
  use_package_name_for_source
39
39
  use_magic_version
40
40
 
41
- provides :package, platform_family: "fedora_derived"
41
+ provides :package, platform_family: %w{fedora rhel amazon}
42
42
 
43
43
  provides :yum_package
44
44
 
@@ -174,13 +174,15 @@ action_class do
174
174
  end
175
175
 
176
176
  def rest_url_collection
177
- current_resource.class.rest_api_collection
177
+ endpoint = current_resource.class.rest_api_endpoint || ""
178
+ "#{endpoint}#{current_resource.class.rest_api_collection}"
178
179
  end
179
180
 
180
181
  # Resource document URL after RFC 6570 template evaluation via properties substitution
181
182
  def rest_url_document
183
+ endpoint = current_resource.class.rest_api_endpoint || ""
182
184
  template = ::Addressable::Template.new(current_resource.class.rest_api_document)
183
- template.expand(property_map).to_s
185
+ "#{endpoint}#{template.expand(property_map)}"
184
186
  end
185
187
 
186
188
  # Convenience method for conditional requires
@@ -172,9 +172,16 @@ class Chef
172
172
 
173
173
  if new_resource.owner || new_resource.group
174
174
  converge_by("set owner of files extracted in #{new_resource.destination} to #{new_resource.owner}:#{new_resource.group}") do
175
- archive = Archive::Reader.open_filename(new_resource.path, nil, strip_components: new_resource.strip_components)
176
- archive.each_entry do |e|
177
- FileUtils.chown(new_resource.owner, new_resource.group, "#{new_resource.destination}/#{e.pathname}")
175
+ dest_realpath = ::File.expand_path(new_resource.destination)
176
+ Archive::Reader.open_filename(new_resource.path, nil, strip_components: new_resource.strip_components) do |archive|
177
+ archive.each_entry do |e|
178
+ # Validate path is within destination before chown-ing.
179
+ # Use expand_path for security check, original string for the chown call.
180
+ chown_path_expanded = ::File.expand_path("#{new_resource.destination}/#{e.pathname}")
181
+ next unless chown_path_expanded.start_with?(dest_realpath + ::File::SEPARATOR) || chown_path_expanded == dest_realpath
182
+
183
+ FileUtils.chown(new_resource.owner, new_resource.group, "#{new_resource.destination}/#{e.pathname}")
184
+ end
178
185
  end
179
186
  end
180
187
  end
@@ -211,16 +218,23 @@ class Chef
211
218
  # @return [Boolean]
212
219
  def archive_differs_from_disk?(src, dest)
213
220
  modified = false
214
- archive = Archive::Reader.open_filename(src, nil, strip_components: new_resource.strip_components)
221
+ dest_realpath = ::File.expand_path(dest)
215
222
  Chef::Log.trace("Beginning the comparison of file mtime between contents of #{src} and #{dest}")
216
- archive.each_entry do |e|
217
- pathname = ::File.expand_path(e.pathname, dest)
218
- if ::File.exist?(pathname)
219
- Chef::Log.trace("#{pathname} mtime is #{::File.mtime(pathname)} and archive is #{e.mtime}")
220
- modified = true unless ::File.mtime(pathname) == e.mtime
221
- else
222
- Chef::Log.trace("#{pathname} doesn't exist on disk, but exists in the archive")
223
- modified = true
223
+ Archive::Reader.open_filename(src, nil, strip_components: new_resource.strip_components) do |archive|
224
+ archive.each_entry do |e|
225
+ pathname = ::File.expand_path(e.pathname, dest_realpath)
226
+ # Skip archive entries that resolve outside the destination directory.
227
+ # A traversal entry (e.g. "../escaped") must not influence the mtime
228
+ # comparison and must never trigger extraction.
229
+ next unless pathname.start_with?(dest_realpath + ::File::SEPARATOR) || pathname == dest_realpath
230
+
231
+ if ::File.exist?(pathname)
232
+ Chef::Log.trace("#{pathname} mtime is #{::File.mtime(pathname)} and archive is #{e.mtime}")
233
+ modified = true unless ::File.mtime(pathname) == e.mtime
234
+ else
235
+ Chef::Log.trace("#{pathname} doesn't exist on disk, but exists in the archive")
236
+ modified = true
237
+ end
224
238
  end
225
239
  end
226
240
  modified
@@ -235,15 +249,35 @@ class Chef
235
249
  # @return [void]
236
250
  def extract(src, dest, options = [])
237
251
  converge_by("extract #{src} to #{dest}") do
238
- flags = [options].flatten.map { |option| extract_option_map[option] }.compact.reduce(:|)
252
+ user_flags = [options].flatten.map { |option| extract_option_map[option] }.compact.reduce(:|).to_i
253
+ # Always apply libarchive security flags to prevent path traversal.
254
+ # These are not user-overridable hence directly added here
255
+ # instead of options array. NODOTDOT blocks "../" sequences,
256
+ # NOABSOLUTEPATHS blocks absolute-path entries, SECURE_SYMLINKS prevents
257
+ # symlink attacks. Combined with the check below in archive.each_entry
258
+ # they safeguard against Zip Slip / CWE-22 attacks.
259
+ security_flags = Archive::EXTRACT_SECURE_NODOTDOT |
260
+ Archive::EXTRACT_SECURE_NOABSOLUTEPATHS |
261
+ Archive::EXTRACT_SECURE_SYMLINKS
262
+ all_flags = user_flags | security_flags
263
+ dest_realpath = ::File.expand_path(dest)
239
264
 
240
265
  Dir.chdir(dest) do
241
- archive = Archive::Reader.open_filename(src, nil, strip_components: new_resource.strip_components)
242
-
243
- archive.each_entry do |e|
244
- archive.extract(e, flags.to_i)
266
+ Archive::Reader.open_filename(src, nil, strip_components: new_resource.strip_components) do |archive|
267
+ archive.each_entry do |e|
268
+ # reject any entry whose resolved path
269
+ # falls outside the destination directory before calling into libarchive.
270
+ entry_realpath = ::File.expand_path(e.pathname, dest_realpath)
271
+ unless entry_realpath.start_with?(dest_realpath + ::File::SEPARATOR) || entry_realpath == dest_realpath
272
+ raise Chef::Exceptions::ValidationFailed,
273
+ "Refusing to extract archive entry #{e.pathname.inspect}: resolved path " \
274
+ "#{entry_realpath.inspect} is outside destination #{dest_realpath.inspect}. " \
275
+ "This archive may contain a path traversal attack."
276
+ end
277
+
278
+ archive.extract(e, all_flags)
279
+ end
245
280
  end
246
- archive.close
247
281
  end
248
282
  end
249
283
  end
@@ -23,6 +23,14 @@ class Chef
23
23
 
24
24
  provides :chef_client_config
25
25
 
26
+ DIRECTORY_SPEC_DEFAULTS = {
27
+ config: { mode: "0750", inherits: :auto },
28
+ client_d: { mode: "0750", inherits: :auto },
29
+ logs: { mode: "0755", inherits: :auto },
30
+ cache: { mode: "0750", inherits: :auto },
31
+ backups: { mode: "0750", inherits: :auto },
32
+ }.freeze
33
+
26
34
  description "Use the **chef_client_config** resource to create a client.rb file in the #{ChefUtils::Dist::Infra::PRODUCT} configuration directory. See the [client.rb docs](https://docs.chef.io/config_rb_client/) for more details on options available in the client.rb configuration file."
27
35
  introduced "16.6"
28
36
  examples <<~DOC
@@ -76,12 +84,12 @@ class Chef
76
84
  chef_server_url 'https://chef.example.dmz'
77
85
  report_handlers [
78
86
  {
79
- 'class' => 'ReportHandler1Class',
80
- 'arguments' => ["'FirstArgument'", "'SecondArgument'"],
87
+ 'class' => 'ReportHandler1Class',
88
+ 'arguments' => ["'FirstArgument'", "'SecondArgument'"],
81
89
  },
82
90
  {
83
- 'class' => 'ReportHandler2Class',
84
- 'arguments' => ["'FirstArgument'", "'SecondArgument'"],
91
+ 'class' => 'ReportHandler2Class',
92
+ 'arguments' => ["'FirstArgument'", "'SecondArgument'"],
85
93
  },
86
94
  ]
87
95
  end
@@ -96,6 +104,48 @@ class Chef
96
104
  data_collector_token 'TEST_TOKEN_TEST'
97
105
  end
98
106
  ```
107
+
108
+ **Set Linux directory ownership and modes**:
109
+
110
+ ```ruby
111
+ chef_client_config 'Create client.rb' do
112
+ chef_server_url 'https://chef.example.dmz'
113
+ user 'root'
114
+ group 'root'
115
+ file_cache_path '/var/chef/cache'
116
+ file_backup_path '/var/chef/backups'
117
+ log_location '/var/log/chef/client.log'
118
+ directory_specs(
119
+ config: { owner: 'root', group: 'root', mode: '0700' },
120
+ client_d: { owner: 'root', group: 'root', mode: '0700' },
121
+ logs: { owner: 'root', group: 'root', mode: '0750' },
122
+ cache: { owner: 'root', group: 'root', mode: '0750' },
123
+ backups: { owner: 'root', group: 'root', mode: '0750' }
124
+ )
125
+ end
126
+ ```
127
+
128
+ **Set Windows directory inheritance and rights**:
129
+
130
+ ```ruby
131
+ chef_client_config 'Create client.rb' do
132
+ chef_server_url 'https://chef.example.dmz'
133
+ config_directory 'C:/chef'
134
+ file_cache_path 'C:/chef/cache'
135
+ file_backup_path 'C:/chef/backups'
136
+ log_location 'C:/chef/log/client.log'
137
+ directory_specs(
138
+ config: {
139
+ owner: 'Administrators',
140
+ inherits: false,
141
+ rights: {
142
+ 'Administrators' => :full_control,
143
+ 'SYSTEM' => :full_control,
144
+ },
145
+ }
146
+ )
147
+ end
148
+ ```
99
149
  DOC
100
150
 
101
151
  # @todo policy_file or policy_group being set requires the other to be set so enforce that.
@@ -254,21 +304,83 @@ class Chef
254
304
  description: "The data collector token to interact with the data collector server URL (Automate). Note: If possible, use Chef Infra Server to do all data collection reporting, as this removes the need to distribute tokens to individual nodes.",
255
305
  introduced: "17.8"
256
306
 
307
+ property :directory_specs, Hash,
308
+ description: <<~DESC,
309
+ Permission overrides for Chef-managed directories.
310
+ This must be a hash of hashes: top-level keys are directory names, values are per-directory option hashes.
311
+
312
+ Use this property like:
313
+ directory_specs(
314
+ config: { owner: "root", group: "root", mode: "0700" },
315
+ client_d: { owner: "root", group: "root", mode: "0700" },
316
+ logs: { owner: "root", group: "root", mode: "0700" },
317
+ cache: { owner: "root", group: "root", mode: "0700" },
318
+ backups: { owner: "root", group: "root", mode: "0700" }
319
+ )
320
+
321
+ Directory keys: :config, :client_d, :logs, :cache, :backups
322
+ Override keys: :owner, :group, :mode, :rights, :inherits (:rights and :inherits are Windows-only)
323
+ DESC
324
+ callbacks: {
325
+ "keys must be one of :config, :client_d, :logs, :cache, :backups" => lambda { |v|
326
+ valid_keys = %i{config client_d logs cache backups}
327
+ v.keys.all? { |k| valid_keys.include?(k) }
328
+ },
329
+ "values must be a hash with keys only from :owner, :group, :mode, :rights, :inherits" => lambda { |v|
330
+ valid_inner_keys = %i{owner group mode rights inherits}
331
+ v.values.all? { |value| value.is_a?(Hash) && value.keys.all? { |k| valid_inner_keys.include?(k) } }
332
+ },
333
+ },
334
+ default: DIRECTORY_SPEC_DEFAULTS,
335
+ coerce: proc { |v|
336
+ DIRECTORY_SPEC_DEFAULTS.merge(v) do |_key, base, override|
337
+ override.is_a?(Hash) ? base.merge(override) : override
338
+ end
339
+ }
340
+
341
+ property :client_rb_mode, String,
342
+ description: "The mode to set on the client.rb file that is written out by this resource (e.g., 0600).",
343
+ default: "0640"
344
+
257
345
  action :create, description: "Create a client.rb config file and folders for configuring #{ChefUtils::Dist::Infra::PRODUCT}." do
258
- [
259
- new_resource.config_directory,
260
- (::File.dirname(new_resource.log_location) if new_resource.log_location.is_a?(String) && !%w{STDOUT STDERR}.include?(new_resource.log_location) && !new_resource.log_location.empty?),
261
- new_resource.file_backup_path,
262
- new_resource.file_cache_path,
263
- ::File.join(new_resource.config_directory, "client.d"),
264
- (::File.dirname(new_resource.pid_file) unless new_resource.pid_file.nil?),
265
- ].compact.each do |dir_path|
266
-
267
- directory dir_path do
268
- user new_resource.user unless new_resource.user.nil?
269
- group new_resource.group unless new_resource.group.nil?
270
- mode dir_path == ::File.dirname(new_resource.log_location.to_s) ? "0755" : "0750"
271
- recursive true
346
+ # The logs directory is derived from log_location, which may be a symbol
347
+ # or a stream name (STDOUT/STDERR) rather than a file path. Only raise if
348
+ # the user customized logs specs but log_location is not a usable file path.
349
+ if new_resource.directory_specs[:logs] != DIRECTORY_SPEC_DEFAULTS[:logs] && log_directory.nil?
350
+ raise ArgumentError, "Invalid directory_specs: logs requires :logs (file path)"
351
+ end
352
+
353
+ # Build path map for each managed directory. For cache and backups, fall
354
+ # back to ChefConfig defaults when not explicitly set so that directory_specs
355
+ # overrides are applied even if the user didn't configure a custom path in client.rb.
356
+ spec_paths = {
357
+ config: new_resource.config_directory,
358
+ client_d: ::File.join(new_resource.config_directory, "client.d"),
359
+ logs: log_directory,
360
+ cache: new_resource.file_cache_path || ChefConfig::Config.file_cache_path,
361
+ backups: new_resource.file_backup_path || ChefConfig::Config.file_backup_path,
362
+ }
363
+
364
+ # Build directory specs based on user input and defaults
365
+ specs = {}
366
+ new_resource.directory_specs.each do |key, overrides|
367
+ path = spec_paths[key]
368
+ next if path.nil?
369
+
370
+ specs[key] = DirectorySpec.new(
371
+ path: path,
372
+ owner: overrides[:owner] || new_resource.user,
373
+ group: overrides[:group] || new_resource.group,
374
+ mode: overrides[:mode],
375
+ rights: overrides[:rights],
376
+ inherits: overrides[:inherits]
377
+ )
378
+ end
379
+
380
+ # Converge directories
381
+ specs.each_value do |spec|
382
+ directory spec.path do
383
+ spec.apply(self)
272
384
  end
273
385
  end
274
386
 
@@ -309,7 +421,7 @@ class Chef
309
421
  data_collector_server_url: new_resource.data_collector_server_url,
310
422
  data_collector_token: new_resource.data_collector_token
311
423
  )
312
- mode "0640"
424
+ mode new_resource.client_rb_mode
313
425
  action :create
314
426
  end
315
427
  end
@@ -333,6 +445,60 @@ class Chef
333
445
  "#{handler["class"]}.new(#{handler["arguments"].join(",")})"
334
446
  end
335
447
  end
448
+
449
+ # A helper class to encapsulate directory spec data and application logic for both *nix and Windows.
450
+ # This allows us to keep the logic for how to apply permissions to directories in one place and makes it easier to test.
451
+ class DirectorySpec
452
+ attr_reader :path, :owner, :group, :mode, :rights, :inherits
453
+
454
+ def initialize(path:, owner: nil, group: nil, mode: nil, rights: nil, inherits: :auto)
455
+ @path = path
456
+ @owner = owner
457
+ @group = group
458
+ @mode = mode
459
+ @rights = rights
460
+ @inherits = inherits
461
+ end
462
+
463
+ def windows?
464
+ Chef::Platform.windows?
465
+ end
466
+
467
+ def apply(resource)
468
+ resource.recursive true
469
+
470
+ if windows?
471
+ if rights && !rights.empty?
472
+ resource.inherits(inherits == :auto ? false : inherits)
473
+
474
+ rights.each do |principal, permission|
475
+ resource.rights(permission, principal)
476
+ end
477
+ else
478
+ # Ensure defaults can restore inherited ACL behavior after custom rights were applied.
479
+ resource.inherits(inherits == :auto ? true : inherits)
480
+ resource.mode(mode) if mode
481
+ end
482
+
483
+ resource.owner(owner) if owner
484
+ else
485
+ resource.owner(owner) if owner
486
+ resource.group(group) if group
487
+ resource.mode(mode) if mode
488
+ end
489
+ end
490
+ end
491
+
492
+ # A helper method to determine the directory to apply log permissions to based on the log_location property.
493
+ # If log_location is a file path, this returns the directory containing that file.
494
+ # If log_location is not a file path (e.g., it's a symbol for syslog or win_evt, or it's STDOUT/STDERR),
495
+ # this returns nil since there is no directory to apply permissions to.
496
+ def log_directory
497
+ return unless new_resource.log_location.is_a?(String)
498
+ return if %w{STDOUT STDERR}.include?(new_resource.log_location)
499
+
500
+ ::File.dirname(new_resource.log_location)
501
+ end
336
502
  end
337
503
  end
338
504
  end
@@ -65,7 +65,7 @@ class Chef
65
65
  property :search, String, default: "*:*", desired_state: false,
66
66
  description: "Search query that would match the same used for the clients, gets stored as a field in the item."
67
67
 
68
- property :raw_data, [Hash, Mash], default: {},
68
+ property :raw_data, [Hash, Mash], default: {}, sensitive: true,
69
69
  description: "The raw data, as a Ruby Hash, that will be stored in the item."
70
70
 
71
71
  property :environment, [String, NilClass], desired_state: false,
@@ -1,3 +1,6 @@
1
+ require "cgi" unless defined?(CGI)
2
+ require "uri" unless defined?(URI)
3
+
1
4
  class Chef
2
5
  class Resource
3
6
  class ChocolateyInstaller < Chef::Resource
@@ -70,7 +73,7 @@ class Chef
70
73
  property :proxy_user, String,
71
74
  description: "The username to use to build a proxy credential with. Will be consumed by the proxy_credential property if both this property and proxy_password are set"
72
75
 
73
- property :proxy_password, String,
76
+ property :proxy_password, String, sensitive: true,
74
77
  description: "The password to use to build a proxy credential with. Will be consumed by the proxy_credential property if both this property and proxy_user are set"
75
78
 
76
79
  load_current_value do
@@ -91,6 +94,25 @@ class Chef
91
94
  Gem::Version.new(get_choco_version)
92
95
  end
93
96
 
97
+ def download_url_path
98
+ return download_url if download_url.match?(%r{^[a-zA-Z]:[\\/]}) || download_url.start_with?("\\\\")
99
+
100
+ ::URI.parse(download_url).path
101
+ rescue URI::InvalidURIError
102
+ download_url
103
+ end
104
+
105
+ def download_url_script?
106
+ ::File.extname(download_url_path).casecmp(".ps1") == 0
107
+ end
108
+
109
+ def download_destination
110
+ filename = ::File.basename(::CGI.unescape(download_url_path.to_s))
111
+ filename = "chocolatey.nupkg" if filename.empty? || ["/", "\\", "."].include?(filename)
112
+
113
+ Chef::Util::PathHelper.join(ChefConfig::Config.etc_chef_dir(windows: true), filename)
114
+ end
115
+
94
116
  def define_resource_requirements
95
117
  requirements.assert(:install, :upgrade).each do |a|
96
118
  a.assertion do
@@ -133,16 +155,14 @@ class Chef
133
155
  if new_resource.download_url
134
156
  Chef::Log.info("Using custom download URL for Chocolatey installation: #{new_resource.download_url}")
135
157
  # If it's a PowerShell script, execute it directly (original behavior)
136
- if new_resource.download_url.downcase.end_with?(".ps1")
158
+ if download_url_script?
137
159
  powershell_exec("Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('#{new_resource.download_url}'))").error!
138
160
  else
139
161
  # Assume it's a direct link to a nupkg or other file and handle accordingly
140
- chef_dir = ChefConfig::Config.etc_chef_dir(windows: true)
162
+ destination = download_destination
141
163
 
142
- # Let PowerShell handle the filename - it will use the server's suggested filename
143
- # from Content-Disposition header, or fall back to the URL path
144
- powershell_exec("Invoke-WebRequest '#{new_resource.download_url}' -OutFile '#{chef_dir}'").error!
145
- Chef::Log.info("Downloaded file from #{new_resource.download_url} to #{chef_dir}")
164
+ powershell_exec("Invoke-WebRequest '#{new_resource.download_url}' -OutFile '#{destination}'").error!
165
+ Chef::Log.info("Downloaded file from #{new_resource.download_url} to #{destination}")
146
166
  end
147
167
  else
148
168
  powershell_exec("Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))").error!
@@ -151,11 +171,9 @@ class Chef
151
171
  end
152
172
 
153
173
  action :upgrade, description: "Upgrades the Chocolatey package manager" do
154
- if new_resource.chocolatey_version
155
- proposed_version = Gem::Version.new(new_resource.chocolatey_version)
156
- else
157
- proposed_version = nil
158
- end
174
+ proposed_version = if new_resource.chocolatey_version
175
+ Gem::Version.new(new_resource.chocolatey_version)
176
+ end
159
177
 
160
178
  if new_resource.download_url
161
179
  powershell_exec("Set-Item -path env:ChocolateyDownloadUrl -Value #{new_resource.download_url}")
@@ -193,7 +211,7 @@ class Chef
193
211
  end
194
212
 
195
213
  action :uninstall, description: "Uninstall Chocolatey package manager" do
196
- path = "c:\\programdata\\chocolatey\\bin"
214
+ path = 'c:\\programdata\\chocolatey\\bin'
197
215
  if File.exists?(path)
198
216
  converge_by("Uninstall Choco") do
199
217
  powershell_code = <<~CODE
@@ -59,7 +59,7 @@ class Chef
59
59
  introduced: "15.3",
60
60
  description: "The username to authenticate feeds."
61
61
 
62
- property :password, String,
62
+ property :password, String, sensitive: true,
63
63
  introduced: "15.3",
64
64
  description: "The password to authenticate to the source."
65
65
 
@@ -106,7 +106,7 @@ class Chef
106
106
  property :channel, String, default: "stable",
107
107
  description: "The release channel to install your package from."
108
108
 
109
- property :auth_token, String,
109
+ property :auth_token, String, sensitive: true,
110
110
  description: "Auth token for installing a package from a private organization on Habitat builder."
111
111
 
112
112
  property :binlink, [true, false, :force], default: false,
@@ -129,10 +129,10 @@ class Chef
129
129
  property :auto_update, [true, false], default: false,
130
130
  description: "Passes `--auto-update`. This will set the Habitat supervisor to automatically update itself any time a stable version has been released."
131
131
 
132
- property :auth_token, String,
132
+ property :auth_token, String, sensitive: true,
133
133
  description: "Auth token for accessing a private organization on bldr. This value is templated into the appropriate service file."
134
134
 
135
- property :gateway_auth_token, String,
135
+ property :gateway_auth_token, String, sensitive: true,
136
136
  description: "Auth token for accessing the supervisor's HTTP gateway. This value is templated into the appropriate service file."
137
137
 
138
138
  property :update_condition, String,
@@ -53,7 +53,7 @@ class Chef
53
53
  property :remote_sup_http, String, default: "127.0.0.1:9631", desired_state: false,
54
54
  description: "Address for remote supervisor http port. Used to pull existing."
55
55
 
56
- property :gateway_auth_token, String, desired_state: false,
56
+ property :gateway_auth_token, String, sensitive: true, desired_state: false,
57
57
  description: "Auth token for accessing the remote supervisor's http port."
58
58
 
59
59
  property :user, String, desired_state: false,
@@ -118,7 +118,7 @@ class Chef
118
118
  property :remote_sup_http, String, default: "127.0.0.1:9631", desired_state: false,
119
119
  description: "IP address and port used to communicate with the remote supervisor. If this value is invalid, the resource will update the supervisor configuration each time #{ChefUtils::Dist::Server::PRODUCT} runs."
120
120
 
121
- property :gateway_auth_token, String, desired_state: false,
121
+ property :gateway_auth_token, String, sensitive: true, desired_state: false,
122
122
  description: "Auth token for accessing the remote supervisor's http port."
123
123
 
124
124
  property :update_condition, [Symbol, String], equal_to: [:latest, "latest", :'track-channel', "track-channel"], default: :latest, coerce: proc { |s| s.is_a?(String) ? s.to_sym : s },
@@ -60,7 +60,7 @@ class Chef
60
60
  description: "The desired curve of the generated key (if key_type is equal to 'ec'). Run openssl ecparam -list_curves to see available options.",
61
61
  default: "prime256v1"
62
62
 
63
- property :key_pass, String,
63
+ property :key_pass, String, sensitive: true,
64
64
  description: "The desired passphrase for the key."
65
65
 
66
66
  property :key_cipher, String,
@@ -56,10 +56,10 @@ class Chef
56
56
  property :private_key_path, String,
57
57
  description: "The path to the private key file."
58
58
 
59
- property :private_key_content, String,
59
+ property :private_key_content, String, sensitive: true,
60
60
  description: "The content of the private key including new lines. This property is used in place of private_key_path in instances where you want to avoid having to first write the private key to disk"
61
61
 
62
- property :private_key_pass, String,
62
+ property :private_key_pass, String, sensitive: true,
63
63
  description: "The passphrase of the provided private key."
64
64
 
65
65
  property :owner, [String, Integer],
@@ -59,7 +59,7 @@ class Chef
59
59
  description: "The desired bit length of the generated key.",
60
60
  default: 2048
61
61
 
62
- property :key_pass, String,
62
+ property :key_pass, String, sensitive: true,
63
63
  description: "The desired passphrase for the key."
64
64
 
65
65
  property :key_cipher, String,