chef 17.4.25-universal-mingw32 → 17.6.18-universal-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) 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/client.rb +1 -2
  6. data/lib/chef/compliance/input.rb +115 -0
  7. data/lib/chef/compliance/input_collection.rb +139 -0
  8. data/lib/chef/compliance/profile.rb +122 -0
  9. data/lib/chef/compliance/profile_collection.rb +109 -0
  10. data/lib/chef/compliance/reporter/automate.rb +1 -1
  11. data/lib/chef/compliance/runner.rb +48 -6
  12. data/lib/chef/compliance/waiver.rb +115 -0
  13. data/lib/chef/compliance/waiver_collection.rb +143 -0
  14. data/lib/chef/dsl/compliance.rb +38 -0
  15. data/lib/chef/dsl/reader_helpers.rb +51 -0
  16. data/lib/chef/dsl/recipe.rb +4 -2
  17. data/lib/chef/dsl/secret.rb +2 -4
  18. data/lib/chef/dsl/universal.rb +2 -0
  19. data/lib/chef/event_dispatch/base.rb +44 -2
  20. data/lib/chef/formatters/doc.rb +60 -13
  21. data/lib/chef/formatters/minimal.rb +6 -5
  22. data/lib/chef/http/basic_client.rb +15 -7
  23. data/lib/chef/http.rb +12 -8
  24. data/lib/chef/provider/file.rb +2 -0
  25. data/lib/chef/provider/link.rb +2 -2
  26. data/lib/chef/provider/registry_key.rb +3 -2
  27. data/lib/chef/provider/remote_file/http.rb +1 -1
  28. data/lib/chef/provider/template.rb +1 -1
  29. data/lib/chef/resource/archive_file.rb +17 -14
  30. data/lib/chef/resource/chef_client_scheduled_task.rb +45 -2
  31. data/lib/chef/resource/chocolatey_config.rb +13 -13
  32. data/lib/chef/resource/execute.rb +2 -2
  33. data/lib/chef/resource/file/verification/json.rb +50 -0
  34. data/lib/chef/resource/file/verification/yaml.rb +52 -0
  35. data/lib/chef/resource/inspec_input.rb +127 -0
  36. data/lib/chef/resource/inspec_waiver.rb +184 -0
  37. data/lib/chef/resource/mount.rb +1 -1
  38. data/lib/chef/resource/openssl_x509_certificate.rb +1 -1
  39. data/lib/chef/resource/powershell_package_source.rb +234 -70
  40. data/lib/chef/resource/registry_key.rb +36 -48
  41. data/lib/chef/resource/remote_file.rb +98 -2
  42. data/lib/chef/resource/timezone.rb +2 -2
  43. data/lib/chef/resource/user_ulimit.rb +1 -0
  44. data/lib/chef/resource/windows_printer.rb +1 -1
  45. data/lib/chef/resource/windows_uac.rb +3 -1
  46. data/lib/chef/resource/windows_user_privilege.rb +1 -1
  47. data/lib/chef/resource.rb +1 -1
  48. data/lib/chef/resources.rb +2 -0
  49. data/lib/chef/run_context/cookbook_compiler.rb +112 -28
  50. data/lib/chef/run_context.rb +31 -1
  51. data/lib/chef/secret_fetcher/akeyless_vault.rb +57 -0
  52. data/lib/chef/secret_fetcher/aws_secrets_manager.rb +1 -1
  53. data/lib/chef/secret_fetcher/azure_key_vault.rb +1 -1
  54. data/lib/chef/secret_fetcher/base.rb +1 -1
  55. data/lib/chef/secret_fetcher/hashi_vault.rb +100 -0
  56. data/lib/chef/secret_fetcher.rb +8 -2
  57. data/lib/chef/version.rb +1 -1
  58. data/lib/chef/win32/version.rb +2 -1
  59. data/spec/data/archive_file/test_archive.tar.gz +0 -0
  60. data/spec/functional/resource/archive_file_spec.rb +87 -0
  61. data/spec/functional/resource/group_spec.rb +5 -1
  62. data/spec/functional/resource/link_spec.rb +8 -0
  63. data/spec/functional/resource/powershell_package_source_spec.rb +5 -6
  64. data/spec/integration/compliance/compliance_spec.rb +60 -0
  65. data/spec/spec_helper.rb +3 -0
  66. data/spec/support/platform_helpers.rb +4 -0
  67. data/spec/support/ruby_installer.rb +51 -0
  68. data/spec/unit/compliance/input_spec.rb +104 -0
  69. data/spec/unit/compliance/profile_spec.rb +120 -0
  70. data/spec/unit/compliance/waiver_spec.rb +104 -0
  71. data/spec/unit/formatters/doc_spec.rb +1 -1
  72. data/spec/unit/http/basic_client_spec.rb +30 -0
  73. data/spec/unit/http_spec.rb +8 -2
  74. data/spec/unit/provider/link_spec.rb +13 -7
  75. data/spec/unit/provider/remote_file/http_spec.rb +10 -0
  76. data/spec/unit/provider/template_spec.rb +2 -2
  77. data/spec/unit/resource/archive_file_spec.rb +414 -3
  78. data/spec/unit/resource/chef_client_scheduled_task_spec.rb +69 -0
  79. data/spec/unit/resource/file/verification/json_spec.rb +72 -0
  80. data/spec/unit/resource/file/verification/yaml_spec.rb +67 -0
  81. data/spec/unit/resource/inspec_input_spec.rb +300 -0
  82. data/spec/unit/resource/inspec_waiver_spec.rb +312 -0
  83. data/spec/unit/resource/mount_spec.rb +10 -0
  84. data/spec/unit/resource/powershell_package_source_spec.rb +63 -62
  85. data/spec/unit/resource/user_ulimit_spec.rb +14 -1
  86. data/spec/unit/secret_fetcher/akeyless_vault_spec.rb +37 -0
  87. data/spec/unit/secret_fetcher/hashi_vault_spec.rb +80 -0
  88. data/tasks/rspec.rb +2 -1
  89. metadata +60 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b16e62ba7d28cff9fe62328af38e23c484ff0f7f4f6e8c8ea24c768fa2da78d4
4
- data.tar.gz: 007b4a36cd90ec76ba99b61b1a8dda12687e20787f1e3611d37a81504a831e05
3
+ metadata.gz: 0ff701d08b25d18128b8e0b6180c5fa376fb3016e99f35530cb56de54fa570ce
4
+ data.tar.gz: db08c621aa2c52c80ff0528ff2ab7df3eff56b40ad9f1aa0813652835239b220
5
5
  SHA512:
6
- metadata.gz: 9dd9c1790477e60242859ba4e0e7bed672e04702946aebd965fcc7bb31bf5ecb6e43eb8db6e44d55968760c2f2b8bc4cfc472072eb4f662b2c58eb031d04b651
7
- data.tar.gz: 79249489f9347537dffa27bd7b8b152e2ebea6486d54c64a570a638fdf91c17c5e4079a37198ffea8b762bcf3572ff8d80631865cf5a21dfece9106c01dbbf63
6
+ metadata.gz: 3f570c574fdee1879e1db8e882239d1d09474715ef315769f7769d295b6e74d91f8cd5f207e4eb298f32ae96efdcb51c650450d1dc0d895388ae851d4a0047f5
7
+ data.tar.gz: 1d1ca95e861d92dffba948cbd0fb9fcdeee94b98c851f2fe015bd4d472387a7c276f04716b67f72124b31b2af092755d14d6eb2fc16f214c12726aa233d41679
data/Gemfile CHANGED
@@ -2,7 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  gem "chef", path: "."
4
4
 
5
- gem "ohai", git: "https://github.com/chef/ohai.git", branch: "master"
5
+ gem "ohai", git: "https://github.com/chef/ohai.git", branch: "main"
6
6
 
7
7
  gem "chef-utils", path: File.expand_path("chef-utils", __dir__) if File.exist?(File.expand_path("chef-utils", __dir__))
8
8
  gem "chef-config", path: File.expand_path("chef-config", __dir__) if File.exist?(File.expand_path("chef-config", __dir__))
@@ -48,7 +48,7 @@ end
48
48
 
49
49
  group(:chefstyle) do
50
50
  # for testing new chefstyle rules
51
- gem "chefstyle", git: "https://github.com/chef/chefstyle.git", branch: "master"
51
+ gem "chefstyle", git: "https://github.com/chef/chefstyle.git", branch: "main"
52
52
  end
53
53
 
54
54
  instance_eval(ENV["GEMFILE_MOD"]) if ENV["GEMFILE_MOD"]
data/chef.gemspec CHANGED
@@ -55,7 +55,9 @@ Gem::Specification.new do |s|
55
55
 
56
56
  s.add_dependency "proxifier", "~> 1.0"
57
57
 
58
+ s.add_dependency "aws-sdk-s3", "~> 1.91" # s3 recipe-url support
58
59
  s.add_dependency "aws-sdk-secretsmanager", "~> 1.46"
60
+ s.add_dependency "vault", "~> 0.16" # hashi vault official client gem
59
61
  s.bindir = "bin"
60
62
  s.executables = %w{ }
61
63
 
@@ -378,9 +378,19 @@ class Chef::Application::Base < Chef::Application
378
378
 
379
379
  def fetch_recipe_tarball(url, path)
380
380
  require "open-uri" unless defined?(OpenURI)
381
+ uri = URI.parse(url)
382
+
381
383
  Chef::Log.trace("Download recipes tarball from #{url} to #{path}")
382
384
  if File.exist?(url)
383
385
  FileUtils.cp(url, path)
386
+ elsif uri.scheme == "s3"
387
+ require "aws-sdk-s3" unless defined?(Aws::S3)
388
+
389
+ s3 = Aws::S3::Client.new
390
+ object = s3.get_object(bucket: uri.hostname, key: uri.path[1..-1])
391
+ File.open(path, "wb") do |f|
392
+ f.write(object.body.read)
393
+ end
384
394
  elsif URI::DEFAULT_PARSER.make_regexp.match?(url)
385
395
  File.open(path, "wb") do |f|
386
396
  URI.open(url) do |r|
@@ -388,7 +398,7 @@ class Chef::Application::Base < Chef::Application
388
398
  end
389
399
  end
390
400
  else
391
- Chef::Application.fatal! "You specified --recipe-url but the value is neither a valid URL nor a path to a file that exists on disk." +
401
+ Chef::Application.fatal! "You specified --recipe-url but the value is neither a valid URL, an S3 bucket nor a path to a file that exists on disk." +
392
402
  "Please confirm the location of the tarball and try again."
393
403
  end
394
404
  end
data/lib/chef/client.rb CHANGED
@@ -241,8 +241,7 @@ class Chef
241
241
 
242
242
  run_status.run_id = request_id = Chef::RequestID.instance.request_id
243
243
 
244
- @run_context = Chef::RunContext.new
245
- run_context.events = events
244
+ @run_context = Chef::RunContext.new(nil, nil, events)
246
245
  run_status.run_context = run_context
247
246
 
248
247
  events.run_start(Chef::VERSION, run_status)
@@ -0,0 +1,115 @@
1
+ #
2
+ # Copyright:: Copyright (c) Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require "yaml"
19
+
20
+ class Chef
21
+ module Compliance
22
+ #
23
+ # Chef object that represents a single input file in the compliance segment
24
+ # of a cookbook.
25
+ #
26
+ class Input
27
+ # @return [Boolean] if the input has been enabled
28
+ attr_reader :enabled
29
+
30
+ # @return [String] The name of the cookbook that the input is in
31
+ attr_reader :cookbook_name
32
+
33
+ # @return [String] The full path on the host to the input yml file
34
+ attr_reader :path
35
+
36
+ # @return [String] the pathname in the cookbook
37
+ attr_reader :pathname
38
+
39
+ # Event dispatcher for this run.
40
+ #
41
+ # @return [Chef::EventDispatch::Dispatcher]
42
+ #
43
+ attr_reader :events
44
+
45
+ # @api private
46
+ attr_reader :data
47
+
48
+ def initialize(events, data, path, cookbook_name)
49
+ @events = events
50
+ @data = data
51
+ @cookbook_name = cookbook_name
52
+ @path = path
53
+ @pathname = File.basename(path, File.extname(path)) unless path.nil?
54
+ disable!
55
+ end
56
+
57
+ # @return [Boolean] if the input has been enabled
58
+ #
59
+ def enabled?
60
+ !!@enabled
61
+ end
62
+
63
+ # Set the input to being enabled
64
+ #
65
+ def enable!
66
+ events.compliance_input_enabled(self)
67
+ @enabled = true
68
+ end
69
+
70
+ # Set the input as being disabled
71
+ #
72
+ def disable!
73
+ @enabled = false
74
+ end
75
+
76
+ # Render the input in a way that it can be consumed by inspec
77
+ #
78
+ def inspec_data
79
+ data
80
+ end
81
+
82
+ HIDDEN_IVARS = [ :@events ].freeze
83
+
84
+ # Omit the event object from error output
85
+ #
86
+ def inspect
87
+ ivar_string = (instance_variables.map(&:to_sym) - HIDDEN_IVARS).map do |ivar|
88
+ "#{ivar}=#{instance_variable_get(ivar).inspect}"
89
+ end.join(", ")
90
+ "#<#{self.class}:#{object_id} #{ivar_string}>"
91
+ end
92
+
93
+ # Helper to construct a input object from a hash. Since the path and
94
+ # cookbook_name are required this is probably not externally useful.
95
+ #
96
+ def self.from_hash(events, hash, path = nil, cookbook_name = nil)
97
+ new(events, hash, path, cookbook_name)
98
+ end
99
+
100
+ # Helper to construct a input object from a yaml string. Since the path
101
+ # and cookbook_name are required this is probably not externally useful.
102
+ #
103
+ def self.from_yaml(events, string, path = nil, cookbook_name = nil)
104
+ from_hash(events, YAML.load(string), path, cookbook_name)
105
+ end
106
+
107
+ # @param filename [String] full path to the yml file in the cookbook
108
+ # @param cookbook_name [String] cookbook that the input is in
109
+ #
110
+ def self.from_file(events, filename, cookbook_name = nil)
111
+ from_yaml(events, IO.read(filename), filename, cookbook_name)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,139 @@
1
+ # Copyright:: Copyright (c) Chef Software Inc.
2
+ # License:: Apache License, Version 2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ require_relative "input"
18
+
19
+ class Chef
20
+ module Compliance
21
+ class InputCollection < Array
22
+
23
+ # Event dispatcher for this run.
24
+ #
25
+ # @return [Chef::EventDispatch::Dispatcher]
26
+ #
27
+ attr_reader :events
28
+
29
+ def initialize(events)
30
+ @events = events
31
+ end
32
+
33
+ # Add a input to the input collection. The cookbook_name needs to be determined by the
34
+ # caller and is used in the `include_input` API to match on. The path should be the complete
35
+ # path on the host of the yml file, including the filename.
36
+ #
37
+ # @param path [String]
38
+ # @param cookbook_name [String]
39
+ #
40
+ def from_file(filename, cookbook_name)
41
+ new_input = Input.from_file(events, filename, cookbook_name)
42
+ self << new_input
43
+ events.compliance_input_loaded(new_input)
44
+ end
45
+
46
+ # Add a input from a raw hash. This input will be enabled by default.
47
+ #
48
+ # @param path [String]
49
+ # @param cookbook_name [String]
50
+ #
51
+ def from_hash(hash)
52
+ new_input = Input.from_hash(events, hash)
53
+ new_input.enable!
54
+ self << new_input
55
+ end
56
+
57
+ # @return [Array<Input>] inspec inputs which are enabled in a form suitable to pass to inspec
58
+ #
59
+ def inspec_data
60
+ select(&:enabled?).each_with_object({}) { |input, hash| hash.merge(input.inspec_data) }
61
+ end
62
+
63
+ # DSL method to enable input files. This matches on the filename of the input file.
64
+ # If the specific input is omitted then it uses the default input. The string
65
+ # supports regular expression matching.
66
+ #
67
+ # @example Specific input file in a cookbook
68
+ #
69
+ # include_input "acme_cookbook::ssh-001"
70
+ #
71
+ # @example The compliance/inputs/default.yml input in a cookbook
72
+ #
73
+ # include_input "acme_cookbook"
74
+ #
75
+ # @example Every input file in a cookbook
76
+ #
77
+ # include_input "acme_cookbook::.*"
78
+ #
79
+ # @example Matching inputs by regexp in a cookbook
80
+ #
81
+ # include_input "acme_cookbook::ssh.*"
82
+ #
83
+ # @example Matching inputs by regexp in any cookbook in the cookbook collection
84
+ #
85
+ # include_input ".*::ssh.*"
86
+ #
87
+ # @example Adding an arbitrary hash of data (not from any file in a cookbook)
88
+ #
89
+ # include_input({ "ssh_custom_path": "/usr/local/bin" })
90
+ #
91
+ def include_input(arg)
92
+ raise "include_input was given a nil value" if arg.nil?
93
+
94
+ # if we're given a hash argument just shove it in the raw_hash
95
+ if arg.is_a?(Hash)
96
+ from_hash(arg)
97
+ return
98
+ end
99
+
100
+ matching_inputs(arg).each(&:enable!)
101
+ end
102
+
103
+ def valid?(arg)
104
+ !matching_inputs(arg).empty?
105
+ end
106
+
107
+ HIDDEN_IVARS = [ :@events ].freeze
108
+
109
+ # Omit the event object from error output
110
+ #
111
+ def inspect
112
+ ivar_string = (instance_variables.map(&:to_sym) - HIDDEN_IVARS).map do |ivar|
113
+ "#{ivar}=#{instance_variable_get(ivar).inspect}"
114
+ end.join(", ")
115
+ "#<#{self.class}:#{object_id} #{ivar_string}>"
116
+ end
117
+
118
+ private
119
+
120
+ def matching_inputs(arg, should_raise: false)
121
+ (cookbook_name, input_name) = arg.split("::")
122
+
123
+ input_name = "default" if input_name.nil?
124
+
125
+ inputs = select { |input| /^#{cookbook_name}$/.match?(input.cookbook_name) && /^#{input_name}$/.match?(input.pathname) }
126
+
127
+ if inputs.empty? && should_raise
128
+ raise "No inspec inputs matching '#{input_name}' found in cookbooks matching '#{cookbook_name}'"
129
+ end
130
+
131
+ inputs
132
+ end
133
+
134
+ def matching_inputs!(arg)
135
+ matching_inputs(arg, should_raise: true)
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,122 @@
1
+ #
2
+ # Copyright:: Copyright (c) Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ class Chef
19
+ module Compliance
20
+ class Profile
21
+ # @return [Boolean] if the profile has been enabled
22
+ attr_accessor :enabled
23
+
24
+ # @return [String] The full path on the host to the profile inspec.yml
25
+ attr_reader :path
26
+
27
+ # @return [String] The name of the cookbook that the profile is in
28
+ attr_reader :cookbook_name
29
+
30
+ # @return [String] the pathname in the cookbook
31
+ attr_accessor :pathname
32
+
33
+ # @return [Chef::EventDispatch::Dispatcher] Event dispatcher for this run.
34
+ attr_reader :events
35
+
36
+ # @api private
37
+ attr_reader :data
38
+
39
+ def initialize(events, data, path, cookbook_name)
40
+ @events = events
41
+ @data = data
42
+ @path = path
43
+ @cookbook_name = cookbook_name
44
+ @pathname = File.basename(File.dirname(path))
45
+ disable!
46
+ validate!
47
+ end
48
+
49
+ # @return [String] name of the inspec profile from parsing the inspec.yml
50
+ def name
51
+ @data["name"]
52
+ end
53
+
54
+ # @return [String] version of the inspec profile from parsing the inspec.yml
55
+ def version
56
+ @data["version"]
57
+ end
58
+
59
+ # Raises if the inspec profile is not valid.
60
+ #
61
+ def validate!
62
+ raise "Inspec profile at #{path} has no name" unless name
63
+ end
64
+
65
+ # @return [Boolean] if the profile has been enabled
66
+ def enabled?
67
+ !!@enabled
68
+ end
69
+
70
+ # Set the profile to being enabled
71
+ #
72
+ def enable!
73
+ events.compliance_profile_enabled(self)
74
+ @enabled = true
75
+ end
76
+
77
+ # Set the profile as being disabled
78
+ #
79
+ def disable!
80
+ @enabled = false
81
+ end
82
+
83
+ # Render the profile in a way that it can be consumed by inspec
84
+ #
85
+ def inspec_data
86
+ { name: name, path: File.dirname(path) }
87
+ end
88
+
89
+ HIDDEN_IVARS = [ :@events ].freeze
90
+
91
+ # Omit the event object from error output
92
+ #
93
+ def inspect
94
+ ivar_string = (instance_variables.map(&:to_sym) - HIDDEN_IVARS).map do |ivar|
95
+ "#{ivar}=#{instance_variable_get(ivar).inspect}"
96
+ end.join(", ")
97
+ "#<#{self.class}:#{object_id} #{ivar_string}>"
98
+ end
99
+
100
+ # Helper to construct a profile object from a hash. Since the path and
101
+ # cookbook_name are required this is probably not externally useful.
102
+ #
103
+ def self.from_hash(events, hash, path, cookbook_name)
104
+ new(events, hash, path, cookbook_name)
105
+ end
106
+
107
+ # Helper to construct a profile object from a yaml string. Since the path
108
+ # and cookbook_name are required this is probably not externally useful.
109
+ #
110
+ def self.from_yaml(events, string, path, cookbook_name)
111
+ from_hash(events, YAML.load(string), path, cookbook_name)
112
+ end
113
+
114
+ # @param filename [String] full path to the inspec.yml file in the cookbook
115
+ # @param cookbook_name [String] cookbook that the profile is in
116
+ #
117
+ def self.from_file(events, filename, cookbook_name)
118
+ from_yaml(events, IO.read(filename), filename, cookbook_name)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,109 @@
1
+ #
2
+ # Copyright:: Copyright (c) Chef Software Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require_relative "profile"
19
+
20
+ class Chef
21
+ module Compliance
22
+ class ProfileCollection < Array
23
+
24
+ # Event dispatcher for this run.
25
+ #
26
+ # @return [Chef::EventDispatch::Dispatcher]
27
+ #
28
+ attr_reader :events
29
+
30
+ def initialize(events)
31
+ @events = events
32
+ end
33
+
34
+ # Add a profile to the profile collection. The cookbook_name needs to be determined by the
35
+ # caller and is used in the `include_profile` API to match on. The path should be the complete
36
+ # path on the host of the inspec.yml file, including the filename.
37
+ #
38
+ # @param path [String]
39
+ # @param cookbook_name [String]
40
+ #
41
+ def from_file(path, cookbook_name)
42
+ new_profile = Profile.from_file(events, path, cookbook_name)
43
+ self << new_profile
44
+ events.compliance_profile_loaded(new_profile)
45
+ end
46
+
47
+ # @return [Boolean] if any of the profiles are enabled
48
+ #
49
+ def using_profiles?
50
+ any?(&:enabled?)
51
+ end
52
+
53
+ # @return [Array<Profile>] inspec profiles which are enabled in a form suitable to pass to inspec
54
+ #
55
+ def inspec_data
56
+ select(&:enabled?).each_with_object([]) { |profile, arry| arry << profile.inspec_data }
57
+ end
58
+
59
+ # DSL method to enable profile files. This matches on the name of the profile being included it
60
+ # does not match on the filename of the input file. If the specific profile is omitted then
61
+ # it uses the default profile. The string supports regular expression matching.
62
+ #
63
+ # @example Specific profile in a cookbook
64
+ #
65
+ # include_profile "acme_cookbook::ssh-001"
66
+ #
67
+ # @example The profile named "default" in a cookbook
68
+ #
69
+ # include_profile "acme_cookbook"
70
+ #
71
+ # @example Every profile in a cookbook
72
+ #
73
+ # include_profile "acme_cookbook::.*"
74
+ #
75
+ # @example Matching profiles by regexp in a cookbook
76
+ #
77
+ # include_profile "acme_cookbook::ssh.*"
78
+ #
79
+ # @example Matching profiles by regexp in any cookbook in the cookbook collection
80
+ #
81
+ # include_profile ".*::ssh.*"
82
+ #
83
+ def include_profile(arg)
84
+ (cookbook_name, profile_name) = arg.split("::")
85
+
86
+ profile_name = "default" if profile_name.nil?
87
+
88
+ profiles = select { |profile| /^#{cookbook_name}$/.match?(profile.cookbook_name) && /^#{profile_name}$/.match?(profile.pathname) }
89
+
90
+ if profiles.empty?
91
+ raise "No inspec profiles matching '#{profile_name}' found in cookbooks matching '#{cookbook_name}'"
92
+ end
93
+
94
+ profiles.each(&:enable!)
95
+ end
96
+
97
+ HIDDEN_IVARS = [ :@events ].freeze
98
+
99
+ # Omit the event object from error output
100
+ #
101
+ def inspect
102
+ ivar_string = (instance_variables.map(&:to_sym) - HIDDEN_IVARS).map do |ivar|
103
+ "#{ivar}=#{instance_variable_get(ivar).inspect}"
104
+ end.join(", ")
105
+ "#<#{self.class}:#{object_id} #{ivar_string}>"
106
+ end
107
+ end
108
+ end
109
+ end
@@ -76,7 +76,7 @@ class Chef
76
76
 
77
77
  begin
78
78
  Chef::Log.info "Report to #{ChefUtils::Dist::Automate::PRODUCT}: #{@url}"
79
- Chef::Log.debug "Compliance Report: #{json_report}"
79
+ Chef::Log.debug "Compliance Phase report: #{json_report}"
80
80
  http_client.post(nil, json_report, headers)
81
81
  true
82
82
  rescue => e
@@ -12,6 +12,8 @@ class Chef
12
12
 
13
13
  attr_accessor :run_id
14
14
  attr_reader :node
15
+ attr_reader :run_context
16
+
15
17
  def_delegators :node, :logger
16
18
 
17
19
  def enabled?
@@ -25,7 +27,9 @@ class Chef
25
27
  logger.debug("#{self.class}##{__method__}: audit cookbook? #{audit_cookbook_present}")
26
28
  logger.debug("#{self.class}##{__method__}: compliance phase attr? #{node["audit"]["compliance_phase"]}")
27
29
 
28
- if node["audit"]["compliance_phase"].nil?
30
+ if safe_profile_collection&.using_profiles?
31
+ true
32
+ elsif node["audit"]["compliance_phase"].nil?
29
33
  inspec_profiles.any? && !audit_cookbook_present
30
34
  else
31
35
  node["audit"]["compliance_phase"]
@@ -41,6 +45,14 @@ class Chef
41
45
  self.node = node
42
46
  end
43
47
 
48
+ # This hook gives us the run_context immediately after it is created so that we can wire up this object to it.
49
+ #
50
+ # (see EventDispatch::Base#)
51
+ #
52
+ def cookbook_compilation_start(run_context)
53
+ @run_context = run_context
54
+ end
55
+
44
56
  def run_started(run_status)
45
57
  self.run_id = run_status.run_id
46
58
  end
@@ -121,8 +133,16 @@ class Chef
121
133
  end
122
134
  end
123
135
 
136
+ def inputs_from_collection
137
+ safe_input_collection&.inspec_data || {}
138
+ end
139
+
140
+ def waivers_from_collection
141
+ safe_waiver_collection&.inspec_data || {}
142
+ end
143
+
124
144
  def inspec_opts
125
- inputs = inputs_from_attributes
145
+ inputs = inputs_from_attributes.merge(inputs_from_collection).merge(waivers_from_collection)
126
146
 
127
147
  if node["audit"]["chef_node_attribute_enabled"]
128
148
  inputs["chef_node"] = node.to_h
@@ -133,24 +153,34 @@ class Chef
133
153
  backend_cache: node["audit"]["inspec_backend_cache"],
134
154
  inputs: inputs,
135
155
  logger: logger,
156
+ # output: STDOUT,
136
157
  output: node["audit"]["quiet"] ? ::File::NULL : STDOUT,
137
158
  report: true,
138
159
  reporter: ["json-automate"],
160
+ # reporter: ["cli"],
139
161
  reporter_backtrace_inclusion: node["audit"]["result_include_backtrace"],
140
162
  reporter_message_truncation: node["audit"]["result_message_limit"],
141
- waiver_file: Array(node["audit"]["waiver_file"]),
163
+ waiver_file: waiver_files,
142
164
  }
143
165
  end
144
166
 
167
+ def waiver_files
168
+ Array(node["audit"]["waiver_file"])
169
+ end
170
+
145
171
  def inspec_profiles
146
172
  profiles = node["audit"]["profiles"]
147
173
  unless profiles.respond_to?(:map) && profiles.all? { |_, p| p.respond_to?(:transform_keys) && p.respond_to?(:update) }
148
174
  raise "CMPL010: #{Inspec::Dist::PRODUCT_NAME} profiles specified in an unrecognized format, expected a hash of hashes."
149
175
  end
150
176
 
151
- profiles.map do |name, profile|
177
+ from_attributes = profiles.map do |name, profile|
152
178
  profile.transform_keys(&:to_sym).update(name: name)
153
- end
179
+ end || []
180
+
181
+ from_cookbooks = safe_profile_collection&.inspec_data || []
182
+
183
+ from_attributes + from_cookbooks
154
184
  end
155
185
 
156
186
  def load_fetchers!
@@ -180,7 +210,7 @@ class Chef
180
210
  logger.info "Running profiles from: #{profiles.inspect}"
181
211
  runner.run
182
212
  runner.report.tap do |r|
183
- logger.debug "Compliance Report #{r}"
213
+ logger.debug "Compliance Phase report #{r}"
184
214
  end
185
215
  rescue Inspec::FetcherFailure => e
186
216
  failed_report("Cannot fetch all profiles: #{profiles}. Please make sure you're authenticated and the server is reachable. #{e.message}")
@@ -316,6 +346,18 @@ class Chef
316
346
 
317
347
  @validation_passed = true
318
348
  end
349
+
350
+ def safe_profile_collection
351
+ run_context&.profile_collection
352
+ end
353
+
354
+ def safe_waiver_collection
355
+ run_context&.waiver_collection
356
+ end
357
+
358
+ def safe_input_collection
359
+ run_context&.input_collection
360
+ end
319
361
  end
320
362
  end
321
363
  end