inspec-core 5.24.7 → 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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/Chef-EULA +9 -0
  3. data/Gemfile +25 -36
  4. data/etc/features.sig +6 -0
  5. data/etc/features.yaml +94 -0
  6. data/inspec-core.gemspec +21 -19
  7. data/lib/inspec/archive/tar.rb +0 -1
  8. data/lib/inspec/backend.rb +2 -0
  9. data/lib/inspec/base_cli.rb +80 -14
  10. data/lib/inspec/cached_fetcher.rb +24 -3
  11. data/lib/inspec/cli.rb +292 -235
  12. data/lib/inspec/config.rb +24 -11
  13. data/lib/inspec/dependencies/cache.rb +33 -0
  14. data/lib/inspec/dependencies/dependency_set.rb +2 -2
  15. data/lib/inspec/dsl.rb +1 -1
  16. data/lib/inspec/enhanced_outcomes.rb +1 -0
  17. data/lib/inspec/errors.rb +5 -0
  18. data/lib/inspec/exceptions.rb +1 -0
  19. data/lib/inspec/feature/config.rb +75 -0
  20. data/lib/inspec/feature/runner.rb +26 -0
  21. data/lib/inspec/feature.rb +34 -0
  22. data/lib/inspec/fetcher/git.rb +6 -21
  23. data/lib/inspec/fetcher/url.rb +7 -29
  24. data/lib/inspec/file_provider.rb +0 -1
  25. data/lib/inspec/globals.rb +6 -0
  26. data/lib/inspec/input_registry.rb +1 -5
  27. data/lib/inspec/plugin/v1/plugin_types/fetcher.rb +7 -0
  28. data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +30 -2
  29. data/lib/inspec/profile.rb +49 -13
  30. data/lib/inspec/reporters/cli.rb +1 -1
  31. data/lib/inspec/reporters.rb +67 -54
  32. data/lib/inspec/resources/audit_policy.rb +2 -8
  33. data/lib/inspec/resources/groups.rb +0 -52
  34. data/lib/inspec/resources/mssql_session.rb +1 -13
  35. data/lib/inspec/resources/nftables.rb +1 -14
  36. data/lib/inspec/resources/oracledb_session.rb +11 -72
  37. data/lib/inspec/resources/postgres_session.rb +5 -9
  38. data/lib/inspec/resources/sybase_session.rb +2 -11
  39. data/lib/inspec/resources/virtualization.rb +1 -1
  40. data/lib/inspec/rule.rb +9 -14
  41. data/lib/inspec/run_data.rb +7 -5
  42. data/lib/inspec/runner.rb +35 -6
  43. data/lib/inspec/runner_rspec.rb +12 -9
  44. data/lib/inspec/secrets/yaml.rb +5 -1
  45. data/lib/inspec/shell.rb +10 -0
  46. data/lib/inspec/ui.rb +4 -0
  47. data/lib/inspec/utils/licensing_config.rb +9 -0
  48. data/lib/inspec/utils/profile_ast_helpers.rb +12 -39
  49. data/lib/inspec/utils/waivers/csv_file_reader.rb +1 -1
  50. data/lib/inspec/utils/waivers/excel_file_reader.rb +1 -1
  51. data/lib/inspec/version.rb +1 -1
  52. data/lib/inspec/waiver_file_reader.rb +18 -35
  53. data/lib/inspec.rb +2 -1
  54. data/lib/matchers/matchers.rb +3 -3
  55. data/lib/plugins/inspec-compliance/README.md +1 -11
  56. data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +189 -170
  57. data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +10 -3
  58. data/lib/plugins/inspec-init/lib/inspec-init/cli.rb +1 -0
  59. data/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb +23 -21
  60. data/lib/plugins/inspec-init/lib/inspec-init/cli_profile.rb +15 -13
  61. data/lib/plugins/inspec-init/lib/inspec-init/cli_resource.rb +15 -13
  62. data/lib/plugins/inspec-license/README.md +16 -0
  63. data/lib/plugins/inspec-license/inspec-license.gemspec +6 -0
  64. data/lib/plugins/inspec-license/lib/inspec-license/cli.rb +26 -0
  65. data/lib/plugins/inspec-license/lib/inspec-license.rb +14 -0
  66. data/lib/plugins/inspec-parallel/README.md +27 -0
  67. data/lib/plugins/inspec-parallel/inspec-parallel.gemspec +6 -0
  68. data/lib/plugins/inspec-parallel/lib/inspec-parallel/child_status_reporter.rb +61 -0
  69. data/lib/plugins/inspec-parallel/lib/inspec-parallel/cli.rb +39 -0
  70. data/lib/plugins/inspec-parallel/lib/inspec-parallel/command.rb +219 -0
  71. data/lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb +265 -0
  72. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/base.rb +24 -0
  73. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/silent.rb +7 -0
  74. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/status.rb +124 -0
  75. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/text.rb +23 -0
  76. data/lib/plugins/inspec-parallel/lib/inspec-parallel/validator.rb +170 -0
  77. data/lib/plugins/inspec-parallel/lib/inspec-parallel.rb +18 -0
  78. data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +10 -11
  79. data/lib/plugins/inspec-sign/lib/inspec-sign/cli.rb +11 -4
  80. data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +6 -13
  81. data/lib/source_readers/inspec.rb +1 -1
  82. metadata +55 -47
@@ -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
@@ -18,7 +18,11 @@ module Secrets
18
18
  def initialize(target)
19
19
  # Ruby 3.1 treats YAML load as a dangerous operation by default, requiring us to declare date and time classes as permitted
20
20
  # It's not a valid option in 3.0.x
21
- @inputs = ::YAML.load_file(target, permitted_classes: [Date, Time])
21
+ if Gem.ruby_version >= Gem::Version.new("3.1.0")
22
+ @inputs = ::YAML.load_file(target, permitted_classes: [Date, Time])
23
+ else
24
+ @inputs = ::YAML.load_file(target)
25
+ end
22
26
 
23
27
  # In case of empty yaml file raise the warning else raise the parsing error.
24
28
  if !@inputs || @inputs.empty?
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
@@ -3,7 +3,8 @@ require "rubocop-ast"
3
3
  module Inspec
4
4
  class Profile
5
5
  class AstHelper
6
- class CollectorBase < Parser::AST::Processor
6
+ class CollectorBase
7
+ include Parser::AST::Processor::Mixin
7
8
  include RuboCop::AST::Traversal
8
9
 
9
10
  attr_reader :memo
@@ -24,37 +25,6 @@ module Inspec
24
25
  @memo = memo
25
26
  end
26
27
 
27
- def extract_node_value(node)
28
- case node.class.to_s
29
- when "RuboCop::AST::HashNode"
30
- # Handle hash nodes
31
- values = {}
32
- node.children.each do |pair_node|
33
- values.merge!(pair_node.key.value => extract_node_value(pair_node.value))
34
- end
35
- values
36
- when "RuboCop::AST::ArrayNode"
37
- # Handle array nodes
38
- node.children.map { |element| extract_node_value(element) }
39
- else
40
- # Handle simple nodes (strings, numbers, symbols, booleans, nil, etc.)
41
- if node.respond_to?(:type)
42
- case node.type
43
- when :true
44
- true
45
- when :false
46
- false
47
- when :nil
48
- nil
49
- else
50
- node.respond_to?(:value) ? node.value : node
51
- end
52
- else
53
- node.respond_to?(:value) ? node.value : node
54
- end
55
- end
56
- end
57
-
58
28
  def collect_input(input_children)
59
29
  input_name = input_children.children[2].value
60
30
 
@@ -70,9 +40,15 @@ module Inspec
70
40
  if VALID_INPUT_OPTIONS.include?(child_node.key.value)
71
41
  if child_node.value.class == RuboCop::AST::Node && REQUIRED_VALUES_MAP.key?(child_node.value.type)
72
42
  opts.merge!(child_node.key.value => REQUIRED_VALUES_MAP[child_node.value.type])
43
+ elsif child_node.value.class == RuboCop::AST::HashNode
44
+ # Here value will be a hash
45
+ values = {}
46
+ child_node.value.children.each do |grand_child_node|
47
+ values.merge!(grand_child_node.key.value => grand_child_node.value.value)
48
+ end
49
+ opts.merge!(child_node.key.value => values)
73
50
  else
74
- # Use the helper method to recursively extract values from any node type
75
- opts.merge!(child_node.key.value => extract_node_value(child_node.value))
51
+ opts.merge!(child_node.key.value => child_node.value.value)
76
52
  end
77
53
  end
78
54
  end
@@ -338,11 +314,8 @@ module Inspec
338
314
  collectors.push InputCollectorWithinControlBlock.new(@memo)
339
315
  collectors.push TestsCollector.new(control_data) if include_tests
340
316
 
341
- # Handle empty control blocks (e.g., control "id" do end)
342
- if begin_block
343
- begin_block.each_node do |node_within_control|
344
- collectors.each { |collector| collector.process(node_within_control) }
345
- end
317
+ begin_block.each_node do |node_within_control|
318
+ collectors.each { |collector| collector.process(node_within_control) }
346
319
  end
347
320
 
348
321
  memo[:controls].push control_data
@@ -19,7 +19,7 @@ module Waivers
19
19
  row_hash.delete("control_id")
20
20
  row_hash.delete_if { |k, v| k.nil? || v.nil? }
21
21
 
22
- waiver_data_hash[control_id] = row_hash if control_id && !(row_hash.nil? || row_hash.empty?)
22
+ waiver_data_hash[control_id] = row_hash if control_id && !row_hash.blank?
23
23
  end
24
24
 
25
25
  waiver_data_hash
@@ -25,7 +25,7 @@ module Waivers
25
25
  row_hash.delete_if { |k, v| k.nil? || v.nil? }
26
26
  end
27
27
 
28
- waiver_data_hash[control_id] = row_hash if control_id && !(row_hash.nil? || row_hash.empty?)
28
+ waiver_data_hash[control_id] = row_hash if control_id && !row_hash.blank?
29
29
  end
30
30
  waiver_data_hash
31
31
  rescue Exception => e
@@ -1,3 +1,3 @@
1
1
  module Inspec
2
- VERSION = "5.24.7".freeze
2
+ VERSION = "6.6.0".freeze
3
3
  end
@@ -5,8 +5,6 @@ require "inspec/utils/waivers/json_file_reader"
5
5
  module Inspec
6
6
  class WaiverFileReader
7
7
 
8
- SUPPORTED_FILE_EXTENSION = %w{.yaml .yml .csv .json}.freeze
9
-
10
8
  def self.fetch_waivers_by_profile(profile_id, files)
11
9
  read_waivers_from_file(profile_id, files) if @waivers_data.nil? || @waivers_data[profile_id].nil?
12
10
  @waivers_data[profile_id]
@@ -17,10 +15,14 @@ module Inspec
17
15
  output = {}
18
16
 
19
17
  files.each do |file_path|
20
- next unless valid_waiver_file?(file_path)
18
+ data = read_from_file(file_path)
19
+ output.merge!(data) if !data.nil? && data.is_a?(Hash)
21
20
 
22
- data = parse_waiver_file(file_path)
23
- output.merge!(data) if data.is_a?(Hash)
21
+ if data.nil?
22
+ raise Inspec::Exceptions::WaiversFileNotReadable,
23
+ "Cannot find parser for waivers file." \
24
+ "Check to make sure file has the appropriate extension."
25
+ end
24
26
  rescue Inspec::Exceptions::WaiversFileNotReadable, Inspec::Exceptions::WaiversFileInvalidFormatting => e
25
27
  Inspec::Log.error "Error reading waivers file #{file_path}. #{e.message}"
26
28
  Inspec::UI.new.exit(:usage_error)
@@ -29,38 +31,21 @@ module Inspec
29
31
  @waivers_data[profile_id] = output
30
32
  end
31
33
 
32
- def self.valid_waiver_file?(file_path)
33
- # Check if the file is readable
34
- file_extension = File.extname(file_path).downcase
35
- unless SUPPORTED_FILE_EXTENSION.include?(file_extension)
36
- raise Inspec::Exceptions::WaiversFileNotReadable,
37
- "Unsupported file extension for '#{file_path}'. Allowed waiver file extensions: #{SUPPORTED_FILE_EXTENSION.join(", ")}"
38
- end
39
-
40
- # Check if the file is empty
41
- if File.zero?(file_path)
42
- Inspec::Log.warn "Waivers file '#{file_path}' is empty. Skipping waivers."
43
- return false
44
- end
45
-
46
- true
47
- end
48
-
49
- def self.parse_waiver_file(file_path)
50
- file_extension = File.extname(file_path).downcase
51
-
52
- case file_extension
53
- when ".yaml", ".yml"
54
- data = Secrets::YAML.resolve(file_path)&.inputs
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?
55
40
  validate_json_yaml(data)
56
- when ".csv"
41
+ elsif file_extension == ".csv"
57
42
  data = Waivers::CSVFileReader.resolve(file_path)
58
- validate_csv_headers(Waivers::CSVFileReader.headers)
59
- when ".json"
43
+ headers = Waivers::CSVFileReader.headers
44
+ validate_csv_headers(headers)
45
+ elsif file_extension == ".json"
60
46
  data = Waivers::JSONFileReader.resolve(file_path)
61
- validate_json_yaml(data)
47
+ validate_json_yaml(data) unless data.nil?
62
48
  end
63
-
64
49
  data
65
50
  end
66
51
 
@@ -96,8 +81,6 @@ module Inspec
96
81
  end
97
82
 
98
83
  def self.validate_json_yaml(data)
99
- return if data.nil?
100
-
101
84
  missing_required_field = false
102
85
  data.each do |key, value|
103
86
  # In case of yaml or json we need to validate headers/parametes for each value
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"
@@ -299,9 +299,9 @@ RSpec::Matchers.define :cmp do |first_expected| # rubocop:disable Metrics/BlockL
299
299
  end
300
300
  end
301
301
 
302
- def format_actual(actual, negate = false)
302
+ def format_actual(actual)
303
303
  actual = "0%o" % actual if octal?(@expected) && !actual.nil?
304
- "\n%s\n got: %s\n\n(compared using `cmp` matcher)\n" % [format_expectation(negate), actual]
304
+ "\n%s\n got: %s\n\n(compared using `cmp` matcher)\n" % [format_expectation(false), actual]
305
305
  end
306
306
 
307
307
  def format_expectation(negate)
@@ -316,7 +316,7 @@ RSpec::Matchers.define :cmp do |first_expected| # rubocop:disable Metrics/BlockL
316
316
  end
317
317
 
318
318
  failure_message_when_negated do |actual|
319
- format_actual actual, true
319
+ format_actual actual
320
320
  end
321
321
 
322
322
  description do
@@ -14,18 +14,8 @@ To use the CLI, this InSpec add-on adds the following commands:
14
14
  * `$ inspec automate profiles` - list all available Compliance profiles
15
15
  * `$ inspec exec compliance://profile` - runs a Compliance profile
16
16
  * `$ inspec automate upload path/to/local/profile` - uploads a local profile to Chef Automate/Chef Compliance
17
- * `$ inspec automate upload path/to/local/profile --legacy` - uploads a local profile to Chef Automate/Chef Compliance using legacy functionalities of inspec check and inspec export
18
-
19
- *Options*:
20
- ```
21
- [--overwrite], [--no-overwrite] # Overwrite existing profile on Server.
22
- [--owner=OWNER] # Owner that should own the profile
23
- [--legacy], [--no-legacy] # Enable legacy functionality, activating both legacy export and legacy check.
24
-
25
- uploads a local profile to Chef Automate
26
- ```
27
17
  * `$ inspec automate logout` - logout of Chef Automate/Chef Compliance
28
-
18
+
29
19
  Similar to these CLI commands are:
30
20
 
31
21
  * `$ inspec compliance login` - authentication of the API token against Chef Automate/Chef Compliance