inspec-core 5.22.29 → 6.6.0

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