inspec-core 5.22.36 → 6.6.0

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/Chef-EULA +9 -0
  3. data/Gemfile +10 -5
  4. data/etc/features.sig +6 -0
  5. data/etc/features.yaml +94 -0
  6. data/inspec-core.gemspec +14 -5
  7. data/lib/inspec/backend.rb +2 -0
  8. data/lib/inspec/base_cli.rb +80 -4
  9. data/lib/inspec/cached_fetcher.rb +24 -3
  10. data/lib/inspec/cli.rb +292 -235
  11. data/lib/inspec/config.rb +24 -11
  12. data/lib/inspec/dependencies/cache.rb +33 -0
  13. data/lib/inspec/enhanced_outcomes.rb +1 -0
  14. data/lib/inspec/errors.rb +5 -0
  15. data/lib/inspec/exceptions.rb +2 -0
  16. data/lib/inspec/feature/config.rb +75 -0
  17. data/lib/inspec/feature/runner.rb +26 -0
  18. data/lib/inspec/feature.rb +34 -0
  19. data/lib/inspec/fetcher/git.rb +5 -0
  20. data/lib/inspec/globals.rb +6 -0
  21. data/lib/inspec/plugin/v1/plugin_types/fetcher.rb +7 -0
  22. data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +30 -2
  23. data/lib/inspec/profile.rb +44 -1
  24. data/lib/inspec/reporters/cli.rb +1 -1
  25. data/lib/inspec/reporters.rb +67 -54
  26. data/lib/inspec/run_data.rb +7 -5
  27. data/lib/inspec/runner.rb +34 -5
  28. data/lib/inspec/runner_rspec.rb +12 -9
  29. data/lib/inspec/secrets/yaml.rb +9 -3
  30. data/lib/inspec/shell.rb +10 -0
  31. data/lib/inspec/ui.rb +4 -0
  32. data/lib/inspec/utils/licensing_config.rb +9 -0
  33. data/lib/inspec/version.rb +1 -1
  34. data/lib/inspec/waiver_file_reader.rb +68 -27
  35. data/lib/inspec.rb +2 -1
  36. data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +189 -168
  37. data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +10 -3
  38. data/lib/plugins/inspec-init/lib/inspec-init/cli.rb +1 -0
  39. data/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb +23 -21
  40. data/lib/plugins/inspec-init/lib/inspec-init/cli_profile.rb +15 -13
  41. data/lib/plugins/inspec-init/lib/inspec-init/cli_resource.rb +15 -13
  42. data/lib/plugins/inspec-license/README.md +16 -0
  43. data/lib/plugins/inspec-license/inspec-license.gemspec +6 -0
  44. data/lib/plugins/inspec-license/lib/inspec-license/cli.rb +26 -0
  45. data/lib/plugins/inspec-license/lib/inspec-license.rb +14 -0
  46. data/lib/plugins/inspec-parallel/README.md +27 -0
  47. data/lib/plugins/inspec-parallel/inspec-parallel.gemspec +6 -0
  48. data/lib/plugins/inspec-parallel/lib/inspec-parallel/child_status_reporter.rb +61 -0
  49. data/lib/plugins/inspec-parallel/lib/inspec-parallel/cli.rb +39 -0
  50. data/lib/plugins/inspec-parallel/lib/inspec-parallel/command.rb +219 -0
  51. data/lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb +265 -0
  52. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/base.rb +24 -0
  53. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/silent.rb +7 -0
  54. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/status.rb +124 -0
  55. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/text.rb +23 -0
  56. data/lib/plugins/inspec-parallel/lib/inspec-parallel/validator.rb +170 -0
  57. data/lib/plugins/inspec-parallel/lib/inspec-parallel.rb +18 -0
  58. data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +6 -2
  59. data/lib/plugins/inspec-sign/lib/inspec-sign/cli.rb +11 -4
  60. data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +6 -13
  61. metadata +53 -13
data/lib/inspec/runner.rb CHANGED
@@ -11,6 +11,7 @@ require "inspec/dependencies/cache"
11
11
  require "inspec/dist"
12
12
  require "inspec/reporters"
13
13
  require "inspec/runner_rspec"
14
+ require "chef-licensing"
14
15
  # spec requirements
15
16
 
16
17
  module Inspec
@@ -60,11 +61,13 @@ module Inspec
60
61
  end
61
62
 
62
63
  if @conf[:waiver_file]
63
- @conf[:waiver_file].each do |file|
64
- unless File.file?(file)
65
- raise Inspec::Exceptions::WaiversFileDoesNotExist, "Waiver file #{file} does not exist."
64
+ Inspec.with_feature("inspec-waivers") {
65
+ @conf[:waiver_file].each do |file|
66
+ unless File.file?(file)
67
+ raise Inspec::Exceptions::WaiversFileDoesNotExist, "Waiver file #{file} does not exist."
68
+ end
66
69
  end
67
- end
70
+ }
68
71
  end
69
72
 
70
73
  # About reading inputs:
@@ -159,16 +162,42 @@ module Inspec
159
162
  end
160
163
 
161
164
  def run(with = nil)
165
+ ChefLicensing.check_software_entitlement! if Inspec::Dist::EXEC_NAME == "inspec"
166
+
167
+ # Validate if profiles are signed and verified
168
+ # Additional check is required to provide error message in case of inspec exec command (exec command can use multiple profiles as well)
169
+ # Only runs this block when preview flag CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING is set
170
+ Inspec.with_feature("inspec-mandatory-profile-signing") {
171
+ unless @conf.allow_unsigned_profiles?
172
+ verify_target_profiles_if_signed(@target_profiles)
173
+ end
174
+ }
175
+
162
176
  Inspec::Log.debug "Starting run with targets: #{@target_profiles.map(&:to_s)}"
163
177
  load
164
178
  run_tests(with)
179
+ rescue ChefLicensing::SoftwareNotEntitled
180
+ Inspec::Log.error "License is not entitled to use InSpec."
181
+ Inspec::UI.new.exit(:license_not_entitled)
182
+ rescue ChefLicensing::Error => e
183
+ Inspec::Log.error e.message
184
+ Inspec::UI.new.exit(:usage_error)
185
+ end
186
+
187
+ def verify_target_profiles_if_signed(target_profiles)
188
+ unsigned_profiles = []
189
+ target_profiles.each do |profile|
190
+ unsigned_profiles << profile.name unless profile.verify_if_signed
191
+ end
192
+ raise Inspec::ProfileSignatureRequired, "Signature required for profile/s: #{unsigned_profiles.join(", ")}. Please provide a signed profile. Or set CHEF_ALLOW_UNSIGNED_PROFILES in the environment. Or use `--allow-unsigned-profiles` flag with InSpec CLI. " unless unsigned_profiles.empty?
165
193
  end
166
194
 
167
195
  def render_output(run_data)
168
196
  return if @conf["reporter"].nil?
169
197
 
170
198
  @conf["reporter"].each do |reporter|
171
- result = Inspec::Reporters.render(reporter, run_data, @conf["enhanced_outcomes"])
199
+ enhanced_outcome_flag = @conf["enhanced_outcomes"]
200
+ result = Inspec::Reporters.render(reporter, run_data, enhanced_outcome_flag)
172
201
  raise Inspec::ReporterError, "Error generating reporter '#{reporter[0]}'" if result == false
173
202
  end
174
203
  end
@@ -177,16 +177,18 @@ module Inspec
177
177
  next unless streaming_reporters.include? streaming_reporter_name
178
178
 
179
179
  # Activate the plugin so the formatter ID gets registered with RSpec, presumably
180
- activator = reg.find_activator(plugin_type: :streaming_reporter, activator_name: streaming_reporter_name.to_sym)
181
- activator.activate!
182
180
 
183
- # We cannot pass in a nil output path. Rspec only accepts a valid string or a IO object.
184
- if file_target&.[]("file").nil?
185
- RSpec.configuration.add_formatter(activator.implementation_class)
186
- else
187
- RSpec.configuration.add_formatter(activator.implementation_class, file_target["file"])
188
- end
189
- @conf["reporter"].delete(streaming_reporter_name)
181
+ Inspec.with_feature("inspec-reporter-#{streaming_reporter_name}") {
182
+ activator = reg.find_activator(plugin_type: :streaming_reporter, activator_name: streaming_reporter_name.to_sym)
183
+ activator.activate!
184
+ # We cannot pass in a nil output path. Rspec only accepts a valid string or a IO object.
185
+ if file_target&.[]("file").nil?
186
+ RSpec.configuration.add_formatter(activator.implementation_class)
187
+ else
188
+ RSpec.configuration.add_formatter(activator.implementation_class, file_target["file"])
189
+ end
190
+ @conf["reporter"].delete(streaming_reporter_name)
191
+ }
190
192
  end
191
193
  end
192
194
 
@@ -196,6 +198,7 @@ module Inspec
196
198
  def configure_output
197
199
  RSpec.configuration.output_stream = $stdout
198
200
  @formatter = RSpec.configuration.add_formatter(Inspec::Formatters::Base)
201
+
199
202
  @formatter.enhanced_outcomes = @conf.final_options["enhanced_outcomes"]
200
203
  RSpec.configuration.add_formatter(Inspec::Formatters::ShowProgress, $stderr) if @conf[:show_progress]
201
204
  set_optional_formatters
@@ -24,12 +24,18 @@ module Secrets
24
24
  @inputs = ::YAML.load_file(target)
25
25
  end
26
26
 
27
- if @inputs == false || !@inputs.is_a?(Hash)
28
- Inspec::Log.warn("#{self.class} unable to parse #{target}: invalid YAML or contents is not a Hash")
27
+ # In case of empty yaml file raise the warning else raise the parsing error.
28
+ if !@inputs || @inputs.empty?
29
+ Inspec::Log.warn("Unable to parse #{target}: YAML file is empty.")
29
30
  @inputs = nil
31
+ elsif !@inputs.is_a?(Hash)
32
+ # Exits with usage error.
33
+ Inspec::Log.error("Unable to parse #{target}: invalid YAML or contents is not a Hash")
34
+ Inspec::UI.new.exit(:usage_error)
30
35
  end
31
36
  rescue => e
32
- raise "Error reading InSpec inputs: #{e}"
37
+ # Any other error related to Yaml parsing will be raised here.
38
+ raise "Error reading YAML file #{target}: #{e}"
33
39
  end
34
40
  end
35
41
  end
data/lib/inspec/shell.rb CHANGED
@@ -1,3 +1,6 @@
1
+ require "chef-licensing"
2
+ require "inspec/dist"
3
+
1
4
  autoload :Pry, "pry"
2
5
 
3
6
  module Inspec
@@ -10,6 +13,7 @@ module Inspec
10
13
  end
11
14
 
12
15
  def start
16
+ ChefLicensing.check_software_entitlement! if Inspec::Dist::EXEC_NAME == "inspec"
13
17
  # This will hold a single evaluation binding context as opened within
14
18
  # the instance_eval context of the anonymous class that the profile
15
19
  # context creates to evaluate each individual test file. We want to
@@ -18,6 +22,12 @@ module Inspec
18
22
  @ctx_binding = @runner.eval_with_virtual_profile("binding")
19
23
  configure_pry
20
24
  @ctx_binding.pry
25
+ rescue ChefLicensing::SoftwareNotEntitled
26
+ Inspec::Log.error "License is not entitled to use InSpec."
27
+ Inspec::UI.new.exit(:license_not_entitled)
28
+ rescue ChefLicensing::Error => e
29
+ Inspec::Log.error e.message
30
+ Inspec::UI.new.exit(:usage_error)
21
31
  end
22
32
 
23
33
  def configure_pry # rubocop:disable Metrics/AbcSize
data/lib/inspec/ui.rb CHANGED
@@ -32,9 +32,13 @@ module Inspec
32
32
  EXIT_FATAL_DEPRECATION = 3
33
33
  EXIT_GEM_DEPENDENCY_LOAD_ERROR = 4
34
34
  EXIT_BAD_SIGNATURE = 5
35
+ EXIT_SIGNATURE_REQUIRED = 6
35
36
  EXIT_LICENSE_NOT_ACCEPTED = 172
37
+ EXIT_LICENSE_NOT_ENTITLED = 173
38
+ EXIT_LICENSE_NOT_SET = 174
36
39
  EXIT_FAILED_TESTS = 100
37
40
  EXIT_SKIPPED_TESTS = 101
41
+ EXIT_TERMINATED_BY_CTL_C = 130
38
42
 
39
43
  attr_reader :io
40
44
 
@@ -0,0 +1,9 @@
1
+ require_relative "../log"
2
+ require "chef-licensing"
3
+ ChefLicensing.configure do |config|
4
+ config.chef_product_name = "InSpec"
5
+ config.chef_entitlement_id = "3ff52c37-e41f-4f6c-ad4d-365192205968"
6
+ config.chef_executable_name = "inspec"
7
+ config.license_server_url = "https://services.chef.io/licensing"
8
+ config.logger = Inspec::Log
9
+ end
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "5.22.36".freeze
2
+ VERSION = "6.6.0".freeze
3
3
  end
@@ -15,49 +15,90 @@ module Inspec
15
15
  output = {}
16
16
 
17
17
  files.each do |file_path|
18
- file_extension = File.extname(file_path)
19
- data = nil
20
- if [".yaml", ".yml"].include? file_extension
21
- data = Secrets::YAML.resolve(file_path)
22
- unless data.nil?
23
- data = data.inputs
24
- validate_json_yaml(data)
25
- end
26
- elsif file_extension == ".csv"
27
- data = Waivers::CSVFileReader.resolve(file_path)
28
- headers = Waivers::CSVFileReader.headers
29
- validate_headers(headers)
30
- elsif file_extension == ".json"
31
- data = Waivers::JSONFileReader.resolve(file_path)
32
- validate_json_yaml(data) unless data.nil?
33
- end
18
+ data = read_from_file(file_path)
34
19
  output.merge!(data) if !data.nil? && data.is_a?(Hash)
35
20
 
36
21
  if data.nil?
37
22
  raise Inspec::Exceptions::WaiversFileNotReadable,
38
- "Cannot find parser for waivers file '#{file_path}'. " \
23
+ "Cannot find parser for waivers file." \
39
24
  "Check to make sure file has the appropriate extension."
40
25
  end
26
+ rescue Inspec::Exceptions::WaiversFileNotReadable, Inspec::Exceptions::WaiversFileInvalidFormatting => e
27
+ Inspec::Log.error "Error reading waivers file #{file_path}. #{e.message}"
28
+ Inspec::UI.new.exit(:usage_error)
41
29
  end
42
30
 
43
31
  @waivers_data[profile_id] = output
44
32
  end
45
33
 
46
- def self.validate_headers(headers, json_yaml = false)
47
- required_fields = json_yaml ? %w{justification} : %w{control_id justification}
48
- all_fields = %w{control_id justification expiration_date run}
34
+ def self.read_from_file(file_path)
35
+ data = nil
36
+ file_extension = File.extname(file_path)
37
+ if [".yaml", ".yml"].include? file_extension
38
+ data = Secrets::YAML.resolve(file_path)
39
+ data = data.inputs unless data.nil?
40
+ validate_json_yaml(data)
41
+ elsif file_extension == ".csv"
42
+ data = Waivers::CSVFileReader.resolve(file_path)
43
+ headers = Waivers::CSVFileReader.headers
44
+ validate_csv_headers(headers)
45
+ elsif file_extension == ".json"
46
+ data = Waivers::JSONFileReader.resolve(file_path)
47
+ validate_json_yaml(data) unless data.nil?
48
+ end
49
+ data
50
+ end
51
+
52
+ def self.all_fields
53
+ %w{control_id justification expiration_date run}
54
+ end
55
+
56
+ def self.validate_csv_headers(headers)
57
+ invalid_headers_info = fetch_invalid_headers_info(headers)
58
+ # Warn if blank column found in csv file
59
+ Inspec::Log.warn "Invalid column headers: Column can't be nil" if invalid_headers_info[:blank_column]
60
+ # Warn if extra header found in csv file
61
+ Inspec::Log.warn "Extra header/s #{invalid_headers_info[:extra_headers]}" unless invalid_headers_info[:extra_headers].empty?
62
+ unless invalid_headers_info[:missing_required_fields].empty?
63
+ raise Inspec::Exceptions::WaiversFileInvalidFormatting,
64
+ "Missing required header/s #{invalid_headers_info[:missing_required_fields]}. Fix headers in file to proceed."
65
+ end
66
+ end
49
67
 
50
- Inspec::Log.warn "Missing column headers: #{(required_fields - headers)}" unless (required_fields - headers).empty?
51
- Inspec::Log.warn "Invalid column header: Column can't be nil" if headers.include? nil
52
- Inspec::Log.warn "Extra column headers: #{(headers - all_fields)}" unless (headers - all_fields).empty?
68
+ def self.fetch_invalid_headers_info(headers, json_yaml = false)
69
+ required_fields = json_yaml ? %w{justification} : %w{control_id justification}
70
+ data = {}
71
+ data[:missing_required_fields] = []
72
+ # Finds missing required fields
73
+ unless (required_fields - headers).empty?
74
+ data[:missing_required_fields] = required_fields - headers
75
+ end
76
+ # If column with no header found set the blank_column flag. Only applicable for csv
77
+ data[:blank_column] = headers.include?(nil) ? true : false
78
+ # Find extra headers/parameters
79
+ data[:extra_headers] = (headers - all_fields)
80
+ data
53
81
  end
54
82
 
55
83
  def self.validate_json_yaml(data)
56
- headers = []
57
- data.each_value do |value|
58
- headers.push value.keys
84
+ missing_required_field = false
85
+ data.each do |key, value|
86
+ # In case of yaml or json we need to validate headers/parametes for each value
87
+ invalid_headers_info = fetch_invalid_headers_info(value.keys, true)
88
+ # WARN in case of extra parameters found in each waived control
89
+ Inspec::Log.warn "Control ID #{key}: extra parameter/s #{invalid_headers_info[:extra_headers]}" unless invalid_headers_info[:extra_headers].empty?
90
+ unless invalid_headers_info[:missing_required_fields].empty?
91
+ missing_required_field = true
92
+ # Log error for each waived control
93
+ Inspec::Log.error "Control ID #{key}: missing required parameter/s #{invalid_headers_info[:missing_required_fields]}"
94
+ end
95
+ end
96
+
97
+ # Raise error if any of the waived control has missing required filed
98
+ if missing_required_field
99
+ raise Inspec::Exceptions::WaiversFileInvalidFormatting,
100
+ "Missing required parameter [justification]. Fix parameters in file to proceed."
59
101
  end
60
- validate_headers(headers.flatten.uniq, true)
61
102
  end
62
103
  end
63
104
  end
data/lib/inspec.rb CHANGED
@@ -4,6 +4,7 @@ libdir = File.dirname(__FILE__)
4
4
  $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
5
5
 
6
6
  require "inspec/version"
7
+ require "inspec/utils/licensing_config"
7
8
  require "inspec/exceptions"
8
9
  require "inspec/utils/deprecation"
9
10
  require "inspec/profile"
@@ -30,4 +31,4 @@ require "inspec/source_reader"
30
31
  require "inspec/resource"
31
32
 
32
33
  require "inspec/dependency_loader"
33
- require "inspec/dependency_installer"
34
+ require "inspec/dependency_installer"