inspec-core 4.18.100 → 4.19.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/inspec-core.gemspec +2 -2
  4. data/lib/inspec/base_cli.rb +4 -0
  5. data/lib/inspec/cli.rb +7 -17
  6. data/lib/inspec/config.rb +28 -6
  7. data/lib/inspec/fetcher/git.rb +1 -1
  8. data/lib/inspec/fetcher/local.rb +1 -1
  9. data/lib/inspec/fetcher/url.rb +1 -1
  10. data/lib/inspec/input.rb +9 -9
  11. data/lib/inspec/plugin/v2/installer.rb +23 -2
  12. data/lib/inspec/plugin/v2/plugin_types/reporter.rb +68 -0
  13. data/lib/inspec/profile.rb +16 -4
  14. data/lib/inspec/reporters.rb +4 -1
  15. data/lib/inspec/reporters/base.rb +22 -0
  16. data/lib/inspec/resources/service.rb +2 -2
  17. data/lib/inspec/resources/virtualization.rb +86 -2
  18. data/lib/inspec/resources/x509_certificate.rb +1 -1
  19. data/lib/inspec/rule.rb +8 -4
  20. data/lib/inspec/run_data.rb +64 -0
  21. data/lib/inspec/run_data/control.rb +83 -0
  22. data/lib/inspec/run_data/profile.rb +109 -0
  23. data/lib/inspec/run_data/result.rb +40 -0
  24. data/lib/inspec/run_data/statistics.rb +36 -0
  25. data/lib/inspec/shell.rb +8 -3
  26. data/lib/inspec/utils/json_profile_summary.rb +35 -0
  27. data/lib/inspec/version.rb +1 -1
  28. data/lib/plugins/inspec-artifact/lib/inspec-artifact/base.rb +18 -1
  29. data/lib/plugins/inspec-compliance/lib/inspec-compliance/api.rb +4 -0
  30. data/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb +28 -6
  31. data/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/plugin.rb +18 -0
  32. data/lib/plugins/inspec-init/templates/plugins/inspec-plugin-template/lib/inspec-plugin-template/reporter.rb +27 -0
  33. data/lib/plugins/inspec-plugin-manager-cli/lib/inspec-plugin-manager-cli/cli_command.rb +11 -8
  34. metadata +20 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 699f8109813626e556c8f56fe2324ac80e444b62265808cbe37619d3ae5a46b2
4
- data.tar.gz: 45f7ade4fe0020e8bec0f7f21bd9819807df1ed48666e65e7ab994e24fe97cb5
3
+ metadata.gz: 0c6037004db1c4ef1ba337ea6ef710938a2ce1d02c45e56b0bdfe7349b146a92
4
+ data.tar.gz: 926e31bc929c5e5c76e3a5e89ed686218000a2ad5374c6b7901ac006ee2536a7
5
5
  SHA512:
6
- metadata.gz: b85550dd9d166506cbcda16e776db29edff56cf2f174c91dee528498d6224232dcd8c5d8b11ec8a56252cf40c03ed43205de56d802ea36f6ad8a03740c368826
7
- data.tar.gz: a5b46d86ecf6762dd6f2643e67a0dc3274a36c71da4acd81950211ba19ac5f388f7eff2c7b860b65f168299df96d76727a083a0e8d9c50fa1184fb39a221c0a3
6
+ metadata.gz: 36c0d1cf39247500f0bc33a633279c08bdaeab0c2dc848fc14f54405cb8c3b3fb1d86ae0ef0afc0ce1394c73b6de978fc61e4ff523b2dfc56d58536920caae47
7
+ data.tar.gz: c385ff9ee77aca4689dc4a83c8bef4984a3b509e6a7f459655e44a5f1a1944792c2ac1f78b0626cde72697c2b6fee1d4c2eb05ae566d928596e447d5d08f2e34
data/README.md CHANGED
@@ -141,7 +141,7 @@ That requires [bundler](http://bundler.io/):
141
141
 
142
142
  ```bash
143
143
  bundle install
144
- bundle exec bin/inspec help
144
+ bundle exec inspec help
145
145
  ```
146
146
 
147
147
  To install it as a gem locally, run:
@@ -27,11 +27,11 @@ Gem::Specification.new do |spec|
27
27
  spec.add_dependency "license-acceptance", ">= 0.2.13", "< 2.0"
28
28
  spec.add_dependency "thor", ">= 0.20", "< 2.0"
29
29
  spec.add_dependency "json_schemer", "~> 0.2.1"
30
- spec.add_dependency "method_source", "~> 0.8"
30
+ spec.add_dependency "method_source", ">= 0.8", "< 2.0"
31
31
  spec.add_dependency "rubyzip", "~> 1.2", ">= 1.2.2"
32
32
  spec.add_dependency "rspec", "~> 3.9"
33
33
  spec.add_dependency "rspec-its", "~> 1.2"
34
- spec.add_dependency "pry", "~> 0"
34
+ spec.add_dependency "pry", "~> 0.13"
35
35
  spec.add_dependency "hashie", "~> 3.4"
36
36
  spec.add_dependency "mixlib-log", "~> 3.0"
37
37
  spec.add_dependency "sslshake", "~> 1.2"
@@ -135,6 +135,10 @@ module Inspec
135
135
  option :reporter, type: :array,
136
136
  banner: "one two:/output/file/path",
137
137
  desc: "Enable one or more output reporters: cli, documentation, html, progress, json, json-min, json-rspec, junit, yaml"
138
+ option :reporter_message_truncation, type: :string,
139
+ desc: "Number of characters to truncate failure messages in report data to (default: no truncation)"
140
+ option :reporter_backtrace_inclusion, type: :boolean,
141
+ desc: "Include a code backtrace in report data (default: true)"
138
142
  option :input, type: :array, banner: "name1=value1 name2=value2",
139
143
  desc: "Specify one or more inputs directly on the command line, as --input NAME=VALUE"
140
144
  option :input_file, type: :array,
@@ -4,6 +4,7 @@ require "inspec/utils/deprecation/deprecator"
4
4
  require "inspec/dist"
5
5
  require "inspec/backend"
6
6
  require "inspec/dependencies/cache"
7
+ require "inspec/utils/json_profile_summary"
7
8
 
8
9
  module Inspec # TODO: move this somewhere "better"?
9
10
  autoload :BaseCLI, "inspec/base_cli"
@@ -77,24 +78,13 @@ class Inspec::InspecCLI < Inspec::BaseCLI
77
78
  o[:vendor_cache] = Inspec::Cache.new(o[:vendor_cache])
78
79
 
79
80
  profile = Inspec::Profile.for_target(target, o)
80
- info = profile.info
81
- # add in inspec version
82
- info[:generator] = {
83
- name: "inspec",
84
- version: Inspec::VERSION,
85
- }
86
81
  dst = o[:output].to_s
87
- if dst.empty?
88
- puts JSON.dump(info)
89
- else
90
- if File.exist? dst
91
- puts "----> updating #{dst}"
92
- else
93
- puts "----> creating #{dst}"
94
- end
95
- fdst = File.expand_path(dst)
96
- File.write(fdst, JSON.dump(info))
97
- end
82
+
83
+ # Write JSON
84
+ Inspec::Utils::JsonProfileSummary.produce_json(
85
+ info: profile.info,
86
+ write_path: dst
87
+ )
98
88
  rescue StandardError => e
99
89
  pretty_handle_exception(e)
100
90
  end
@@ -328,21 +328,36 @@ module Inspec
328
328
  def validate_reporters!(reporters)
329
329
  return if reporters.nil?
330
330
 
331
- # TODO: move this into a reporter plugin type system
332
- valid_types = %w{
333
- automate
334
- cli
331
+ # These "reporters" are actually RSpec Formatters.
332
+ # json-rspec is our alias for RSpec's json formatter.
333
+ rspec_built_in_formatters = %w{
335
334
  documentation
336
335
  html
336
+ json-rspec
337
+ progress
338
+ }
339
+
340
+ # These are true reporters, but have not been migrated to be plugins yet.
341
+ # Tracked on https://github.com/inspec/inspec/issues/3667
342
+ inspec_reporters_that_are_not_yet_plugins = %w{
343
+ automate
344
+ cli
337
345
  json
338
346
  json-automate
339
347
  json-min
340
- json-rspec
341
348
  junit
342
- progress
343
349
  yaml
344
350
  }
345
351
 
352
+ # Additional reporters may be loaded via plugins. They will have already been detected at
353
+ # this point (see v2_loader.load_all in cli.rb) but they may not (and need not) be
354
+ # activated at this point. We only care about their existance and their name, for validation's sake.
355
+ plugin_reporters = Inspec::Plugin::V2::Registry.instance\
356
+ .find_activators(plugin_type: :reporter)\
357
+ .map(&:activator_name).map(&:to_s)
358
+
359
+ valid_types = rspec_built_in_formatters + inspec_reporters_that_are_not_yet_plugins + plugin_reporters
360
+
346
361
  reporters.each do |reporter_name, reporter_config|
347
362
  raise NotImplementedError, "'#{reporter_name}' is not a valid reporter type." unless valid_types.include?(reporter_name)
348
363
 
@@ -360,6 +375,13 @@ module Inspec
360
375
  end
361
376
 
362
377
  raise ArgumentError, "The option --reporter can only have a single report outputting to stdout." if stdout_reporters > 1
378
+
379
+ # reporter_message_truncation needs to either be the string "ALL", an Integer, or a string representing an integer
380
+ if (truncation = @merged_options["reporter_message_truncation"])
381
+ unless truncation == "ALL" || truncation.is_a?(Integer) || truncation.to_i.to_s == truncation
382
+ raise ArgumentError, "reporter_message_truncation is set to #{truncation}. It must be set to an integer value or ALL to indicate no truncation."
383
+ end
384
+ end
363
385
  end
364
386
 
365
387
  def validate_plugins!
@@ -99,7 +99,7 @@ module Inspec::Fetcher
99
99
  def cache_key
100
100
  return resolved_ref unless @relative_path
101
101
 
102
- OpenSSL::Digest::SHA256.hexdigest(resolved_ref + @relative_path)
102
+ OpenSSL::Digest.hexdigest("SHA256", resolved_ref + @relative_path)
103
103
  end
104
104
 
105
105
  def archive_path
@@ -104,7 +104,7 @@ module Inspec::Fetcher
104
104
  return @archive_shasum if @archive_shasum
105
105
  raise(Inspec::FetcherFailure, "Profile dependency local path '#{target}' does not exist") unless File.exist?(target)
106
106
 
107
- @archive_shasum = OpenSSL::Digest::SHA256.digest(File.read(target)).unpack("H*")[0]
107
+ @archive_shasum = OpenSSL::Digest.digest("SHA256", File.read(target)).unpack("H*")[0]
108
108
  end
109
109
 
110
110
  def resolved_source
@@ -127,7 +127,7 @@ module Inspec::Fetcher
127
127
  end
128
128
 
129
129
  def sha256
130
- @archive_shasum ||= OpenSSL::Digest::SHA256.digest(File.read(@archive_path || temp_archive_path)).unpack("H*")[0]
130
+ @archive_shasum ||= OpenSSL::Digest.digest("SHA256", File.read(@archive_path || temp_archive_path)).unpack("H*")[0]
131
131
  end
132
132
 
133
133
  def file_type_from_remote(remote)
@@ -98,15 +98,15 @@ module Inspec
98
98
  # not been assigned a value. This allows a user to explicitly assign nil
99
99
  # to an input.
100
100
  class NO_VALUE_SET # rubocop: disable Naming/ClassAndModuleCamelCase
101
- def initialize(name)
101
+ def initialize(name, warn_on_create = true)
102
102
  @name = name
103
103
 
104
104
  # output warn message if we are in a exec call
105
- if Inspec::BaseCLI.inspec_cli_command == :exec
105
+ if warn_on_create && Inspec::BaseCLI.inspec_cli_command == :exec
106
106
  Inspec::Log.warn(
107
107
  "Input '#{@name}' does not have a value. "\
108
- "Use --input-file to provide a value for '#{@name}' or specify a "\
109
- "value with `attribute('#{@name}', value: 'somevalue', ...)`."
108
+ "Use --input-file or --input to provide a value for '#{@name}' or specify a "\
109
+ "value with `input('#{@name}', value: 'somevalue', ...)`."
110
110
  )
111
111
  end
112
112
  end
@@ -277,7 +277,7 @@ module Inspec
277
277
  end
278
278
 
279
279
  # Determine the current winning value, but don't validate it
280
- def current_value
280
+ def current_value(warn_on_missing = true)
281
281
  # Examine the events to determine highest-priority value. Tie-break
282
282
  # by using the last one set.
283
283
  events_that_set_a_value = events.select(&:value_has_been_set?)
@@ -287,7 +287,7 @@ module Inspec
287
287
 
288
288
  if winning_event.nil?
289
289
  # No value has been set - return special no value object
290
- NO_VALUE_SET.new(name)
290
+ NO_VALUE_SET.new(name, warn_on_missing)
291
291
  else
292
292
  winning_event.value # May still be nil
293
293
  end
@@ -315,7 +315,7 @@ module Inspec
315
315
  end
316
316
 
317
317
  def has_value?
318
- !current_value.is_a? NO_VALUE_SET
318
+ !current_value(false).is_a? NO_VALUE_SET
319
319
  end
320
320
 
321
321
  def to_hash
@@ -348,7 +348,7 @@ module Inspec
348
348
  # skip if we are not doing an exec call (archive/vendor/check)
349
349
  return unless Inspec::BaseCLI.inspec_cli_command == :exec
350
350
 
351
- proposed_value = current_value
351
+ proposed_value = current_value(false)
352
352
  if proposed_value.nil? || proposed_value.is_a?(NO_VALUE_SET)
353
353
  error = Inspec::Input::RequiredError.new
354
354
  error.input_name = name
@@ -363,7 +363,7 @@ module Inspec
363
363
  type_req = type
364
364
  return if type_req == "Any"
365
365
 
366
- proposed_value = current_value
366
+ proposed_value = current_value(false)
367
367
 
368
368
  invalid_type = false
369
369
  if type_req == "Regexp"
@@ -3,6 +3,7 @@
3
3
  require "singleton"
4
4
  require "forwardable"
5
5
  require "fileutils"
6
+ require "uri"
6
7
 
7
8
  # Gem extensions for doing unusual things - not loaded by Gem default
8
9
  require "rubygems/package"
@@ -56,6 +57,7 @@ module Inspec::Plugin::V2
56
57
  # @option opts [String] :gem_file Path to a local gem file to install from
57
58
  # @option opts [String] :path Path to a file to be used as the entry point for a path-based plugin
58
59
  # @option opts [String] :version Version constraint for remote gem installs
60
+ # @option opts [String] :source Alternate URL to use instead of rubygems.org
59
61
  def install(plugin_name, opts = {})
60
62
  # TODO: - check plugins.json for validity before trying anything that needs to modify it.
61
63
  validate_installation_opts(plugin_name, opts)
@@ -127,6 +129,11 @@ module Inspec::Plugin::V2
127
129
  validate_search_opts(plugin_query, opts)
128
130
 
129
131
  fetcher = Gem::SpecFetcher.fetcher
132
+ if opts[:source]
133
+ source_list = Gem::SourceList.from([opts[:source]])
134
+ fetcher = Gem::SpecFetcher.new(source_list)
135
+ end
136
+
130
137
  matched_tuples = []
131
138
  if opts[:exact]
132
139
  matched_tuples = fetcher.detect(opts[:scope]) { |tuple| tuple.name == plugin_query }
@@ -284,8 +291,22 @@ module Inspec::Plugin::V2
284
291
 
285
292
  def install_from_remote_gems(requested_plugin_name, opts)
286
293
  plugin_dependency = Gem::Dependency.new(requested_plugin_name, opts[:version] || "> 0")
287
- # BestSet is rubygems.org API + indexing
288
- install_gem_to_plugins_dir(plugin_dependency, [Gem::Resolver::BestSet.new], opts[:update_mode])
294
+
295
+ # BestSet is rubygems.org API + indexing, APISet is for custom sources
296
+ sources = if opts[:source]
297
+ Gem::Resolver::APISet.new(URI.join(opts[:source] + "/api/v1/dependencies"))
298
+ else
299
+ Gem::Resolver::BestSet.new
300
+ end
301
+
302
+ begin
303
+ install_gem_to_plugins_dir(plugin_dependency, [sources], opts[:update_mode])
304
+ rescue Gem::RemoteFetcher::FetchError => gem_ex
305
+ # TODO: Give a hint if the host was not resolvable or a 404 occured
306
+ ex = Inspec::Plugin::V2::InstallError.new(gem_ex.message)
307
+ ex.plugin_name = requested_plugin_name
308
+ raise ex
309
+ end
289
310
  end
290
311
 
291
312
  def install_gem_to_plugins_dir(new_plugin_dependency, # rubocop: disable Metrics/AbcSize
@@ -0,0 +1,68 @@
1
+ require_relative "../../../run_data"
2
+
3
+ module Inspec::Plugin::V2::PluginType
4
+ class Reporter < Inspec::Plugin::V2::PluginBase
5
+ register_plugin_type(:reporter)
6
+
7
+ attr_reader :run_data
8
+
9
+ def initialize(config)
10
+ @config = config
11
+
12
+ # Trim the run_data while still a Hash; if it is huge, this
13
+ # saves on conversion time
14
+ @run_data = config[:run_data] || {}
15
+ apply_report_resize_options
16
+
17
+ unless Inspec::RunData.compatible_schema?(self.class.run_data_schema_constraints)
18
+ # Best we can do is warn here, the InSpec run has finished
19
+ # TODO: one day, perhaps switch RunData implementations to try to satisfy constraints?
20
+ Inspec::Log.warn "Reporter does not support RunData API (#{Inspec::RunData::SCHEMA_VERSION}), Reporter constraints: '#{self.class.run_data_schema_constraints}'"
21
+ end
22
+ # Convert to RunData object for consumption by Reporter
23
+ @run_data = Inspec::RunData.new(@run_data)
24
+ @output = ""
25
+ end
26
+
27
+ # This is a temporary duplication of code from lib/inspec/reporters/base.rb
28
+ # To be DRY'd up once the core reporters become plugins...
29
+ # Apply options such as message truncation and removal of backtraces
30
+ def apply_report_resize_options
31
+ runtime_config = Inspec::Config.cached.respond_to?(:final_options) ? Inspec::Config.cached.final_options : {}
32
+
33
+ message_truncation = runtime_config[:reporter_message_truncation] || "ALL"
34
+ trunc = message_truncation == "ALL" ? -1 : message_truncation.to_i
35
+ include_backtrace = runtime_config[:reporter_backtrace_inclusion].nil? ? true : runtime_config[:reporter_backtrace_inclusion]
36
+
37
+ @run_data[:profiles]&.each do |p|
38
+ p[:controls].each do |c|
39
+ c[:results]&.map! do |r|
40
+ r.delete(:backtrace) unless include_backtrace
41
+ if r.key?(:message) && r[:message] != "" && trunc > -1
42
+ r[:message] = r[:message][0...trunc] + "[Truncated to #{trunc} characters]"
43
+ end
44
+ r
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def output(str, newline = true)
51
+ @output << str
52
+ @output << "\n" if newline
53
+ end
54
+
55
+ def rendered_output
56
+ @output
57
+ end
58
+
59
+ # each reporter must implement #render
60
+ def render
61
+ raise NotImplementedError, "#{self.class} must implement a `#render` method to format its output."
62
+ end
63
+
64
+ def self.run_data_schema_constraints
65
+ raise NotImplementedError, "#{self.class} must implement a `run_data_schema_constraints` class method to declare its compatibiltity with the RunData API."
66
+ end
67
+ end
68
+ end
@@ -12,6 +12,7 @@ require "inspec/method_source"
12
12
  require "inspec/dependencies/cache"
13
13
  require "inspec/dependencies/lockfile"
14
14
  require "inspec/dependencies/dependency_set"
15
+ require "inspec/utils/json_profile_summary"
15
16
 
16
17
  module Inspec
17
18
  class Profile
@@ -465,28 +466,39 @@ module Inspec
465
466
  end
466
467
 
467
468
  # remove existing archive
468
- File.delete(dst) if dst.exist?
469
+ FileUtils.rm_f(dst) if dst.exist?
469
470
  @logger.info "Generate archive #{dst}."
470
471
 
471
472
  # filter files that should not be part of the profile
472
473
  # TODO ignore all .files, but add the files to debug output
473
474
 
475
+ # Generate temporary inspec.json for archive
476
+ Inspec::Utils::JsonProfileSummary.produce_json(
477
+ info: info,
478
+ write_path: "#{root_path}inspec.json",
479
+ suppress_output: true
480
+ )
481
+
474
482
  # display all files that will be part of the archive
475
483
  @logger.debug "Add the following files to archive:"
476
484
  files.each { |f| @logger.debug " " + f }
485
+ @logger.debug " inspec.json"
477
486
 
478
487
  if opts[:zip]
479
488
  # generate zip archive
480
489
  require "inspec/archive/zip"
481
490
  zag = Inspec::Archive::ZipArchiveGenerator.new
482
- zag.archive(root_path, files, dst)
491
+ zag.archive(root_path, files.push("inspec.json"), dst)
483
492
  else
484
493
  # generate tar archive
485
494
  require "inspec/archive/tar"
486
495
  tag = Inspec::Archive::TarArchiveGenerator.new
487
- tag.archive(root_path, files, dst)
496
+ tag.archive(root_path, files.push("inspec.json"), dst)
488
497
  end
489
498
 
499
+ # Cleanup
500
+ FileUtils.rm_f("#{root_path}inspec.json")
501
+
490
502
  @logger.info "Finished archive generation."
491
503
  true
492
504
  end
@@ -559,7 +571,7 @@ module Inspec
559
571
  # get all dependency checksums
560
572
  deps = Hash[locked_dependencies.list.map { |k, v| [k, v.profile.sha256] }]
561
573
 
562
- res = OpenSSL::Digest::SHA256.new
574
+ res = OpenSSL::Digest.new("SHA256")
563
575
  files = source_reader.tests.to_a + source_reader.libraries.to_a +
564
576
  source_reader.data_files.to_a +
565
577
  [["inspec.yml", source_reader.metadata.content]] +
@@ -30,7 +30,10 @@ module Inspec::Reporters
30
30
  when "yaml"
31
31
  reporter = Inspec::Reporters::Yaml.new(config)
32
32
  else
33
- raise NotImplementedError, "'#{name}' is not a valid reporter type."
33
+ # If we made it here, it must be a plugin, and we know it exists (because we validated it in config.rb)
34
+ activator = Inspec::Plugin::V2::Registry.instance.find_activator(plugin_type: :reporter, activator_name: name.to_sym)
35
+ activator.activate!
36
+ reporter = activator.implementation_class.new(config)
34
37
  end
35
38
 
36
39
  # optional send_report method on reporter
@@ -5,9 +5,31 @@ module Inspec::Reporters
5
5
  def initialize(config)
6
6
  @config = config
7
7
  @run_data = config[:run_data]
8
+ apply_report_resize_options unless @run_data.nil?
8
9
  @output = ""
9
10
  end
10
11
 
12
+ # Apply options such as message truncation and removal of backtraces
13
+ def apply_report_resize_options
14
+ runtime_config = Inspec::Config.cached.respond_to?(:final_options) ? Inspec::Config.cached.final_options : {}
15
+
16
+ message_truncation = runtime_config[:reporter_message_truncation] || "ALL"
17
+ trunc = message_truncation == "ALL" ? -1 : message_truncation.to_i
18
+ include_backtrace = runtime_config[:reporter_backtrace_inclusion].nil? ? true : runtime_config[:reporter_backtrace_inclusion]
19
+
20
+ @run_data[:profiles]&.each do |p|
21
+ p[:controls].each do |c|
22
+ c[:results]&.map! do |r|
23
+ r.delete(:backtrace) unless include_backtrace
24
+ if r.key?(:message) && r[:message] != "" && trunc > -1
25
+ r[:message] = r[:message][0...trunc] + "[Truncated to #{trunc} characters]"
26
+ end
27
+ r
28
+ end
29
+ end
30
+ end
31
+ end
32
+
11
33
  def output(str, newline = true)
12
34
  @output << str
13
35
  @output << "\n" if newline