chef 19.3.15 → 19.4.46

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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -2
  3. data/chef.gemspec +5 -4
  4. data/lib/chef/application/base.rb +1 -5
  5. data/lib/chef/chef_fs/file_system/chef_server/organization_invites_entry.rb +1 -1
  6. data/lib/chef/cookbook_manifest.rb +18 -7
  7. data/lib/chef/cookbook_uploader.rb +1 -1
  8. data/lib/chef/cookbook_version.rb +3 -3
  9. data/lib/chef/data_bag.rb +3 -4
  10. data/lib/chef/data_collector.rb +1 -0
  11. data/lib/chef/encrypted_data_bag_item.rb +1 -1
  12. data/lib/chef/formatters/error_description.rb +8 -1
  13. data/lib/chef/http/auth_credentials.rb +1 -1
  14. data/lib/chef/http/authenticator.rb +18 -5
  15. data/lib/chef/http.rb +2 -2
  16. data/lib/chef/mixin/properties.rb +1 -1
  17. data/lib/chef/mixin/template.rb +24 -2
  18. data/lib/chef/property.rb +11 -1
  19. data/lib/chef/provider/group/dscl.rb +9 -5
  20. data/lib/chef/provider/group.rb +5 -0
  21. data/lib/chef/provider/mount/solaris.rb +10 -18
  22. data/lib/chef/provider/package/chocolatey.rb +2 -2
  23. data/lib/chef/provider/package/homebrew.rb +2 -3
  24. data/lib/chef/provider/package/portage.rb +0 -1
  25. data/lib/chef/provider/package/yum/python_helper.rb +0 -1
  26. data/lib/chef/provider/service/debian.rb +9 -9
  27. data/lib/chef/resource/apt_repository.rb +5 -1
  28. data/lib/chef/resource/archive_dll_locator.rb +70 -0
  29. data/lib/chef/resource/archive_file.rb +56 -27
  30. data/lib/chef/resource/chef_client_config.rb +187 -21
  31. data/lib/chef/resource/chef_client_hab_ca_cert.rb +10 -3
  32. data/lib/chef/resource/chef_vault_secret.rb +1 -1
  33. data/lib/chef/resource/chocolatey_installer.rb +41 -7
  34. data/lib/chef/resource/chocolatey_package.rb +1 -1
  35. data/lib/chef/resource/dnf_package.rb +1 -1
  36. data/lib/chef/resource/habitat/habitat_package.rb +1 -1
  37. data/lib/chef/resource/habitat/habitat_sup.rb +2 -2
  38. data/lib/chef/resource/habitat_config.rb +1 -1
  39. data/lib/chef/resource/habitat_service.rb +1 -1
  40. data/lib/chef/resource/helpers/path_helpers.rb +2 -2
  41. data/lib/chef/resource/homebrew_cask.rb +11 -5
  42. data/lib/chef/resource/homebrew_tap.rb +18 -0
  43. data/lib/chef/resource/launchd.rb +1 -1
  44. data/lib/chef/resource/mount.rb +2 -2
  45. data/lib/chef/resource/openssl_ec_private_key.rb +1 -1
  46. data/lib/chef/resource/openssl_ec_public_key.rb +2 -2
  47. data/lib/chef/resource/openssl_rsa_private_key.rb +1 -1
  48. data/lib/chef/resource/openssl_rsa_public_key.rb +2 -2
  49. data/lib/chef/resource/openssl_x509_certificate.rb +1 -1
  50. data/lib/chef/resource/openssl_x509_request.rb +1 -1
  51. data/lib/chef/resource/powershell_package_source.rb +1 -1
  52. data/lib/chef/resource/rhsm_register.rb +1 -1
  53. data/lib/chef/resource/service.rb +1 -1
  54. data/lib/chef/resource/sysctl.rb +22 -5
  55. data/lib/chef/resource/windows_ad_join.rb +1 -1
  56. data/lib/chef/resource/windows_certificate.rb +1 -1
  57. data/lib/chef/resource/windows_security_policy.rb +0 -1
  58. data/lib/chef/resource/windows_service.rb +1 -1
  59. data/lib/chef/resource/windows_task.rb +1 -3
  60. data/lib/chef/resource/windows_update_settings.rb +51 -30
  61. data/lib/chef/resource/yum_package.rb +1 -1
  62. data/lib/chef/resource/yum_repository.rb +2 -2
  63. data/lib/chef/resource.rb +2 -2
  64. data/lib/chef/resource_inspector.rb +1 -1
  65. data/lib/chef/role.rb +1 -1
  66. data/lib/chef/target_io/train/dir.rb +10 -1
  67. data/lib/chef/target_io/train/fileutils.rb +1 -1
  68. data/lib/chef/util/selinux.rb +2 -2
  69. data/lib/chef/version.rb +1 -1
  70. data/lib/chef/win32/api/command_line_helper.rb +2 -2
  71. data/lib/chef/win32/api/error.rb +0 -2
  72. data/lib/chef/win32/api/net.rb +2 -4
  73. data/lib/chef/win32/api.rb +4 -4
  74. data/lib/chef/win32/net.rb +0 -1
  75. data/lib/chef/win32/security/acl.rb +1 -1
  76. data/lib/chef/win32/security.rb +1 -1
  77. metadata +18 -11
@@ -0,0 +1,70 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2009-2026 Progress Software Corporation and/or its subsidiaries or affiliates. All Rights Reserved.
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 "open3" unless defined?(Open3)
19
+
20
+ class Chef
21
+ class Resource
22
+ # Locates archive.dll for a Habitat-packaged Chef on Windows. Sources are
23
+ # tried from most to least authoritative so that a stray or older libarchive
24
+ # package on the machine is only used when nothing more specific exists:
25
+ # 1. RUBY_DLL_PATH (set by the Habitat plan)
26
+ # 2. the hab CLI (`hab pkg path core/libarchive`)
27
+ # 3. a search of the hab root derived from the location of chef itself
28
+ module ArchiveDllLocator
29
+ DLL_NAME = "archive.dll".freeze
30
+
31
+ def self.find(source_file: __FILE__, env: ::ENV)
32
+ from_ruby_dll_path(env) || from_hab_cli || from_hab_root_search(source_file)
33
+ end
34
+
35
+ def self.from_ruby_dll_path(env = ::ENV)
36
+ env["RUBY_DLL_PATH"].to_s.split(::File::PATH_SEPARATOR).each do |dir|
37
+ next if dir.empty?
38
+
39
+ candidate = ::File.join(dir.tr("\\", "/"), DLL_NAME)
40
+ return candidate if ::File.exist?(candidate)
41
+ end
42
+ nil
43
+ end
44
+
45
+ def self.from_hab_cli
46
+ stdout, stderr, status = Open3.capture3("hab pkg path core/libarchive")
47
+ unless status.success?
48
+ Chef::Log.debug("Failed to determine Habitat libarchive path: #{stderr}")
49
+ return nil
50
+ end
51
+
52
+ candidate = ::File.join(stdout.strip.tr("\\", "/"), "bin", DLL_NAME)
53
+ return candidate if ::File.exist?(candidate)
54
+
55
+ Chef::Log.debug("#{DLL_NAME} not found in Habitat path: #{candidate}")
56
+ nil
57
+ rescue SystemCallError => e
58
+ Chef::Log.debug("hab CLI not available to locate libarchive: #{e.message}")
59
+ nil
60
+ end
61
+
62
+ def self.from_hab_root_search(source_file)
63
+ hab_root = source_file.tr("\\", "/")[%r{\A(.*?/hab)/pkgs/}, 1]
64
+ return nil unless hab_root
65
+
66
+ ::Dir.glob("#{hab_root}/pkgs/core/libarchive/*/*/bin/#{DLL_NAME}").max
67
+ end
68
+ end
69
+ end
70
+ end
@@ -20,6 +20,7 @@
20
20
 
21
21
  require_relative "../resource"
22
22
  require "fileutils" unless defined?(FileUtils)
23
+
23
24
  begin
24
25
  # The explicit call to FFI::DynamicLibrary.open was added to address an issue specific to the Habitat packaging of Chef Infra Client on windows.
25
26
  # In the Habitat environment, the libarchive library (archive.dll) is installed in a non-standard location that is not included
@@ -37,34 +38,29 @@ begin
37
38
  # This code dynamically determines the path to archive.dll using the Habitat CLI (`hab pkg path core/libarchive`) and explicitly
38
39
  # loads the library using FFI::DynamicLibrary.open. This ensures that the library is correctly loaded in the Habitat environment.
39
40
  #
40
- # Note: This logic is gated by a check for Habitat-specific environment variables (HAB_CACHE_SRC_PATH or HAB_PKG_PATH) to ensure
41
- # that it is only applied in Habitat runs. For other environments (e.g., Omnibus, plain gem installations, or git checkouts),
42
- # the default behavior of FFI is sufficient, as the libraries are installed in standard locations or embedded paths that are
43
- # included in the default search paths.
44
- if RUBY_PLATFORM.match?(/mswin|mingw|windows/) && (ENV["HAB_CACHE_SRC_PATH"] || ENV["HAB_PKG_PATH"])
41
+ # Note: This logic is gated by a check that we are running from a Habitat package
42
+ # path to ensure it is only applied in Habitat runs. For other environments
43
+ # (e.g., Omnibus, plain gem installations, or git checkouts), the default behavior
44
+ # of FFI is sufficient, as the libraries are installed in standard locations or
45
+ # embedded paths that are included in the default search paths.
46
+ # Detect Habitat runtime environment reliably. HAB_CACHE_SRC_PATH is only set
47
+ # during `hab pkg build` (studio), not at runtime. HAB_PKG_PATH is also not
48
+ # guaranteed at runtime. Checking __FILE__ is reliable: when chef-client runs
49
+ # from a Hab package via `hab pkg exec`, this file's path always contains
50
+ # "hab/pkgs".
51
+ if RUBY_PLATFORM.match?(/mswin|mingw|windows/) && __FILE__.match?(%r{hab[/\\]pkgs})
45
52
  require "ffi" unless defined?(FFI)
46
- require "open3" unless defined?(Open3)
47
- # Dynamically determine the path to the core/libarchive package
48
- stdout, stderr, status = Open3.capture3("hab pkg path core/libarchive")
49
- unless status.success?
50
- Chef::Log.debug("Failed to determine Habitat libarchive path: #{stderr}")
51
- return
52
- end
53
+ require_relative "archive_dll_locator"
53
54
 
54
- habitat_libarchive_path = File.join(stdout.strip.tr("\\", "/"), "bin")
55
- unless Dir.exist?(habitat_libarchive_path)
56
- Chef::Log.debug("Habitat libarchive path not found: #{habitat_libarchive_path}")
57
- return
58
- end
55
+ # Search order: RUBY_DLL_PATH, then the hab CLI, then a search of the hab root.
56
+ archive_dll_path = Chef::Resource::ArchiveDllLocator.find(source_file: __FILE__)
59
57
 
60
- archive_dll_path = File.join(habitat_libarchive_path, "archive.dll")
61
- unless File.exist?(archive_dll_path)
62
- Chef::Log.debug("archive.dll not found in Habitat path: #{habitat_libarchive_path}")
63
- return
58
+ if archive_dll_path
59
+ FFI::DynamicLibrary.open(archive_dll_path, FFI::DynamicLibrary::RTLD_LAZY)
60
+ Chef::Log.debug("Explicitly loaded archive.dll from Habitat path: #{archive_dll_path}")
61
+ else
62
+ Chef::Log.debug("archive.dll not located via Habitat; relying on default library search paths")
64
63
  end
65
-
66
- FFI::DynamicLibrary.open(archive_dll_path, FFI::DynamicLibrary::RTLD_LAZY) # Explicitly load the DLL
67
- Chef::Log.debug("Explicitly loaded archive.dll from Habitat path: #{archive_dll_path}")
68
64
  end
69
65
 
70
66
  # ffi-libarchive must be eager loaded see: https://github.com/chef/chef/issues/12228
@@ -171,8 +167,14 @@ class Chef
171
167
 
172
168
  if new_resource.owner || new_resource.group
173
169
  converge_by("set owner of files extracted in #{new_resource.destination} to #{new_resource.owner}:#{new_resource.group}") do
170
+ dest_realpath = ::File.expand_path(new_resource.destination)
174
171
  Archive::Reader.open_filename(new_resource.path, nil, strip_components: new_resource.strip_components) do |archive|
175
172
  archive.each_entry do |e|
173
+ # Validate path is within destination before chown-ing.
174
+ # Use expand_path for security check, original string for the chown call.
175
+ chown_path_expanded = ::File.expand_path("#{new_resource.destination}/#{e.pathname}")
176
+ next unless chown_path_expanded.start_with?(dest_realpath + ::File::SEPARATOR) || chown_path_expanded == dest_realpath
177
+
176
178
  FileUtils.chown(new_resource.owner, new_resource.group, "#{new_resource.destination}/#{e.pathname}")
177
179
  end
178
180
  end
@@ -211,10 +213,16 @@ class Chef
211
213
  # @return [Boolean]
212
214
  def archive_differs_from_disk?(src, dest)
213
215
  modified = false
216
+ dest_realpath = ::File.expand_path(dest)
214
217
  Archive::Reader.open_filename(src, nil, strip_components: new_resource.strip_components) do |archive|
215
218
  Chef::Log.trace("Beginning the comparison of file mtime between contents of #{src} and #{dest}")
216
219
  archive.each_entry do |e|
217
- pathname = ::File.expand_path(e.pathname, dest)
220
+ pathname = ::File.expand_path(e.pathname, dest_realpath)
221
+ # Skip archive entries that resolve outside the destination directory.
222
+ # A traversal entry (e.g. "../escaped") must not influence the mtime
223
+ # comparison and must never trigger extraction.
224
+ next unless pathname.start_with?(dest_realpath + ::File::SEPARATOR) || pathname == dest_realpath
225
+
218
226
  if ::File.exist?(pathname)
219
227
  Chef::Log.trace("#{pathname} mtime is #{::File.mtime(pathname)} and archive is #{e.mtime}")
220
228
  modified = true unless ::File.mtime(pathname) == e.mtime
@@ -236,12 +244,33 @@ class Chef
236
244
  # @return [void]
237
245
  def extract(src, dest, options = [])
238
246
  converge_by("extract #{src} to #{dest}") do
239
- flags = [options].flatten.map { |option| extract_option_map[option] }.compact.reduce(:|)
247
+ user_flags = [options].flatten.map { |option| extract_option_map[option] }.compact.reduce(:|).to_i
248
+ # Always apply libarchive security flags to prevent path traversal.
249
+ # These are not user-overridable hence directly added here
250
+ # instead of options array. NODOTDOT blocks "../" sequences,
251
+ # NOABSOLUTEPATHS blocks absolute-path entries, SECURE_SYMLINKS prevents
252
+ # symlink attacks. Combined with the check below in archive.each_entry
253
+ # they safeguard against Zip Slip / CWE-22 attacks.
254
+ security_flags = Archive::EXTRACT_SECURE_NODOTDOT |
255
+ Archive::EXTRACT_SECURE_NOABSOLUTEPATHS |
256
+ Archive::EXTRACT_SECURE_SYMLINKS
257
+ all_flags = user_flags | security_flags
258
+ dest_realpath = ::File.expand_path(dest)
240
259
 
241
260
  Dir.chdir(dest) do
242
261
  Archive::Reader.open_filename(src, nil, strip_components: new_resource.strip_components) do |archive|
243
262
  archive.each_entry do |e|
244
- archive.extract(e, flags.to_i)
263
+ # reject any entry whose resolved path
264
+ # falls outside the destination directory before calling into libarchive.
265
+ entry_realpath = ::File.expand_path(e.pathname, dest_realpath)
266
+ unless entry_realpath.start_with?(dest_realpath + ::File::SEPARATOR) || entry_realpath == dest_realpath
267
+ raise Chef::Exceptions::ValidationFailed,
268
+ "Refusing to extract archive entry #{e.pathname.inspect}: resolved path " \
269
+ "#{entry_realpath.inspect} is outside destination #{dest_realpath.inspect}. " \
270
+ "This archive may contain a path traversal attack."
271
+ end
272
+
273
+ archive.extract(e, all_flags)
245
274
  end
246
275
  end
247
276
  end
@@ -21,6 +21,14 @@ class Chef
21
21
  class Resource
22
22
  class ChefClientConfig < Chef::Resource
23
23
 
24
+ DIRECTORY_SPEC_DEFAULTS = {
25
+ config: { mode: "0750", inherits: :auto },
26
+ client_d: { mode: "0750", inherits: :auto },
27
+ logs: { mode: "0755", inherits: :auto },
28
+ cache: { mode: "0750", inherits: :auto },
29
+ backups: { mode: "0750", inherits: :auto },
30
+ }.freeze
31
+
24
32
  provides :chef_client_config, target_mode: true
25
33
  target_mode support: :full
26
34
 
@@ -77,12 +85,12 @@ class Chef
77
85
  chef_server_url 'https://chef.example.dmz'
78
86
  report_handlers [
79
87
  {
80
- 'class' => 'ReportHandler1Class',
81
- 'arguments' => ["'FirstArgument'", "'SecondArgument'"],
88
+ 'class' => 'ReportHandler1Class',
89
+ 'arguments' => ["'FirstArgument'", "'SecondArgument'"],
82
90
  },
83
91
  {
84
- 'class' => 'ReportHandler2Class',
85
- 'arguments' => ["'FirstArgument'", "'SecondArgument'"],
92
+ 'class' => 'ReportHandler2Class',
93
+ 'arguments' => ["'FirstArgument'", "'SecondArgument'"],
86
94
  },
87
95
  ]
88
96
  end
@@ -97,6 +105,48 @@ class Chef
97
105
  data_collector_token 'TEST_TOKEN_TEST'
98
106
  end
99
107
  ```
108
+
109
+ **Set Linux directory ownership and modes**:
110
+
111
+ ```ruby
112
+ chef_client_config 'Create client.rb' do
113
+ chef_server_url 'https://chef.example.dmz'
114
+ user 'root'
115
+ group 'root'
116
+ file_cache_path '/var/chef/cache'
117
+ file_backup_path '/var/chef/backups'
118
+ log_location '/var/log/chef/client.log'
119
+ directory_specs(
120
+ config: { owner: 'root', group: 'root', mode: '0700' },
121
+ client_d: { owner: 'root', group: 'root', mode: '0700' },
122
+ logs: { owner: 'root', group: 'root', mode: '0750' },
123
+ cache: { owner: 'root', group: 'root', mode: '0750' },
124
+ backups: { owner: 'root', group: 'root', mode: '0750' }
125
+ )
126
+ end
127
+ ```
128
+
129
+ **Set Windows directory inheritance and rights**:
130
+
131
+ ```ruby
132
+ chef_client_config 'Create client.rb' do
133
+ chef_server_url 'https://chef.example.dmz'
134
+ config_directory 'C:/chef'
135
+ file_cache_path 'C:/chef/cache'
136
+ file_backup_path 'C:/chef/backups'
137
+ log_location 'C:/chef/log/client.log'
138
+ directory_specs(
139
+ config: {
140
+ owner: 'Administrators',
141
+ inherits: false,
142
+ rights: {
143
+ 'Administrators' => :full_control,
144
+ 'SYSTEM' => :full_control,
145
+ },
146
+ }
147
+ )
148
+ end
149
+ ```
100
150
  DOC
101
151
 
102
152
  # @todo policy_file or policy_group being set requires the other to be set so enforce that.
@@ -120,7 +170,7 @@ class Chef
120
170
  property :config_directory, String,
121
171
  description: "The directory to store the client.rb in.",
122
172
  default: ChefConfig::Config.etc_chef_dir,
123
- default_description: "`/etc/chef/` on *nix-like systems and `C:\\chef\\` on Windows"
173
+ default_description: '`/etc/chef/` on *nix-like systems and `C:\\chef\\` on Windows'
124
174
 
125
175
  property :user, String,
126
176
  description: "The user that should own the client.rb file and the configuration directory if it needs to be created. Note: The configuration directory will not be created if it already exists, which allows you to further control the setup of that directory outside of this resource."
@@ -212,7 +262,7 @@ class Chef
212
262
  default: []
213
263
 
214
264
  property :rubygems_url, [String, Array],
215
- description: "The location to source rubygems. It can be set to a string or array of strings for URIs to set as rubygems sources. This allows individuals to setup an internal mirror of rubygems for “airgapped” environments.",
265
+ description: 'The location to source rubygems. It can be set to a string or array of strings for URIs to set as rubygems sources. This allows individuals to setup an internal mirror of rubygems for "airgapped" environments.',
216
266
  introduced: "17.11"
217
267
 
218
268
  property :exception_handlers, Array,
@@ -255,21 +305,83 @@ class Chef
255
305
  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.",
256
306
  introduced: "17.8"
257
307
 
308
+ property :directory_specs, Hash,
309
+ description: <<~DESC,
310
+ Permission overrides for Chef-managed directories.
311
+ This must be a hash of hashes: top-level keys are directory names, values are per-directory option hashes.
312
+
313
+ Use this property like:
314
+ directory_specs(
315
+ config: { owner: "root", group: "root", mode: "0700" },
316
+ client_d: { owner: "root", group: "root", mode: "0700" },
317
+ logs: { owner: "root", group: "root", mode: "0700" },
318
+ cache: { owner: "root", group: "root", mode: "0700" },
319
+ backups: { owner: "root", group: "root", mode: "0700" }
320
+ )
321
+
322
+ Directory keys: :config, :client_d, :logs, :cache, :backups
323
+ Override keys: :owner, :group, :mode, :rights, :inherits (:rights and :inherits are Windows-only)
324
+ DESC
325
+ callbacks: {
326
+ "keys must be one of :config, :client_d, :logs, :cache, :backups" => lambda { |v|
327
+ valid_keys = %i{config client_d logs cache backups}
328
+ v.keys.all? { |k| valid_keys.include?(k) }
329
+ },
330
+ "values must be a hash with keys only from :owner, :group, :mode, :rights, :inherits" => lambda { |v|
331
+ valid_inner_keys = %i{owner group mode rights inherits}
332
+ v.values.all? { |value| value.is_a?(Hash) && value.keys.all? { |k| valid_inner_keys.include?(k) } }
333
+ },
334
+ },
335
+ default: DIRECTORY_SPEC_DEFAULTS,
336
+ coerce: proc { |v|
337
+ DIRECTORY_SPEC_DEFAULTS.merge(v) do |_key, base, override|
338
+ override.is_a?(Hash) ? base.merge(override) : override
339
+ end
340
+ }
341
+
342
+ property :client_rb_mode, String,
343
+ description: "The mode to set on the client.rb file that is written out by this resource (e.g., 0600).",
344
+ default: "0640"
345
+
258
346
  action :create, description: "Create a client.rb config file and folders for configuring #{ChefUtils::Dist::Infra::PRODUCT}." do
259
- [
260
- new_resource.config_directory,
261
- (::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?),
262
- new_resource.file_backup_path,
263
- new_resource.file_cache_path,
264
- ::File.join(new_resource.config_directory, "client.d"),
265
- (::File.dirname(new_resource.pid_file) unless new_resource.pid_file.nil?),
266
- ].compact.each do |dir_path|
267
-
268
- directory dir_path do
269
- user new_resource.user unless new_resource.user.nil?
270
- group new_resource.group unless new_resource.group.nil?
271
- mode dir_path == ::File.dirname(new_resource.log_location.to_s) ? "0755" : "0750"
272
- recursive true
347
+ # The logs directory is derived from log_location, which may be a symbol
348
+ # or a stream name (STDOUT/STDERR) rather than a file path. Only raise if
349
+ # the user customized logs specs but log_location is not a usable file path.
350
+ if new_resource.directory_specs[:logs] != DIRECTORY_SPEC_DEFAULTS[:logs] && log_directory.nil?
351
+ raise ArgumentError, "Invalid directory_specs: logs requires :logs (file path)"
352
+ end
353
+
354
+ # Build path map for each managed directory. For cache and backups, fall
355
+ # back to ChefConfig defaults when not explicitly set so that directory_specs
356
+ # overrides are applied even if the user didn't configure a custom path in client.rb.
357
+ spec_paths = {
358
+ config: new_resource.config_directory,
359
+ client_d: ::File.join(new_resource.config_directory, "client.d"),
360
+ logs: log_directory,
361
+ cache: new_resource.file_cache_path || ChefConfig::Config.file_cache_path,
362
+ backups: new_resource.file_backup_path || ChefConfig::Config.file_backup_path,
363
+ }
364
+
365
+ # Build directory specs based on user input and defaults
366
+ specs = {}
367
+ new_resource.directory_specs.each do |key, overrides|
368
+ path = spec_paths[key]
369
+ next if path.nil?
370
+
371
+ specs[key] = DirectorySpec.new(
372
+ path: path,
373
+ owner: overrides[:owner] || new_resource.user,
374
+ group: overrides[:group] || new_resource.group,
375
+ mode: overrides[:mode],
376
+ rights: overrides[:rights],
377
+ inherits: overrides[:inherits]
378
+ )
379
+ end
380
+
381
+ # Converge directories
382
+ specs.each_value do |spec|
383
+ directory spec.path do
384
+ spec.apply(self)
273
385
  end
274
386
  end
275
387
 
@@ -310,7 +422,7 @@ class Chef
310
422
  data_collector_server_url: new_resource.data_collector_server_url,
311
423
  data_collector_token: new_resource.data_collector_token
312
424
  )
313
- mode "0640"
425
+ mode new_resource.client_rb_mode
314
426
  action :create
315
427
  end
316
428
  end
@@ -334,6 +446,60 @@ class Chef
334
446
  "#{handler["class"]}.new(#{handler["arguments"].join(",")})"
335
447
  end
336
448
  end
449
+
450
+ # A helper class to encapsulate directory spec data and application logic for both *nix and Windows.
451
+ # This allows us to keep the logic for how to apply permissions to directories in one place and makes it easier to test.
452
+ class DirectorySpec
453
+ attr_reader :path, :owner, :group, :mode, :rights, :inherits
454
+
455
+ def initialize(path:, owner: nil, group: nil, mode: nil, rights: nil, inherits: :auto)
456
+ @path = path
457
+ @owner = owner
458
+ @group = group
459
+ @mode = mode
460
+ @rights = rights
461
+ @inherits = inherits
462
+ end
463
+
464
+ def windows?
465
+ Chef::Platform.windows?
466
+ end
467
+
468
+ def apply(resource)
469
+ resource.recursive true
470
+
471
+ if windows?
472
+ if rights && !rights.empty?
473
+ resource.inherits(inherits == :auto ? false : inherits)
474
+
475
+ rights.each do |principal, permission|
476
+ resource.rights(permission, principal)
477
+ end
478
+ else
479
+ # Ensure defaults can restore inherited ACL behavior after custom rights were applied.
480
+ resource.inherits(inherits == :auto ? true : inherits)
481
+ resource.mode(mode) if mode
482
+ end
483
+
484
+ resource.owner(owner) if owner
485
+ else
486
+ resource.owner(owner) if owner
487
+ resource.group(group) if group
488
+ resource.mode(mode) if mode
489
+ end
490
+ end
491
+ end
492
+
493
+ # A helper method to determine the directory to apply log permissions to based on the log_location property.
494
+ # If log_location is a file path, this returns the directory containing that file.
495
+ # If log_location is not a file path (e.g., it's a symbol for syslog or win_evt, or it's STDOUT/STDERR),
496
+ # this returns nil since there is no directory to apply permissions to.
497
+ def log_directory
498
+ return unless new_resource.log_location.is_a?(String)
499
+ return if %w{STDOUT STDERR}.include?(new_resource.log_location)
500
+
501
+ ::File.dirname(new_resource.log_location)
502
+ end
337
503
  end
338
504
  end
339
505
  end
@@ -104,10 +104,17 @@ class Chef
104
104
 
105
105
  # Extract package ident from path: /hab/pkgs/chef/chef-infra-client/VERSION/RELEASE/bin/chef-client
106
106
  # or: C:\hab\pkgs\chef\chef-infra-client\VERSION\RELEASE\bin\chef-client.exe
107
+ # or: /opt/hab/pkgs/chef/chef-infra-client/VERSION/RELEASE/bin/chef-client (hab root not "/hab")
107
108
  # Result should be: chef/chef-infra-client/VERSION/RELEASE
108
- package_ident = ::File.join(Pathname.new(current_chef_path).each_filename.to_a[2..5])
109
+ filename_parts = Pathname.new(current_chef_path).each_filename.to_a
110
+ pkgs_index = filename_parts.index("pkgs")
111
+ if pkgs_index.nil?
112
+ raise "Unable to determine habitat package ident from #{ChefUtils::Dist::Infra::CLIENT} path '#{current_chef_path}'."
113
+ end
114
+
115
+ package_ident = ::File.join(filename_parts[(pkgs_index + 1)..(pkgs_index + 4)])
109
116
 
110
- ca_pkg = shell_out("#{current_hab_path} pkg dependencies #{package_ident}")
117
+ ca_pkg = shell_out("#{current_hab_path} pkg dependencies #{package_ident}", environment: { "HAB_LICENSE" => "accept-no-persist" })
111
118
  if ca_pkg.error?
112
119
  raise "Failed to determine CA Certs for the #{ChefUtils::Dist::Infra::PRODUCT}'s habitat package"
113
120
  end
@@ -118,7 +125,7 @@ class Chef
118
125
  raise "Unable to find 'core/cacerts' package in dependencies. Failed to determine CA Certs."
119
126
  end
120
127
 
121
- ca_path = shell_out("#{current_hab_path} pkg path #{hab_cacerts_pkg}")
128
+ ca_path = shell_out("#{current_hab_path} pkg path #{hab_cacerts_pkg}", environment: { "HAB_LICENSE" => "accept-no-persist" })
122
129
  if ca_path.error?
123
130
  raise "Unable to find path for the 'core/cacerts' habitat package."
124
131
  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
@@ -131,16 +153,28 @@ class Chef
131
153
  # note that Invoke-Expression is being called on the downloaded script (outer parens),
132
154
  # not triggering the script download (inner parens)
133
155
  converge_if_changed do
134
- powershell_exec("Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))").error!
156
+ if new_resource.download_url
157
+ Chef::Log.info("Using custom download URL for Chocolatey installation: #{new_resource.download_url}")
158
+ # If it's a PowerShell script, execute it directly (original behavior)
159
+ if download_url_script?
160
+ powershell_exec("Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('#{new_resource.download_url}'))").error!
161
+ else
162
+ # Assume it's a direct link to a nupkg or other file and handle accordingly
163
+ destination = download_destination
164
+
165
+ powershell_exec("Invoke-WebRequest '#{new_resource.download_url}' -OutFile '#{destination}'").error!
166
+ Chef::Log.info("Downloaded file from #{new_resource.download_url} to #{destination}")
167
+ end
168
+ else
169
+ powershell_exec("Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))").error!
170
+ end
135
171
  end
136
172
  end
137
173
 
138
174
  action :upgrade, description: "Upgrades the Chocolatey package manager" do
139
- if new_resource.chocolatey_version
140
- proposed_version = Gem::Version.new(new_resource.chocolatey_version)
141
- else
142
- proposed_version = nil
143
- end
175
+ proposed_version = if new_resource.chocolatey_version
176
+ Gem::Version.new(new_resource.chocolatey_version)
177
+ end
144
178
 
145
179
  if new_resource.download_url
146
180
  powershell_exec("Set-Item -path env:ChocolateyDownloadUrl -Value #{new_resource.download_url}")
@@ -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
 
@@ -57,7 +57,7 @@ class Chef
57
57
  if v.is_a?(Hash)
58
58
  v
59
59
  elsif v.is_a?(Array)
60
- v.each_with_object({}) { |arg, obj| obj[arg] = true }
60
+ v.to_h { |arg| [arg, true] }
61
61
  elsif v.is_a?(TrueClass) || v.is_a?(FalseClass)
62
62
  { before: v, after: v }
63
63
  elsif v == :before
@@ -108,7 +108,7 @@ class Chef
108
108
  property :channel, String, default: "stable",
109
109
  description: "The release channel to install your package from."
110
110
 
111
- property :auth_token, String,
111
+ property :auth_token, String, sensitive: true,
112
112
  description: "Auth token for installing a package from a private organization on Habitat builder."
113
113
 
114
114
  property :binlink, [true, false, :force], default: false,
@@ -130,10 +130,10 @@ class Chef
130
130
  property :auto_update, [true, false], default: false,
131
131
  description: "Passes `--auto-update`. This will set the Habitat supervisor to automatically update itself any time a stable version has been released."
132
132
 
133
- property :auth_token, String,
133
+ property :auth_token, String, sensitive: true,
134
134
  description: "Auth token for accessing a private organization on bldr. This value is templated into the appropriate service file."
135
135
 
136
- property :gateway_auth_token, String,
136
+ property :gateway_auth_token, String, sensitive: true,
137
137
  description: "Auth token for accessing the supervisor's HTTP gateway. This value is templated into the appropriate service file."
138
138
 
139
139
  property :update_condition, String,
@@ -56,7 +56,7 @@ class Chef
56
56
  property :remote_sup_http, String, default: "127.0.0.1:9631", desired_state: false,
57
57
  description: "Address for remote supervisor http port. Used to pull existing."
58
58
 
59
- property :gateway_auth_token, String, desired_state: false,
59
+ property :gateway_auth_token, String, sensitive: true, desired_state: false,
60
60
  description: "Auth token for accessing the remote supervisor's http port."
61
61
 
62
62
  property :user, String, desired_state: false,
@@ -119,7 +119,7 @@ class Chef
119
119
  property :remote_sup_http, String, default: "127.0.0.1:9631", desired_state: false,
120
120
  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."
121
121
 
122
- property :gateway_auth_token, String, desired_state: false,
122
+ property :gateway_auth_token, String, sensitive: true, desired_state: false,
123
123
  description: "Auth token for accessing the remote supervisor's http port."
124
124
 
125
125
  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 },