chef 17.3.48-universal-mingw32 → 17.6.15-universal-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/chef.gemspec +2 -0
  4. data/lib/chef/application/base.rb +11 -1
  5. data/lib/chef/application.rb +3 -1
  6. data/lib/chef/client.rb +1 -2
  7. data/lib/chef/compliance/default_attributes.rb +5 -3
  8. data/lib/chef/compliance/input.rb +115 -0
  9. data/lib/chef/compliance/input_collection.rb +139 -0
  10. data/lib/chef/compliance/profile.rb +122 -0
  11. data/lib/chef/compliance/profile_collection.rb +109 -0
  12. data/lib/chef/compliance/reporter/automate.rb +1 -1
  13. data/lib/chef/compliance/runner.rb +62 -6
  14. data/lib/chef/compliance/waiver.rb +115 -0
  15. data/lib/chef/compliance/waiver_collection.rb +143 -0
  16. data/lib/chef/dsl/compliance.rb +38 -0
  17. data/lib/chef/dsl/reader_helpers.rb +51 -0
  18. data/lib/chef/dsl/recipe.rb +4 -2
  19. data/lib/chef/dsl/secret.rb +5 -7
  20. data/lib/chef/dsl/universal.rb +2 -0
  21. data/lib/chef/event_dispatch/base.rb +44 -2
  22. data/lib/chef/exceptions.rb +0 -2
  23. data/lib/chef/formatters/doc.rb +60 -13
  24. data/lib/chef/formatters/error_mapper.rb +2 -2
  25. data/lib/chef/formatters/minimal.rb +6 -5
  26. data/lib/chef/http/basic_client.rb +15 -7
  27. data/lib/chef/http.rb +12 -8
  28. data/lib/chef/provider/execute.rb +1 -1
  29. data/lib/chef/provider/file.rb +2 -0
  30. data/lib/chef/provider/group/dscl.rb +1 -1
  31. data/lib/chef/provider/launchd.rb +6 -6
  32. data/lib/chef/provider/link.rb +2 -2
  33. data/lib/chef/provider/registry_key.rb +3 -2
  34. data/lib/chef/provider/remote_file/http.rb +1 -1
  35. data/lib/chef/provider/subversion.rb +4 -4
  36. data/lib/chef/provider/support/yum_repo.erb +1 -1
  37. data/lib/chef/provider/systemd_unit.rb +17 -16
  38. data/lib/chef/provider/template.rb +1 -1
  39. data/lib/chef/provider/user/mac.rb +3 -3
  40. data/lib/chef/provider/yum_repository.rb +27 -43
  41. data/lib/chef/provider/zypper_repository.rb +3 -3
  42. data/lib/chef/provider.rb +26 -1
  43. data/lib/chef/provider_resolver.rb +8 -2
  44. data/lib/chef/resource/archive_file.rb +17 -14
  45. data/lib/chef/resource/chef_client_scheduled_task.rb +45 -2
  46. data/lib/chef/resource/chocolatey_config.rb +13 -13
  47. data/lib/chef/resource/execute.rb +2 -2
  48. data/lib/chef/resource/file/verification/json.rb +50 -0
  49. data/lib/chef/resource/file/verification/yaml.rb +52 -0
  50. data/lib/chef/resource/homebrew_cask.rb +1 -1
  51. data/lib/chef/resource/inspec_input.rb +127 -0
  52. data/lib/chef/resource/inspec_waiver.rb +184 -0
  53. data/lib/chef/resource/inspec_waiver_file_entry.rb +2 -2
  54. data/lib/chef/resource/launchd.rb +3 -3
  55. data/lib/chef/resource/mount.rb +1 -1
  56. data/lib/chef/resource/openssl_x509_certificate.rb +1 -1
  57. data/lib/chef/resource/powershell_package_source.rb +234 -70
  58. data/lib/chef/resource/registry_key.rb +36 -48
  59. data/lib/chef/resource/remote_file.rb +99 -3
  60. data/lib/chef/resource/rhsm_subscription.rb +5 -5
  61. data/lib/chef/resource/ruby_block.rb +100 -0
  62. data/lib/chef/resource/scm/subversion.rb +1 -1
  63. data/lib/chef/resource/sysctl.rb +2 -2
  64. data/lib/chef/resource/systemd_unit.rb +3 -3
  65. data/lib/chef/resource/timezone.rb +2 -2
  66. data/lib/chef/resource/user_ulimit.rb +1 -0
  67. data/lib/chef/resource/windows_printer.rb +1 -1
  68. data/lib/chef/resource/windows_uac.rb +3 -1
  69. data/lib/chef/resource/windows_user_privilege.rb +1 -1
  70. data/lib/chef/resource/yum_package.rb +1 -5
  71. data/lib/chef/resource.rb +13 -17
  72. data/lib/chef/resource_inspector.rb +6 -2
  73. data/lib/chef/resources.rb +2 -0
  74. data/lib/chef/run_context/cookbook_compiler.rb +112 -28
  75. data/lib/chef/run_context.rb +31 -1
  76. data/lib/chef/secret_fetcher/akeyless_vault.rb +57 -0
  77. data/lib/chef/secret_fetcher/aws_secrets_manager.rb +17 -5
  78. data/lib/chef/secret_fetcher/azure_key_vault.rb +32 -10
  79. data/lib/chef/secret_fetcher/base.rb +6 -2
  80. data/lib/chef/secret_fetcher/hashi_vault.rb +100 -0
  81. data/lib/chef/secret_fetcher.rb +13 -6
  82. data/lib/chef/version.rb +1 -1
  83. data/lib/chef/win32/version.rb +2 -1
  84. data/spec/data/archive_file/test_archive.tar.gz +0 -0
  85. data/spec/functional/resource/archive_file_spec.rb +87 -0
  86. data/spec/functional/resource/group_spec.rb +5 -1
  87. data/spec/functional/resource/link_spec.rb +8 -0
  88. data/spec/functional/resource/powershell_package_source_spec.rb +5 -6
  89. data/spec/integration/compliance/compliance_spec.rb +61 -0
  90. data/spec/integration/recipes/resource_action_spec.rb +2 -2
  91. data/spec/spec_helper.rb +3 -0
  92. data/spec/support/platform_helpers.rb +4 -0
  93. data/spec/support/ruby_installer.rb +51 -0
  94. data/spec/unit/compliance/input_spec.rb +104 -0
  95. data/spec/unit/compliance/profile_spec.rb +120 -0
  96. data/spec/unit/compliance/runner_spec.rb +46 -2
  97. data/spec/unit/compliance/waiver_spec.rb +104 -0
  98. data/spec/unit/dsl/secret_spec.rb +8 -2
  99. data/spec/unit/formatters/doc_spec.rb +1 -1
  100. data/spec/unit/http/basic_client_spec.rb +30 -0
  101. data/spec/unit/http_spec.rb +8 -2
  102. data/spec/unit/provider/link_spec.rb +13 -7
  103. data/spec/unit/provider/remote_file/http_spec.rb +10 -0
  104. data/spec/unit/provider/template_spec.rb +2 -2
  105. data/spec/unit/provider_spec.rb +23 -0
  106. data/spec/unit/resource/archive_file_spec.rb +414 -3
  107. data/spec/unit/resource/chef_client_scheduled_task_spec.rb +69 -0
  108. data/spec/unit/resource/file/verification/json_spec.rb +72 -0
  109. data/spec/unit/resource/file/verification/yaml_spec.rb +67 -0
  110. data/spec/unit/resource/homebrew_cask_spec.rb +29 -11
  111. data/spec/unit/resource/inspec_input_spec.rb +300 -0
  112. data/spec/unit/resource/inspec_waiver_spec.rb +312 -0
  113. data/spec/unit/resource/mount_spec.rb +10 -0
  114. data/spec/unit/resource/powershell_package_source_spec.rb +63 -62
  115. data/spec/unit/resource/rhsm_subscription_spec.rb +50 -3
  116. data/spec/unit/resource/systemd_unit_spec.rb +1 -1
  117. data/spec/unit/resource/user_ulimit_spec.rb +14 -1
  118. data/spec/unit/resource_spec.rb +19 -8
  119. data/spec/unit/secret_fetcher/akeyless_vault_spec.rb +37 -0
  120. data/spec/unit/secret_fetcher/aws_secrets_manager_spec.rb +70 -0
  121. data/spec/unit/secret_fetcher/azure_key_vault_spec.rb +23 -16
  122. data/spec/unit/secret_fetcher/hashi_vault_spec.rb +80 -0
  123. data/spec/unit/secret_fetcher_spec.rb +9 -9
  124. data/tasks/rspec.rb +2 -1
  125. metadata +61 -6
@@ -32,6 +32,7 @@ class Chef
32
32
  attr_reader :events
33
33
  attr_reader :run_list_expansion
34
34
  attr_reader :logger
35
+ attr_reader :run_context
35
36
 
36
37
  def initialize(run_context, run_list_expansion, events)
37
38
  @run_context = run_context
@@ -43,23 +44,51 @@ class Chef
43
44
 
44
45
  # Chef::Node object for the current run.
45
46
  def node
46
- @run_context.node
47
+ run_context.node
47
48
  end
48
49
 
49
50
  # Chef::CookbookCollection object for the current run
50
51
  def cookbook_collection
51
- @run_context.cookbook_collection
52
+ run_context.cookbook_collection
52
53
  end
53
54
 
54
55
  # Resource Definitions from the compiled cookbooks. This is populated by
55
56
  # calling #compile_resource_definitions (which is called by #compile)
56
57
  def definitions
57
- @run_context.definitions
58
+ run_context.definitions
59
+ end
60
+
61
+ # The global waiver_collection hanging off of the run_context, used by
62
+ # compile_compliance and the compliance phase that runs inspec
63
+ #
64
+ # @returns [Chef::Compliance::WaiverCollection]
65
+ #
66
+ def waiver_collection
67
+ run_context.waiver_collection
68
+ end
69
+
70
+ # The global input_collection hanging off of the run_context, used by
71
+ # compile_compliance and the compliance phase that runs inspec
72
+ #
73
+ # @returns [Chef::Compliance::inputCollection]
74
+ #
75
+ def input_collection
76
+ run_context.input_collection
77
+ end
78
+
79
+ # The global profile_collection hanging off of the run_context, used by
80
+ # compile_compliance and the compliance phase that runs inspec
81
+ #
82
+ # @returns [Chef::Compliance::ProfileCollection]
83
+ #
84
+ def profile_collection
85
+ run_context.profile_collection
58
86
  end
59
87
 
60
88
  # Run the compile phase of the chef run. Loads files in the following order:
61
89
  # * Libraries
62
90
  # * Ohai
91
+ # * Compliance Profiles/Waivers
63
92
  # * Attributes
64
93
  # * LWRPs
65
94
  # * Resource Definitions
@@ -73,6 +102,7 @@ class Chef
73
102
  def compile
74
103
  compile_libraries
75
104
  compile_ohai_plugins
105
+ compile_compliance
76
106
  compile_attributes
77
107
  compile_lwrps
78
108
  compile_resource_definitions
@@ -98,7 +128,7 @@ class Chef
98
128
 
99
129
  # Loads library files from cookbooks according to #cookbook_order.
100
130
  def compile_libraries
101
- @events.library_load_start(count_files_by_segment(:libraries))
131
+ events.library_load_start(count_files_by_segment(:libraries))
102
132
  cookbook_order.each do |cookbook|
103
133
  eager_load_libraries = cookbook_collection[cookbook].metadata.eager_load_libraries
104
134
  if eager_load_libraries == true # actually true, not truthy
@@ -110,14 +140,14 @@ class Chef
110
140
  end
111
141
  end
112
142
  end
113
- @events.library_load_complete
143
+ events.library_load_complete
114
144
  end
115
145
 
116
146
  # Loads Ohai Plugins from cookbooks, and ensure any old ones are
117
147
  # properly cleaned out
118
148
  def compile_ohai_plugins
119
149
  ohai_plugin_count = count_files_by_segment(:ohai)
120
- @events.ohai_plugin_load_start(ohai_plugin_count)
150
+ events.ohai_plugin_load_start(ohai_plugin_count)
121
151
  FileUtils.rm_rf(Chef::Config[:ohai_segment_plugin_path])
122
152
 
123
153
  cookbook_order.each do |cookbook|
@@ -131,57 +161,81 @@ class Chef
131
161
  node.consume_ohai_data(ohai)
132
162
  end
133
163
 
134
- @events.ohai_plugin_load_complete
164
+ events.ohai_plugin_load_complete
165
+ end
166
+
167
+ # Loads the compliance segment files from the cookbook into the collections
168
+ # hanging off of the run_context, for later use in the compliance phase
169
+ # inspec run.
170
+ #
171
+ def compile_compliance
172
+ events.compliance_load_start
173
+ events.profiles_load_start
174
+ cookbook_order.each do |cookbook|
175
+ load_profiles_from_cookbook(cookbook)
176
+ end
177
+ events.profiles_load_complete
178
+ events.inputs_load_start
179
+ cookbook_order.each do |cookbook|
180
+ load_inputs_from_cookbook(cookbook)
181
+ end
182
+ events.inputs_load_complete
183
+ events.waivers_load_start
184
+ cookbook_order.each do |cookbook|
185
+ load_waivers_from_cookbook(cookbook)
186
+ end
187
+ events.waivers_load_complete
188
+ events.compliance_load_complete
135
189
  end
136
190
 
137
191
  # Loads attributes files from cookbooks. Attributes files are loaded
138
192
  # according to #cookbook_order; within a cookbook, +default.rb+ is loaded
139
193
  # first, then the remaining attributes files in lexical sort order.
140
194
  def compile_attributes
141
- @events.attribute_load_start(count_files_by_segment(:attributes, "attributes.rb"))
195
+ events.attribute_load_start(count_files_by_segment(:attributes, "attributes.rb"))
142
196
  cookbook_order.each do |cookbook|
143
197
  load_attributes_from_cookbook(cookbook)
144
198
  end
145
- @events.attribute_load_complete
199
+ events.attribute_load_complete
146
200
  end
147
201
 
148
202
  # Loads LWRPs according to #cookbook_order. Providers are loaded before
149
203
  # resources on a cookbook-wise basis.
150
204
  def compile_lwrps
151
205
  lwrp_file_count = count_files_by_segment(:providers) + count_files_by_segment(:resources)
152
- @events.lwrp_load_start(lwrp_file_count)
206
+ events.lwrp_load_start(lwrp_file_count)
153
207
  cookbook_order.each do |cookbook|
154
208
  load_lwrps_from_cookbook(cookbook)
155
209
  end
156
- @events.lwrp_load_complete
210
+ events.lwrp_load_complete
157
211
  end
158
212
 
159
213
  # Loads resource definitions according to #cookbook_order
160
214
  def compile_resource_definitions
161
- @events.definition_load_start(count_files_by_segment(:definitions))
215
+ events.definition_load_start(count_files_by_segment(:definitions))
162
216
  cookbook_order.each do |cookbook|
163
217
  load_resource_definitions_from_cookbook(cookbook)
164
218
  end
165
- @events.definition_load_complete
219
+ events.definition_load_complete
166
220
  end
167
221
 
168
222
  # Iterates over the expanded run_list, loading each recipe in turn.
169
223
  def compile_recipes
170
- @events.recipe_load_start(run_list_expansion.recipes.size)
224
+ events.recipe_load_start(run_list_expansion.recipes.size)
171
225
  run_list_expansion.recipes.each do |recipe|
172
226
 
173
227
  path = resolve_recipe(recipe)
174
- @run_context.load_recipe(recipe)
175
- @events.recipe_file_loaded(path, recipe)
228
+ run_context.load_recipe(recipe)
229
+ events.recipe_file_loaded(path, recipe)
176
230
  rescue Chef::Exceptions::RecipeNotFound => e
177
- @events.recipe_not_found(e)
231
+ events.recipe_not_found(e)
178
232
  raise
179
233
  rescue Exception => e
180
- @events.recipe_file_load_failed(path, e, recipe)
234
+ events.recipe_file_load_failed(path, e, recipe)
181
235
  raise
182
236
 
183
237
  end
184
- @events.recipe_load_complete
238
+ events.recipe_load_complete
185
239
  end
186
240
 
187
241
  # Whether or not a cookbook is reachable from the set of cookbook given
@@ -225,7 +279,7 @@ class Chef
225
279
  attr_file_basename = ::File.basename(filename, ".rb")
226
280
  node.include_attribute("#{cookbook_name}::#{attr_file_basename}")
227
281
  rescue Exception => e
228
- @events.attribute_file_load_failed(filename, e)
282
+ events.attribute_file_load_failed(filename, e)
229
283
  raise
230
284
  end
231
285
 
@@ -234,9 +288,9 @@ class Chef
234
288
 
235
289
  logger.trace("Loading cookbook #{cookbook_name}'s library file: #{filename}")
236
290
  Kernel.require(filename)
237
- @events.library_file_loaded(filename)
291
+ events.library_file_loaded(filename)
238
292
  rescue Exception => e
239
- @events.library_file_load_failed(filename, e)
293
+ events.library_file_load_failed(filename, e)
240
294
  raise
241
295
 
242
296
  end
@@ -260,18 +314,18 @@ class Chef
260
314
  def load_lwrp_provider(cookbook_name, filename)
261
315
  logger.trace("Loading cookbook #{cookbook_name}'s providers from #{filename}")
262
316
  Chef::Provider::LWRPBase.build_from_file(cookbook_name, filename, self)
263
- @events.lwrp_file_loaded(filename)
317
+ events.lwrp_file_loaded(filename)
264
318
  rescue Exception => e
265
- @events.lwrp_file_load_failed(filename, e)
319
+ events.lwrp_file_load_failed(filename, e)
266
320
  raise
267
321
  end
268
322
 
269
323
  def load_lwrp_resource(cookbook_name, filename)
270
324
  logger.trace("Loading cookbook #{cookbook_name}'s resources from #{filename}")
271
325
  Chef::Resource::LWRPBase.build_from_file(cookbook_name, filename, self)
272
- @events.lwrp_file_loaded(filename)
326
+ events.lwrp_file_loaded(filename)
273
327
  rescue Exception => e
274
- @events.lwrp_file_load_failed(filename, e)
328
+ events.lwrp_file_load_failed(filename, e)
275
329
  raise
276
330
  end
277
331
 
@@ -288,6 +342,36 @@ class Chef
288
342
  end
289
343
  end
290
344
 
345
+ # Load the compliance segment files from a single cookbook
346
+ #
347
+ def load_profiles_from_cookbook(cookbook_name)
348
+ # This identifies profiles by their inspec.yml file, we recurse into subdirs so the profiles may be deeply
349
+ # nested in a subdir structure for organization. You could have profiles inside of profiles but
350
+ # since that is not coherently defined, you should not.
351
+ #
352
+ each_file_in_cookbook_by_segment(cookbook_name, :compliance, [ "profiles/**/inspec.{yml,yaml}" ]) do |filename|
353
+ profile_collection.from_file(filename, cookbook_name)
354
+ end
355
+ end
356
+
357
+ def load_waivers_from_cookbook(cookbook_name)
358
+ # This identifies waiver files as any yaml files under the waivers subdir. We recurse into subdirs as well
359
+ # so that waivers may be nested in subdirs for organization. Any other files are ignored.
360
+ #
361
+ each_file_in_cookbook_by_segment(cookbook_name, :compliance, [ "waivers/**/*.{yml,yaml}" ]) do |filename|
362
+ waiver_collection.from_file(filename, cookbook_name)
363
+ end
364
+ end
365
+
366
+ def load_inputs_from_cookbook(cookbook_name)
367
+ # This identifies input files as any yaml files under the inputs subdir. We recurse into subdirs as well
368
+ # so that inputs may be nested in subdirs for organization. Any other files are ignored.
369
+ #
370
+ each_file_in_cookbook_by_segment(cookbook_name, :compliance, [ "inputs/**/*.{yml,yaml}" ]) do |filename|
371
+ input_collection.from_file(filename, cookbook_name)
372
+ end
373
+ end
374
+
291
375
  def load_resource_definitions_from_cookbook(cookbook_name)
292
376
  files_in_cookbook_by_segment(cookbook_name, :definitions).each do |filename|
293
377
  next unless File.extname(filename) == ".rb"
@@ -300,9 +384,9 @@ class Chef
300
384
  logger.info("Overriding duplicate definition #{key}, new definition found in #{filename}")
301
385
  newval
302
386
  end
303
- @events.definition_file_loaded(filename)
387
+ events.definition_file_loaded(filename)
304
388
  rescue Exception => e
305
- @events.definition_file_load_failed(filename, e)
389
+ events.definition_file_load_failed(filename, e)
306
390
  raise
307
391
  end
308
392
  end
@@ -25,6 +25,9 @@ require_relative "log"
25
25
  require_relative "recipe"
26
26
  require_relative "run_context/cookbook_compiler"
27
27
  require_relative "event_dispatch/events_output_stream"
28
+ require_relative "compliance/input_collection"
29
+ require_relative "compliance/waiver_collection"
30
+ require_relative "compliance/profile_collection"
28
31
  require_relative "train_transport"
29
32
  require_relative "exceptions"
30
33
  require "forwardable" unless defined?(Forwardable)
@@ -120,10 +123,28 @@ class Chef
120
123
 
121
124
  # Handle to the global action_collection of executed actions for reporting / data_collector /etc
122
125
  #
123
- # @return [Chef::ActionCollection
126
+ # @return [Chef::ActionCollection]
124
127
  #
125
128
  attr_accessor :action_collection
126
129
 
130
+ # Handle to the global profile_collection of inspec profiles for the compliance phase
131
+ #
132
+ # @return [Chef::Compliance::ProfileCollection]
133
+ #
134
+ attr_accessor :profile_collection
135
+
136
+ # Handle to the global waiver_collection of inspec waiver files for the compliance phase
137
+ #
138
+ # @return [Chef::Compliance::WaiverCollection]
139
+ #
140
+ attr_accessor :waiver_collection
141
+
142
+ # Handle to the global input_collection of inspec input files for the compliance phase
143
+ #
144
+ # @return [Chef::Compliance::inputCollection]
145
+ #
146
+ attr_accessor :input_collection
147
+
127
148
  # Pointer back to the Chef::Runner that created this
128
149
  #
129
150
  attr_accessor :runner
@@ -198,6 +219,9 @@ class Chef
198
219
  @loaded_attributes_hash = {}
199
220
  @reboot_info = {}
200
221
  @cookbook_compiler = nil
222
+ @input_collection = Chef::Compliance::InputCollection.new(events)
223
+ @waiver_collection = Chef::Compliance::WaiverCollection.new(events)
224
+ @profile_collection = Chef::Compliance::ProfileCollection.new(events)
201
225
 
202
226
  initialize_child_state
203
227
  end
@@ -674,6 +698,8 @@ class Chef
674
698
  events=
675
699
  has_cookbook_file_in_cookbook?
676
700
  has_template_in_cookbook?
701
+ input_collection
702
+ input_collection=
677
703
  load
678
704
  loaded_attribute
679
705
  loaded_attributes
@@ -688,6 +714,8 @@ class Chef
688
714
  node
689
715
  node=
690
716
  open_stream
717
+ profile_collection
718
+ profile_collection=
691
719
  reboot_info
692
720
  reboot_info=
693
721
  reboot_requested?
@@ -700,6 +728,8 @@ class Chef
700
728
  transport
701
729
  transport_connection
702
730
  unreachable_cookbook?
731
+ waiver_collection
732
+ waiver_collection=
703
733
  }
704
734
 
705
735
  def initialize(parent_run_context)
@@ -0,0 +1,57 @@
1
+ #
2
+ # Author:: Marc Paradise (<marc@chef.io>)
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
+ require_relative "base"
20
+ require_relative "hashi_vault"
21
+
22
+ class Chef
23
+ class SecretFetcher
24
+ # == Chef::SecretFetcher::AKeylessVault
25
+ # A fetcher that fetches a secret from AKeyless Vault. Initial implementation is
26
+ # based on HashiVault , because AKeyless provides a compatibility layer that makes this possible.
27
+ # Future revisions will use native akeyless authentication.
28
+ #
29
+ # Required config:
30
+ # :access_id - the access id of the API key
31
+ # :access_key - the access key of the API key
32
+ #
33
+ #
34
+ # @example
35
+ #
36
+ # fetcher = SecretFetcher.for_service(:akeyless_vault, { access_id: "my-access-id", access_key: "my-access-key" }, run_context )
37
+ # fetcher.fetch("/secret/data/secretkey1")
38
+ #
39
+ AKEYLESS_VAULT_PROXY_ADDR = "https://hvp.akeyless.io".freeze
40
+ class AKeylessVault < HashiVault
41
+ def validate!
42
+ if config[:access_key].nil?
43
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("You must provide the secret access key in the configuration as :secret_access_key")
44
+ end
45
+ if config[:access_id].nil?
46
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("You must provide the access key id in the configuration as :access_key_id")
47
+ end
48
+
49
+ config[:vault_addr] ||= AKEYLESS_VAULT_PROXY_ADDR
50
+ config[:auth_method] = :token
51
+ config[:token] = "#{config[:access_id]}..#{config[:access_key]}"
52
+ super
53
+ end
54
+ end
55
+ end
56
+ end
57
+
@@ -17,6 +17,7 @@
17
17
  #
18
18
 
19
19
  require_relative "base"
20
+ require "aws-sdk-core"
20
21
  require "aws-sdk-secretsmanager"
21
22
 
22
23
  class Chef
@@ -26,21 +27,32 @@ class Chef
26
27
  # It is possible to pass options that configure it to use alternative credentials.
27
28
  # This implementation supports fetching with version.
28
29
  #
29
- # NOTE: This does not yet support automatic retries, which the AWS client does by default.
30
+ # @note ':region' is required configuration. If it is not explicitly provided,
31
+ # and it is not available via global AWS config, we will pull it from node ohai data by default.
32
+ # If this isn't correct, you will need to explicitly override it.
33
+ # If it is not available via ohai data either (such as if you have the AWS plugin disabled)
34
+ # then the converge will fail with an error.
35
+ #
36
+ # @note: This does not yet support automatic retries, which the AWS client does by default.
30
37
  #
31
38
  # For configuration options see https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/SecretsManager/Client.html#initialize-instance_method
32
39
  #
33
- # Note that ~/.aws default and environment-based configurations are supported by default in the
34
- # ruby SDK.
35
40
  #
36
41
  # Usage Example:
37
42
  #
38
- # fetcher = SecretFetcher.for_service(:aws_secrets_manager, { region: "us-east-1" })
43
+ # fetcher = SecretFetcher.for_service(:aws_secrets_manager)
39
44
  # fetcher.fetch("secretkey1", "v1")
40
45
  class SecretFetcher
41
46
  class AWSSecretsManager < Base
47
+ def validate!
48
+ config[:region] = config[:region] || Aws.config[:region] || run_context.node.dig("ec2", "region")
49
+ if config[:region].nil?
50
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("Missing required config for AWS secret fetcher: :region")
51
+ end
52
+ end
53
+
42
54
  # @param identifier [String] the secret_id
43
- # @param version [String] the secret version. Not usd at this time
55
+ # @param version [String] the secret version.
44
56
  # @return Aws::SecretsManager::Types::GetSecretValueResponse
45
57
  def do_fetch(identifier, version)
46
58
  client = Aws::SecretsManager::Client.new(config)
@@ -2,29 +2,36 @@ require_relative "base"
2
2
 
3
3
  class Chef
4
4
  class SecretFetcher
5
- # == Chef::SecretFetcher::AWSSecretsManager
5
+ # == Chef::SecretFetcher::AzureKeyVault
6
6
  # A fetcher that fetches a secret from Azure Key Vault. Supports fetching with version.
7
7
  #
8
8
  # In this initial iteration this authenticates via token obtained from the OAuth2 /token
9
9
  # endpoint.
10
10
  #
11
- # Usage Example:
11
+ # Validation of required configuration (vault name) is not performed until
12
+ # `fetch` time, to allow for embedding the vault name in with the secret
13
+ # name, such as "my_vault/secretkey1".
12
14
  #
13
- # fetcher = SecretFetcher.for_service(:azure_key_vault)
15
+ # @example
16
+ #
17
+ # fetcher = SecretFetcher.for_service(:azure_key_vault, { vault: "my_vault" }, run_context )
14
18
  # fetcher.fetch("secretkey1", "v1")
19
+ #
20
+ # @example
21
+ #
22
+ # fetcher = SecretFetcher.for_service(:azure_key_vault, {}, run_context )
23
+ # fetcher.fetch("my_vault/secretkey1", "v1")
15
24
  class AzureKeyVault < Base
16
- def validate!
17
- @vault = config[:vault]
18
- if @vault.nil?
19
- raise Chef::Exceptions::Secret::MissingVaultName.new("You must provide a vault name to service options as vault: 'vault_name'")
20
- end
21
- end
22
25
 
23
26
  def do_fetch(name, version)
24
27
  token = fetch_token
28
+ vault, name = resolve_vault_and_secret_name(name)
29
+ if vault.nil?
30
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("You must provide a vault name to fetcher options as vault: 'vault_name' or in the secret name as 'vault_name/secret_name'")
31
+ end
25
32
 
26
33
  # Note that `version` is optional after the final `/`. If nil/"", the latest secret version will be fetched.
27
- secret_uri = URI.parse("https://#{@vault}.vault.azure.net/secrets/#{name}/#{version}?api-version=7.2")
34
+ secret_uri = URI.parse("https://#{vault}.vault.azure.net/secrets/#{name}/#{version}?api-version=7.2")
28
35
  http = Net::HTTP.new(secret_uri.host, secret_uri.port)
29
36
  http.use_ssl = true
30
37
 
@@ -41,6 +48,21 @@ class Chef
41
48
  end
42
49
  end
43
50
 
51
+ # Determine the vault name and secret name from the provided name.
52
+ # If it is not in the provided name in the form "vault_name/secret_name"
53
+ # it will determine the vault name from `config[:vault]`.
54
+ # @param name [String] the secret name or vault and secret name in the form "vault_name/secret_name"
55
+ # @return Array[String, String] vault and secret name respectively
56
+ def resolve_vault_and_secret_name(name)
57
+ # We support a simplified approach where the vault name is not passed i
58
+ # into configuration, but
59
+ if name.include?("/")
60
+ name.split("/", 2)
61
+ else
62
+ [config[:vault], name]
63
+ end
64
+ end
65
+
44
66
  def fetch_token
45
67
  token_uri = URI.parse("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fvault.azure.net")
46
68
  http = Net::HTTP.new(token_uri.host, token_uri.port)
@@ -25,13 +25,17 @@ class Chef
25
25
  class SecretFetcher
26
26
  class Base
27
27
  attr_reader :config
28
+ # Note that this is only available in the context of a recipe.
29
+ # Since that's the only place it's intended to be used, that's probably OK.
30
+ attr_reader :run_context
28
31
 
29
32
  # Initialize a new SecretFetcher::Base
30
33
  #
31
34
  # @param config [Hash] Configuration hash. Expected configuration keys and values
32
35
  # will vary based on implementation, and are validated in `validate!`.
33
- def initialize(config)
36
+ def initialize(config, run_context)
34
37
  @config = config
38
+ @run_context = run_context
35
39
  end
36
40
 
37
41
  # Fetch the named secret by invoking implementation-specific [Chef::SecretFetcher::Base#do_fetch]
@@ -52,7 +56,7 @@ class Chef
52
56
  # @raise [Chef::Exceptions::Secret::ConfigurationInvalid] if it is not.
53
57
  def validate!; end
54
58
 
55
- # Called to fetch the secret identified by 'identifer'. Implementations
59
+ # Called to fetch the secret identified by 'identifier'. Implementations
56
60
  # should expect that `validate!` has been invoked before `do_fetch`.
57
61
  #
58
62
  # @param identifier [Object] Unique identifier of the secret to be retrieved.
@@ -0,0 +1,100 @@
1
+ #
2
+ # Author:: Marc Paradise (<marc@chef.io>)
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
+ require_relative "base"
20
+ require "aws-sdk-core" # Support for aws instance profile auth
21
+ require "vault"
22
+ class Chef
23
+ class SecretFetcher
24
+ # == Chef::SecretFetcher::HashiVault
25
+ # A fetcher that fetches a secret from Hashi Vault.
26
+ #
27
+ # Does not yet support fetching with version when a versioned key store is in use.
28
+ # In this initial iteration the only supported authentication is IAM role-based
29
+ #
30
+ # Required config:
31
+ # :auth_method - one of :iam_role, :token. default: :iam_role
32
+ # :vault_addr - the address of a running Vault instance, eg https://vault.example.com:8200
33
+ #
34
+ # For `:token` auth: `:token` - a Vault token valid for authentication.
35
+ #
36
+ # For `:iam_role`: `:role_name` - the name of the role in Vault that was created
37
+ # to support authentication via IAM. See the Vault documentation for details[1].
38
+ # A Terraform example is also available[2]
39
+ #
40
+ #
41
+ # [1] https://www.vaultproject.io/docs/auth/aws#recommended-vault-iam-policy
42
+ # [2] https://registry.terraform.io/modules/hashicorp/vault/aws/latest/examples/vault-iam-auth
43
+ # an IAM principal ARN bound to it.
44
+ #
45
+ # Optional config
46
+ # :namespace - the namespace under which secrets are kept. Only supported in with Vault Enterprise
47
+ #
48
+ # @example
49
+ #
50
+ # fetcher = SecretFetcher.for_service(:hashi_vault, { role_name: "testing-role", vault_addr: https://localhost:8200}, run_context )
51
+ # fetcher.fetch("secretkey1")
52
+ #
53
+ # @example
54
+ #
55
+ # fetcher = SecretFetcher.for_service(:hashi_vault, { auth_method: :token, token: "s.1234abcdef", vault_addr: https://localhost:8200}, run_context )
56
+ # fetcher.fetch("secretkey1")
57
+ SUPPORTED_AUTH_TYPES = %i{iam_role token}.freeze
58
+ class HashiVault < Base
59
+
60
+ # Validate and authenticate the current session using the configured auth strategy and parameters
61
+ def validate!
62
+ if config[:vault_addr].nil?
63
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("You must provide the Vault address in the configuration as :vault_addr")
64
+ end
65
+
66
+ Vault.address = config[:vault_addr]
67
+ Vault.namespace = config[:namespace] unless config[:namespace].nil?
68
+
69
+ case config[:auth_method]
70
+ when :token
71
+ if config[:token].nil?
72
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("You must provide the token in the configuration as :token")
73
+ end
74
+
75
+ Vault.auth.token(config[:token])
76
+ when :iam_role, nil
77
+ if config[:role_name].nil?
78
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("You must provide the authenticating Vault role name in the configuration as :role_name")
79
+ end
80
+
81
+ Vault.auth.aws_iam(config[:role_name], Aws::InstanceProfileCredentials.new)
82
+ else
83
+ raise Chef::Exceptions::Secret::ConfigurationInvalid.new("Invalid :auth_method provided. You gave #{config[:auth_method]}, expected one of :#{SUPPORTED_AUTH_TYPES.join(", :")} ")
84
+ end
85
+ end
86
+
87
+ # @param identifier [String] Identifier of the secret to be fetched, which should
88
+ # be the full path of that secret, eg 'secret/example'
89
+ # @param _version [String] not used in this implementation
90
+ # @return [Hash] containing key/value pairs stored at the location given in 'identifier'
91
+ def do_fetch(identifier, _version)
92
+ result = Vault.logical.read(identifier)
93
+ raise Chef::Exceptions::Secret::FetchFailed.new("No secret found at #{identifier}. Check to ensure that there is a secrets engine configured for that path") if result.nil?
94
+
95
+ result.data
96
+ end
97
+ end
98
+ end
99
+ end
100
+