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.
- checksums.yaml +4 -4
- data/Chef-EULA +9 -0
- data/Gemfile +10 -1
- data/etc/features.sig +6 -0
- data/etc/features.yaml +94 -0
- data/inspec-core.gemspec +14 -5
- data/lib/inspec/backend.rb +2 -0
- data/lib/inspec/base_cli.rb +80 -4
- data/lib/inspec/cached_fetcher.rb +24 -3
- data/lib/inspec/cli.rb +300 -230
- data/lib/inspec/config.rb +24 -2
- data/lib/inspec/dependencies/cache.rb +33 -0
- data/lib/inspec/enhanced_outcomes.rb +1 -0
- data/lib/inspec/errors.rb +5 -0
- data/lib/inspec/exceptions.rb +2 -0
- data/lib/inspec/feature/config.rb +75 -0
- data/lib/inspec/feature/runner.rb +26 -0
- data/lib/inspec/feature.rb +34 -0
- data/lib/inspec/fetcher/git.rb +5 -0
- data/lib/inspec/globals.rb +6 -0
- data/lib/inspec/plugin/v1/plugin_types/fetcher.rb +7 -0
- data/lib/inspec/plugin/v2/plugin_types/streaming_reporter.rb +30 -2
- data/lib/inspec/profile.rb +373 -12
- data/lib/inspec/reporters/cli.rb +1 -1
- data/lib/inspec/reporters.rb +67 -54
- data/lib/inspec/resources/security_policy.rb +7 -2
- data/lib/inspec/run_data.rb +7 -5
- data/lib/inspec/runner.rb +34 -5
- data/lib/inspec/runner_rspec.rb +12 -9
- data/lib/inspec/secrets/yaml.rb +9 -3
- data/lib/inspec/shell.rb +10 -0
- data/lib/inspec/ui.rb +4 -0
- data/lib/inspec/utils/licensing_config.rb +9 -0
- data/lib/inspec/utils/profile_ast_helpers.rb +372 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/inspec/waiver_file_reader.rb +68 -27
- data/lib/inspec.rb +2 -1
- data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +189 -168
- data/lib/plugins/inspec-habitat/lib/inspec-habitat/cli.rb +10 -3
- data/lib/plugins/inspec-init/lib/inspec-init/cli.rb +1 -0
- data/lib/plugins/inspec-init/lib/inspec-init/cli_plugin.rb +23 -21
- data/lib/plugins/inspec-init/lib/inspec-init/cli_profile.rb +15 -13
- data/lib/plugins/inspec-init/lib/inspec-init/cli_resource.rb +15 -13
- data/lib/plugins/inspec-license/README.md +16 -0
- data/lib/plugins/inspec-license/inspec-license.gemspec +6 -0
- data/lib/plugins/inspec-license/lib/inspec-license/cli.rb +26 -0
- data/lib/plugins/inspec-license/lib/inspec-license.rb +14 -0
- data/lib/plugins/inspec-parallel/README.md +27 -0
- data/lib/plugins/inspec-parallel/inspec-parallel.gemspec +6 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/child_status_reporter.rb +61 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/cli.rb +39 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/command.rb +219 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/runner.rb +265 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/base.rb +24 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/silent.rb +7 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/status.rb +124 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/super_reporter/text.rb +23 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel/validator.rb +170 -0
- data/lib/plugins/inspec-parallel/lib/inspec-parallel.rb +18 -0
- data/lib/plugins/inspec-reporter-html2/templates/control.html.erb +7 -6
- data/lib/plugins/inspec-reporter-html2/templates/default.js +6 -6
- data/lib/plugins/inspec-sign/lib/inspec-sign/base.rb +6 -2
- data/lib/plugins/inspec-sign/lib/inspec-sign/cli.rb +11 -4
- data/lib/plugins/inspec-streaming-reporter-progress-bar/lib/inspec-streaming-reporter-progress-bar/streaming_reporter.rb +6 -13
- metadata +54 -13
data/lib/inspec/profile.rb
CHANGED
@@ -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 =
|
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] =
|
929
|
+
result[:summary][:profile] = info_from_parse[:name]
|
638
930
|
|
639
|
-
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
|
-
|
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
|
677
|
-
|
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
|
-
|
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:
|
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
|
1073
|
+
@logger.debug " inspec.json" if export_opt_enabled
|
713
1074
|
|
714
|
-
archive_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
|
1089
|
+
FileUtils.rm_f("#{root_path}inspec.json") if export_opt_enabled
|
729
1090
|
|
730
1091
|
@logger.info "Finished archive generation."
|
731
1092
|
true
|
data/lib/inspec/reporters/cli.rb
CHANGED
data/lib/inspec/reporters.rb
CHANGED
@@ -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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
35
|
-
|
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
|
-
|
38
|
-
|
43
|
+
# optional send_report method on reporter
|
44
|
+
return reporter.send_report if defined?(reporter.send_report)
|
39
45
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
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
|
-
|
174
|
-
key.include?("\\") ? val : val.split(",")
|
179
|
+
val
|
175
180
|
end
|
176
181
|
end
|
177
182
|
|
data/lib/inspec/run_data.rb
CHANGED
@@ -27,11 +27,13 @@ module Inspec
|
|
27
27
|
) do
|
28
28
|
include HashLikeStruct
|
29
29
|
def initialize(raw_run_data)
|
30
|
-
|
31
|
-
|
32
|
-
self.
|
33
|
-
self.
|
34
|
-
self.
|
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
|
|