inspec-core 5.22.29 → 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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/Chef-EULA +9 -0
  3. data/Gemfile +10 -1
  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 +300 -230
  11. data/lib/inspec/config.rb +24 -2
  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 +373 -12
  24. data/lib/inspec/reporters/cli.rb +1 -1
  25. data/lib/inspec/reporters.rb +67 -54
  26. data/lib/inspec/resources/security_policy.rb +7 -2
  27. data/lib/inspec/run_data.rb +7 -5
  28. data/lib/inspec/runner.rb +34 -5
  29. data/lib/inspec/runner_rspec.rb +12 -9
  30. data/lib/inspec/secrets/yaml.rb +9 -3
  31. data/lib/inspec/shell.rb +10 -0
  32. data/lib/inspec/ui.rb +4 -0
  33. data/lib/inspec/utils/licensing_config.rb +9 -0
  34. data/lib/inspec/utils/profile_ast_helpers.rb +372 -0
  35. data/lib/inspec/version.rb +1 -1
  36. data/lib/inspec/waiver_file_reader.rb +68 -27
  37. data/lib/inspec.rb +2 -1
  38. data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +189 -168
  39. data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +10 -3
  40. data/lib/plugins/inspec-init/lib/inspec-init/cli.rb +1 -0
  41. data/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb +23 -21
  42. data/lib/plugins/inspec-init/lib/inspec-init/cli_profile.rb +15 -13
  43. data/lib/plugins/inspec-init/lib/inspec-init/cli_resource.rb +15 -13
  44. data/lib/plugins/inspec-license/README.md +16 -0
  45. data/lib/plugins/inspec-license/inspec-license.gemspec +6 -0
  46. data/lib/plugins/inspec-license/lib/inspec-license/cli.rb +26 -0
  47. data/lib/plugins/inspec-license/lib/inspec-license.rb +14 -0
  48. data/lib/plugins/inspec-parallel/README.md +27 -0
  49. data/lib/plugins/inspec-parallel/inspec-parallel.gemspec +6 -0
  50. data/lib/plugins/inspec-parallel/lib/inspec-parallel/child_status_reporter.rb +61 -0
  51. data/lib/plugins/inspec-parallel/lib/inspec-parallel/cli.rb +39 -0
  52. data/lib/plugins/inspec-parallel/lib/inspec-parallel/command.rb +219 -0
  53. data/lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb +265 -0
  54. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/base.rb +24 -0
  55. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/silent.rb +7 -0
  56. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/status.rb +124 -0
  57. data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/text.rb +23 -0
  58. data/lib/plugins/inspec-parallel/lib/inspec-parallel/validator.rb +170 -0
  59. data/lib/plugins/inspec-parallel/lib/inspec-parallel.rb +18 -0
  60. data/lib/plugins/inspec-reporter-html2/templates/control.html.erb +7 -6
  61. data/lib/plugins/inspec-reporter-html2/templates/default.js +6 -6
  62. data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +6 -2
  63. data/lib/plugins/inspec-sign/lib/inspec-sign/cli.rb +11 -4
  64. data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +6 -13
  65. metadata +54 -13
@@ -15,6 +15,8 @@ require "inspec/dependencies/dependency_set"
15
15
  require "inspec/utils/json_profile_summary"
16
16
  require "inspec/dependency_loader"
17
17
  require "inspec/dependency_installer"
18
+ require "inspec/utils/profile_ast_helpers"
19
+ require "plugins/inspec-sign/lib/inspec-sign/base"
18
20
 
19
21
  module Inspec
20
22
  class Profile
@@ -81,7 +83,7 @@ module Inspec
81
83
  end
82
84
 
83
85
  attr_reader :source_reader, :backend, :runner_context, :check_mode
84
- attr_accessor :parent_profile, :profile_id, :profile_name
86
+ attr_accessor :parent_profile, :profile_id, :profile_name, :target
85
87
  def_delegator :@source_reader, :tests
86
88
  def_delegator :@source_reader, :libraries
87
89
  def_delegator :@source_reader, :metadata
@@ -180,6 +182,26 @@ module Inspec
180
182
  @state == :failed
181
183
  end
182
184
 
185
+ def verify_if_signed
186
+ if signed?
187
+ verify_signed_profile
188
+ true
189
+ else
190
+ false
191
+ end
192
+ end
193
+
194
+ def signed?
195
+ # Signed profiles have .iaf extension
196
+ (@source_reader&.target&.parent&.class == Inspec::IafProvider)
197
+ end
198
+
199
+ def verify_signed_profile
200
+ # Kitchen inspec send target profile in Hash format in some scenarios. For example: While using local profile with kitchen, {:path => "path/to/kitchen/lcoal-profile"}
201
+ target_profile = target.is_a?(Hash) ? target.values[0] : target
202
+ InspecPlugins::Sign::Base.profile_verify(target_profile, true) if target_profile
203
+ end
204
+
183
205
  #
184
206
  # Is this profile is supported on the current platform of the
185
207
  # backend machine and the current inspec version.
@@ -214,8 +236,30 @@ module Inspec
214
236
  @params ||= load_params
215
237
  end
216
238
 
239
+ def virtual_profile?
240
+ # A virtual profile is for virtual profile evaluation
241
+ # Used by shell & inspec detect command.
242
+ (name == "inspec-shell") && (files&.length == 1) && (files[0] == "inspec.yml")
243
+ end
244
+
217
245
  def collect_tests
218
246
  unless @tests_collected || failed?
247
+
248
+ # This is that one common place in InSpec engine which is used to collect tests of InSpec profile
249
+ # One common place used by most of the CLI commands using profile, like exec, export etc
250
+ # Checking for profile signature in parent profile only
251
+ # Child profiles of a signed profile are extracted to cache dir
252
+ # Hence they are not in .iaf format
253
+ # Only runs this block when preview flag CHEF_PREVIEW_MANDATORY_PROFILE_SIGNING is set
254
+ Inspec.with_feature("inspec-mandatory-profile-signing") {
255
+ if !parent_profile && !virtual_profile?
256
+ cfg = Inspec::Config.cached
257
+ if cfg.is_a?(Inspec::Config) && !cfg.allow_unsigned_profiles?
258
+ raise Inspec::ProfileSignatureRequired, "Signature required for profile: #{name}. Please provide a signed profile. Or set CHEF_ALLOW_UNSIGNED_PROFILES in the environment. Or use `--allow-unsigned-profiles` flag with InSpec CLI." unless verify_if_signed
259
+ end
260
+ end
261
+ }
262
+
219
263
  return unless supports_platform?
220
264
 
221
265
  locked_dependencies.each(&:collect_tests)
@@ -514,6 +558,135 @@ module Inspec
514
558
  res
515
559
  end
516
560
 
561
+ # Return data like profile.info(params), but try to do so without evaluating the profile.
562
+ def info_from_parse(include_tests: false)
563
+ return @info_from_parse unless @info_from_parse.nil?
564
+
565
+ @info_from_parse = {
566
+ controls: [],
567
+ groups: [],
568
+ }
569
+
570
+ # TODO - look at the various source contents
571
+ # PASS 1: parse them using rubocop-ast
572
+ # Look for controls, top-level metadata, and inputs
573
+ # PASS 2: Using the control IDs, deterimine the extents -
574
+ # line locations - of the coontrol IDs in each file, and
575
+ # then extract each source code block. Use this to populate the source code
576
+ # locations and 'code' properties.
577
+
578
+ # TODO: Verify that it doesn't do evaluation (ideally shouldn't because it is reading simply yaml file)
579
+ @info_from_parse = @info_from_parse.merge(metadata.params)
580
+
581
+ inputs_hash = {}
582
+ # Note: This only handles the case when inputs are defined in metadata file
583
+ if @profile_id.nil?
584
+ # identifying inputs using profile name
585
+ inputs_hash = Inspec::InputRegistry.list_inputs_for_profile(@info_from_parse[:name])
586
+ else
587
+ inputs_hash = Inspec::InputRegistry.list_inputs_for_profile(@profile_id)
588
+ end
589
+
590
+ # TODO: Verify if I need to do the below conversion for inputs to array
591
+ if inputs_hash.nil? || inputs_hash.empty?
592
+ # convert to array for backwards compatability
593
+ @info_from_parse[:inputs] = []
594
+ else
595
+ @info_from_parse[:inputs] = inputs_hash.values.map(&:to_hash)
596
+ end
597
+
598
+ @info_from_parse[:sha256] = sha256
599
+
600
+ # Populate :status and :status_message
601
+ if supports_platform?
602
+ @info_from_parse[:status_message] = @status_message || ""
603
+ @info_from_parse[:status] = failed? ? "failed" : "loaded"
604
+ else
605
+ @info_from_parse[:status] = "skipped"
606
+ msg = "Skipping profile: '#{name}' on unsupported platform: '#{backend.platform.name}/#{backend.platform.release}'."
607
+ @info_from_parse[:status_message] = msg
608
+ end
609
+
610
+ # @source_reader.tests contains a hash mapping control filenames to control file contents
611
+ @source_reader.tests.each do |control_filename, control_file_source|
612
+ # Parse the source code
613
+ src = RuboCop::AST::ProcessedSource.new(control_file_source, RUBY_VERSION.to_f)
614
+ source_location_ref = @source_reader.target.abs_path(control_filename)
615
+
616
+ input_collector = Inspec::Profile::AstHelper::InputCollectorOutsideControlBlock.new(@info_from_parse)
617
+ ctl_id_collector = Inspec::Profile::AstHelper::ControlIDCollector.new(@info_from_parse, source_location_ref,
618
+ include_tests: include_tests)
619
+
620
+ # Collect all metadata defined in the control block and inputs defined inside the control block
621
+ src.ast.each_node { |n|
622
+ ctl_id_collector.process(n)
623
+ input_collector.process(n)
624
+ }
625
+
626
+ # For each control ID
627
+ # Look for per-control metadata
628
+ # Filter controls by --controls, list of controls to include is available in include_controls_list
629
+
630
+ # NOTE: This is a hack to duplicate refs.
631
+ # TODO: Fix this in the ref collector or the way we traverse the AST
632
+ @info_from_parse[:controls].each { |control| control[:refs].uniq! }
633
+
634
+ @info_from_parse[:controls] = filter_controls_by_id_and_tags(@info_from_parse[:controls])
635
+
636
+ # Update groups after filtering controls to handle --controls option
637
+ update_groups_from(control_filename, src)
638
+
639
+ # NOTE: This is a hack to duplicate inputs.
640
+ # TODO: Fix this in the input collector or the way we traverse the AST
641
+ @info_from_parse[:inputs] = @info_from_parse[:inputs].uniq
642
+ end
643
+ @info_from_parse
644
+ end
645
+
646
+ def filter_controls_by_id_and_tags(controls)
647
+ controls.select do |control|
648
+ tag_ids = get_all_tags_list(control[:tags])
649
+ (include_controls_list.empty? || include_controls_list.any? { |control_id| control_id.match?(control[:id]) }) &&
650
+ (include_tags_list.empty? || include_tags_list.any? { |tag_id| tag_ids.any? { |tag| tag_id.match?(tag) } })
651
+ end
652
+ end
653
+
654
+ def get_all_tags_list(control_tags)
655
+ all_tags = []
656
+ control_tags.each do |tags|
657
+ all_tags.push(tags)
658
+ end
659
+ all_tags.flatten.compact.uniq.map(&:to_s)
660
+ rescue
661
+ []
662
+ end
663
+
664
+ def include_group_data?(group_data)
665
+ unless include_controls_list.empty?
666
+ # {:id=>"controls/example-tmp.rb", :title=>"/ profile", :controls=>["tmp-1.0"]}
667
+ # Check if the group should be included based on the controls it contains
668
+ group_data[:controls].any? do |control_id|
669
+ include_controls_list.any? { |id| id.match?(control_id) }
670
+ end
671
+ else
672
+ true
673
+ end
674
+ end
675
+
676
+ def update_groups_from(control_filename, src)
677
+ group_data = {
678
+ id: control_filename,
679
+ title: nil,
680
+ }
681
+ source_location_ref = @source_reader.target.abs_path(control_filename)
682
+ Inspec::Profile::AstHelper::TitleCollector.new(group_data)
683
+ .process(src.ast.child_nodes.first) # Picking the title defined for the whole controls file
684
+ group_controls = @info_from_parse[:controls].select { |control| control[:source_location][:ref] == source_location_ref }
685
+ group_data[:controls] = group_controls.map { |control| control[:id] }
686
+
687
+ @info_from_parse[:groups].push(group_data) if include_group_data?(group_data)
688
+ end
689
+
517
690
  def cookstyle_linting_check
518
691
  msgs = []
519
692
  return msgs if Inspec.locally_windows? # See #5723
@@ -553,6 +726,122 @@ module Inspec
553
726
  end
554
727
  end
555
728
 
729
+ def legacy_check # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
730
+ # initial values for response object
731
+ result = {
732
+ summary: {
733
+ valid: false,
734
+ timestamp: Time.now.iso8601,
735
+ location: @target,
736
+ profile: nil,
737
+ controls: 0,
738
+ },
739
+ errors: [],
740
+ warnings: [],
741
+ offenses: [],
742
+ }
743
+
744
+ entry = lambda { |file, line, column, control, msg|
745
+ {
746
+ file: file,
747
+ line: line,
748
+ column: column,
749
+ control_id: control,
750
+ msg: msg,
751
+ }
752
+ }
753
+
754
+ warn = lambda { |file, line, column, control, msg|
755
+ @logger.warn(msg)
756
+ result[:warnings].push(entry.call(file, line, column, control, msg))
757
+ }
758
+
759
+ error = lambda { |file, line, column, control, msg|
760
+ @logger.error(msg)
761
+ result[:errors].push(entry.call(file, line, column, control, msg))
762
+ }
763
+
764
+ offense = lambda { |file, line, column, control, msg|
765
+ result[:offenses].push(entry.call(file, line, column, control, msg))
766
+ }
767
+
768
+ @logger.info "Checking profile in #{@target}"
769
+ meta_path = @source_reader.target.abs_path(@source_reader.metadata.ref)
770
+
771
+ # verify metadata
772
+ m_errors, m_warnings = metadata.valid
773
+ m_errors.each { |msg| error.call(meta_path, 0, 0, nil, msg) }
774
+ m_warnings.each { |msg| warn.call(meta_path, 0, 0, nil, msg) }
775
+ m_unsupported = metadata.unsupported
776
+ m_unsupported.each { |u| warn.call(meta_path, 0, 0, nil, "doesn't support: #{u}") }
777
+ @logger.info "Metadata OK." if m_errors.empty? && m_unsupported.empty?
778
+
779
+ # only run the vendor check if the legacy profile-path is not used as argument
780
+ if @legacy_profile_path == false
781
+ # verify that a lockfile is present if we have dependencies
782
+ unless metadata.dependencies.empty?
783
+ error.call(meta_path, 0, 0, nil, "Your profile needs to be vendored with `inspec vendor`.") unless lockfile_exists?
784
+ end
785
+
786
+ if lockfile_exists?
787
+ # verify if metadata and lockfile are out of sync
788
+ if lockfile.deps.size != metadata.dependencies.size
789
+ error.call(meta_path, 0, 0, nil, "inspec.yml and inspec.lock are out-of-sync. Please re-vendor with `inspec vendor`.")
790
+ end
791
+
792
+ # verify if metadata and lockfile have the same dependency names
793
+ metadata.dependencies.each do |dep|
794
+ # Skip if the dependency does not specify a name
795
+ next if dep[:name].nil?
796
+
797
+ # TODO: should we also verify that the soure is the same?
798
+ unless lockfile.deps.map { |x| x[:name] }.include? dep[:name]
799
+ error.call(meta_path, 0, 0, nil, "Cannot find #{dep[:name]} in lockfile. Please re-vendor with `inspec vendor`.")
800
+ end
801
+ end
802
+ end
803
+ end
804
+
805
+ # extract profile name
806
+ result[:summary][:profile] = metadata.params[:name]
807
+
808
+ count = params[:controls].values.length
809
+ result[:summary][:controls] = count
810
+ if count == 0
811
+ warn.call(nil, nil, nil, nil, "No controls or tests were defined.")
812
+ else
813
+ @logger.info("Found #{count} controls.")
814
+ end
815
+
816
+ # iterate over hash of groups
817
+ params[:controls].each do |id, control|
818
+ sfile = control[:source_location][:ref]
819
+ sline = control[:source_location][:line]
820
+ error.call(sfile, sline, nil, id, "Avoid controls with empty IDs") if id.nil? || id.empty?
821
+ next if id.start_with? "(generated "
822
+
823
+ warn.call(sfile, sline, nil, id, "Control #{id} has no title") if control[:title].to_s.empty?
824
+ warn.call(sfile, sline, nil, id, "Control #{id} has no descriptions") if control[:descriptions][:default].to_s.empty?
825
+ warn.call(sfile, sline, nil, id, "Control #{id} has impact > 1.0") if control[:impact].to_f > 1.0
826
+ warn.call(sfile, sline, nil, id, "Control #{id} has impact < 0.0") if control[:impact].to_f < 0.0
827
+ warn.call(sfile, sline, nil, id, "Control #{id} has no tests defined") if control[:checks].nil? || control[:checks].empty?
828
+ end
829
+
830
+ # Running cookstyle to check for code offenses
831
+ if @check_cookstyle
832
+ cookstyle_linting_check.each do |lint_output|
833
+ data = lint_output.split(":")
834
+ msg = "#{data[-2]}:#{data[-1]}"
835
+ offense.call(data[0], data[1], data[2], nil, msg)
836
+ end
837
+ end
838
+ # profile is valid if we could not find any error & offenses
839
+ result[:summary][:valid] = result[:errors].empty? && result[:offenses].empty?
840
+
841
+ @logger.info "Control definitions OK." if result[:warnings].empty?
842
+ result
843
+ end
844
+
556
845
  # Check if the profile is internally well-structured. The logger will be
557
846
  # used to print information on errors and warnings which are found.
558
847
  #
@@ -572,6 +861,9 @@ module Inspec
572
861
  offenses: [],
573
862
  }
574
863
 
864
+ # memoize `info_from_parse` with tests
865
+ info_from_parse(include_tests: true)
866
+
575
867
  entry = lambda { |file, line, column, control, msg|
576
868
  {
577
869
  file: file,
@@ -600,7 +892,7 @@ module Inspec
600
892
  meta_path = @source_reader.target.abs_path(@source_reader.metadata.ref)
601
893
 
602
894
  # verify metadata
603
- m_errors, m_warnings = metadata.valid
895
+ m_errors, m_warnings = validity_check
604
896
  m_errors.each { |msg| error.call(meta_path, 0, 0, nil, msg) }
605
897
  m_warnings.each { |msg| warn.call(meta_path, 0, 0, nil, msg) }
606
898
  m_unsupported = metadata.unsupported
@@ -634,9 +926,9 @@ module Inspec
634
926
  end
635
927
 
636
928
  # extract profile name
637
- result[:summary][:profile] = metadata.params[:name]
929
+ result[:summary][:profile] = info_from_parse[:name]
638
930
 
639
- count = controls_count
931
+ count = info_from_parse[:controls].count
640
932
  result[:summary][:controls] = count
641
933
  if count == 0
642
934
  warn.call(nil, nil, nil, nil, "No controls or tests were defined.")
@@ -645,9 +937,10 @@ module Inspec
645
937
  end
646
938
 
647
939
  # iterate over hash of groups
648
- params[:controls].each do |id, control|
940
+ info_from_parse[:controls].each do |control|
649
941
  sfile = control[:source_location][:ref]
650
942
  sline = control[:source_location][:line]
943
+ id = control[:id]
651
944
  error.call(sfile, sline, nil, id, "Avoid controls with empty IDs") if id.nil? || id.empty?
652
945
  next if id.start_with? "(generated "
653
946
 
@@ -673,8 +966,74 @@ module Inspec
673
966
  result
674
967
  end
675
968
 
676
- def controls_count
677
- params[:controls].values.length
969
+ def validity_check # rubocop:disable Metrics/AbcSize
970
+ errors = []
971
+ warnings = []
972
+ info_from_parse.merge!(metadata.params)
973
+
974
+ %w{name version}.each do |field|
975
+ next unless info_from_parse[field.to_sym].nil?
976
+
977
+ errors.push("Missing profile #{field} in #{metadata.ref}")
978
+ end
979
+
980
+ if %r{[\/\\]} =~ info_from_parse[:name]
981
+ errors.push("The profile name (#{info_from_parse[:name]}) contains a slash" \
982
+ " which is not permitted. Please remove all slashes from `inspec.yml`.")
983
+ end
984
+
985
+ # if version is set, ensure it is correct
986
+ if !info_from_parse[:version].nil? && !metadata.valid_version?(info_from_parse[:version])
987
+ errors.push("Version needs to be in SemVer format")
988
+ end
989
+
990
+ if info_from_parse[:entitlement_id] && info_from_parse[:entitlement_id].strip.empty?
991
+ errors.push("Entitlement ID should not be blank.")
992
+ end
993
+
994
+ unless metadata.supports_runtime?
995
+ warnings.push("The current inspec version #{Inspec::VERSION} cannot satisfy profile inspec_version constraint #{info_from_parse[:inspec_version]}")
996
+ end
997
+
998
+ %w{title summary maintainer copyright license}.each do |field|
999
+ next unless info_from_parse[field.to_sym].nil?
1000
+
1001
+ warnings.push("Missing profile #{field} in #{metadata.ref}")
1002
+ end
1003
+
1004
+ # if license is set, ensure it is in SPDX format or marked as proprietary
1005
+ if !info_from_parse[:license].nil? && !metadata.valid_license?(info_from_parse[:license])
1006
+ warnings.push("License '#{info_from_parse[:license]}' needs to be in SPDX format or marked as 'Proprietary'. See https://spdx.org/licenses/.")
1007
+ end
1008
+
1009
+ # If gem_dependencies is set, it must be an array of hashes with keys name and optional version
1010
+ unless info_from_parse[:gem_dependencies].nil?
1011
+ list = info_from_parse[:gem_dependencies]
1012
+ if list.is_a?(Array) && list.all? { |e| e.is_a? Hash }
1013
+ list.each do |entry|
1014
+ errors.push("gem_dependencies entries must all have a 'name' field") unless entry.key?(:name)
1015
+ if entry[:version]
1016
+ orig = entry[:version]
1017
+ begin
1018
+ # Split on commas as we may have a complex dep
1019
+ orig.split(",").map { |c| Gem::Requirement.parse(c) }
1020
+ rescue Gem::Requirement::BadRequirementError
1021
+ errors.push "Unparseable gem dependency '#{orig}' for #{entry[:name]}"
1022
+ rescue Inspec::GemDependencyInstallError => e
1023
+ errors.push e.message
1024
+ end
1025
+ end
1026
+ extra = (entry.keys - %i{name version})
1027
+ unless extra.empty?
1028
+ warnings.push "Unknown gem_dependencies key(s) #{extra.join(",")} seen for entry '#{entry[:name]}'"
1029
+ end
1030
+ end
1031
+ else
1032
+ errors.push("gem_dependencies must be a List of Hashes")
1033
+ end
1034
+ end
1035
+
1036
+ [errors, warnings]
678
1037
  end
679
1038
 
680
1039
  def set_status_message(msg)
@@ -698,9 +1057,11 @@ module Inspec
698
1057
  # TODO ignore all .files, but add the files to debug output
699
1058
 
700
1059
  # Generate temporary inspec.json for archive
701
- if opts[:export]
1060
+ export_opt_enabled = opts[:export] || opts[:legacy_export]
1061
+ if export_opt_enabled
1062
+ info_for_profile_summary = opts[:legacy_export] ? info : info_from_parse
702
1063
  Inspec::Utils::JsonProfileSummary.produce_json(
703
- info: info, # TODO: conditionalize and call info_from_parse
1064
+ info: info_for_profile_summary,
704
1065
  write_path: "#{root_path}inspec.json",
705
1066
  suppress_output: true
706
1067
  )
@@ -709,9 +1070,9 @@ module Inspec
709
1070
  # display all files that will be part of the archive
710
1071
  @logger.debug "Add the following files to archive:"
711
1072
  files.each { |f| @logger.debug " " + f }
712
- @logger.debug " inspec.json" if opts[:export]
1073
+ @logger.debug " inspec.json" if export_opt_enabled
713
1074
 
714
- archive_files = opts[:export] ? files.push("inspec.json") : files
1075
+ archive_files = export_opt_enabled ? files.push("inspec.json") : files
715
1076
  if opts[:zip]
716
1077
  # generate zip archive
717
1078
  require "inspec/archive/zip"
@@ -725,7 +1086,7 @@ module Inspec
725
1086
  end
726
1087
 
727
1088
  # Cleanup
728
- FileUtils.rm_f("#{root_path}inspec.json") if opts[:export]
1089
+ FileUtils.rm_f("#{root_path}inspec.json") if export_opt_enabled
729
1090
 
730
1091
  @logger.info "Finished archive generation."
731
1092
  true
@@ -317,7 +317,7 @@ module Inspec::Reporters
317
317
  not_applicable = 0
318
318
 
319
319
  all_unique_controls.each do |control|
320
- next if control[:status].empty?
320
+ next if control[:status].blank?
321
321
 
322
322
  if control[:status] == "failed"
323
323
  failed += 1
@@ -4,76 +4,89 @@ require "inspec/reporters/json"
4
4
  require "inspec/reporters/json_automate"
5
5
  require "inspec/reporters/automate"
6
6
  require "inspec/reporters/yaml"
7
+ require "inspec/feature"
7
8
 
8
9
  module Inspec::Reporters
9
10
  # rubocop:disable Metrics/CyclomaticComplexity
10
11
  def self.render(reporter, run_data, enhanced_outcomes = false)
11
12
  name, config = reporter.dup
12
- config[:run_data] = run_data
13
- case name
14
- when "cli"
15
- reporter = Inspec::Reporters::CLI.new(config)
16
- when "json"
17
- reporter = Inspec::Reporters::Json.new(config)
18
- # This reporter is only used for Chef internal. We reserve the
19
- # right to introduce breaking changes to this reporter at any time.
20
- when "json-automate"
21
- reporter = Inspec::Reporters::JsonAutomate.new(config)
22
- when "automate"
23
- reporter = Inspec::Reporters::Automate.new(config)
24
- when "yaml"
25
- reporter = Inspec::Reporters::Yaml.new(config)
26
- else
27
- # If we made it here, it must be a plugin, and we know it exists (because we validated it in config.rb)
28
- activator = Inspec::Plugin::V2::Registry.instance.find_activator(plugin_type: :reporter, activator_name: name.to_sym)
29
- activator.activate!
30
- reporter = activator.implementation_class.new(config)
31
- end
32
- reporter.enhanced_outcomes = enhanced_outcomes
13
+ Inspec.with_feature("inspec-reporter-#{name}") {
14
+ config[:run_data] = run_data
15
+ case name
16
+ when "cli"
17
+ reporter = Inspec::Reporters::CLI.new(config)
18
+ when "json"
19
+ reporter = Inspec::Reporters::Json.new(config)
20
+ # This reporter is only used for Chef internal. We reserve the
21
+ # right to introduce breaking changes to this reporter at any time.
22
+ when "json-automate"
23
+ reporter = Inspec::Reporters::JsonAutomate.new(config)
24
+ when "automate"
25
+ reporter = Inspec::Reporters::Automate.new(config)
26
+ when "yaml"
27
+ reporter = Inspec::Reporters::Yaml.new(config)
28
+ else
29
+ # If we made it here, it must be a plugin, and we know it exists (because we validated it in config.rb)
30
+ activator = Inspec::Plugin::V2::Registry.instance.find_activator(plugin_type: :reporter, activator_name: name.to_sym)
31
+ activator.activate!
32
+ reporter = activator.implementation_class.new(config)
33
+ end
33
34
 
34
- # optional send_report method on reporter
35
- return reporter.send_report if defined?(reporter.send_report)
35
+ if enhanced_outcomes
36
+ Inspec.with_feature("inspec-enhanced-outcomes") {
37
+ reporter.enhanced_outcomes = enhanced_outcomes
38
+ }
39
+ else
40
+ reporter.enhanced_outcomes = enhanced_outcomes
41
+ end
36
42
 
37
- reporter.render
38
- output = reporter.rendered_output
43
+ # optional send_report method on reporter
44
+ return reporter.send_report if defined?(reporter.send_report)
39
45
 
40
- if config["file"]
41
- # create destination directory if it does not exist
42
- dirname = File.dirname(config["file"])
43
- FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
46
+ reporter.render
47
+ output = reporter.rendered_output
48
+ config_file = config["file"]
49
+ if config_file
50
+ config_file.gsub!("CHILD_PID", Process.pid.to_s)
51
+ # create destination directory if it does not exist
52
+ dirname = File.dirname(config_file)
53
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
44
54
 
45
- File.write(config["file"], output)
46
- elsif config["stdout"] == true
47
- print output
48
- $stdout.flush
49
- end
55
+ File.write(config_file, output)
56
+ elsif config["stdout"] == true
57
+ print output
58
+ $stdout.flush
59
+ end
60
+ }
50
61
  end
51
62
 
52
63
  def self.report(reporter, run_data)
53
64
  name, config = reporter.dup
54
- config[:run_data] = run_data
55
- case name
56
- when "json"
57
- reporter = Inspec::Reporters::Json.new(config)
58
- when "json-automate"
59
- reporter = Inspec::Reporters::JsonAutomate.new(config)
60
- when "yaml"
61
- reporter = Inspec::Reporters::Yaml.new(config)
62
- else
63
- # If we made it here, it might be a plugin
64
- begin
65
- activator = Inspec::Plugin::V2::Registry.instance.find_activator(plugin_type: :reporter, activator_name: name.to_sym)
66
- activator.activate!
67
- reporter = activator.implementation_class.new(config)
68
- unless reporter.respond_to(:report?)
65
+ Inspec.with_feature("inspec-reporter-#{name}") {
66
+ config[:run_data] = run_data
67
+ case name
68
+ when "json"
69
+ reporter = Inspec::Reporters::Json.new(config)
70
+ when "json-automate"
71
+ reporter = Inspec::Reporters::JsonAutomate.new(config)
72
+ when "yaml"
73
+ reporter = Inspec::Reporters::Yaml.new(config)
74
+ else
75
+ # If we made it here, it might be a plugin
76
+ begin
77
+ activator = Inspec::Plugin::V2::Registry.instance.find_activator(plugin_type: :reporter, activator_name: name.to_sym)
78
+ activator.activate!
79
+ reporter = activator.implementation_class.new(config)
80
+ unless reporter.respond_to(:report?)
81
+ return run_data
82
+ end
83
+ rescue Inspec::Plugin::V2::LoadError
84
+ # Must not have been a plugin - just return the run_data
69
85
  return run_data
70
86
  end
71
- rescue Inspec::Plugin::V2::LoadError
72
- # Must not have been a plugin - just return the run_data
73
- return run_data
74
87
  end
75
- end
76
88
 
77
- reporter.report
89
+ reporter.report
90
+ }
78
91
  end
79
92
  end
@@ -169,9 +169,14 @@ module Inspec::Resources
169
169
  # special handling for string values with "
170
170
  elsif !(m = /^\"(.*)\"$/.match(val)).nil?
171
171
  m[1]
172
+ # We get some values of Registry Path as MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Setup\\RecoveryConsole\\SecurityLevel=4,0
173
+ # which we are not going to split as there are chances that it will break if anyone is using string comparison.
174
+ # In some cases privilege value which does not have corresponding SID it returns the values in comma seprated which breakes it for some of
175
+ # the privileges like SeServiceLogonRight as it returns array if previlege values are SID
176
+ elsif !key.include?("\\") && val.match(/,/)
177
+ val.split(",")
172
178
  else
173
- # When there is Registry Values we are not spliting the value for backward compatibility
174
- key.include?("\\") ? val : val.split(",")
179
+ val
175
180
  end
176
181
  end
177
182
 
@@ -27,11 +27,13 @@ module Inspec
27
27
  ) do
28
28
  include HashLikeStruct
29
29
  def initialize(raw_run_data)
30
- self.controls = raw_run_data[:controls].map { |c| Inspec::RunData::Control.new(c) }
31
- self.profiles = raw_run_data[:profiles].map { |p| Inspec::RunData::Profile.new(p) }
32
- self.statistics = Inspec::RunData::Statistics.new(raw_run_data[:statistics])
33
- self.platform = Inspec::RunData::Platform.new(raw_run_data[:platform])
34
- self.version = raw_run_data[:version]
30
+ @raw_run_data = raw_run_data
31
+
32
+ self.controls = @raw_run_data[:controls].map { |c| Inspec::RunData::Control.new(c) }
33
+ self.profiles = @raw_run_data[:profiles].map { |p| Inspec::RunData::Profile.new(p) }
34
+ self.statistics = Inspec::RunData::Statistics.new(@raw_run_data[:statistics])
35
+ self.platform = Inspec::RunData::Platform.new(@raw_run_data[:platform])
36
+ self.version = @raw_run_data[:version]
35
37
  end
36
38
  end
37
39