inspec-core 4.18.100 → 4.19.0

Sign up to get free protection for your applications and to get access to all the features.
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