Merge pull request #5657 from inspec/nm/tags-dependent

Fix --tags filter for dependent profiles
This commit is contained in:
Clinton Wolfe 2021-09-28 00:35:32 -04:00 committed by GitHub
commit 993c9d9225
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 28 deletions

View file

@ -18,6 +18,7 @@ module Inspec
attr_accessor :skip_file attr_accessor :skip_file
attr_accessor :profile_context attr_accessor :profile_context
attr_accessor :resources_dsl attr_accessor :resources_dsl
attr_accessor :conf
def initialize(profile_context, resources_dsl, backend, conf, dependencies, require_loader, skip_only_if_eval) def initialize(profile_context, resources_dsl, backend, conf, dependencies, require_loader, skip_only_if_eval)
@profile_context = profile_context @profile_context = profile_context
@ -189,6 +190,30 @@ module Inspec
@skip_file = true @skip_file = true
end end
# Check if the given control exist in the --tags option
def tag_exist_in_control_tags?(tag_ids)
tag_option_matches_with_list = false
if !tag_ids.empty? && !tag_ids.nil? && profile_tag_config_exist?
tag_option_matches_with_list = !(tag_ids & @conf["profile"].include_tags_list).empty?
unless tag_option_matches_with_list
@conf["profile"].include_tags_list.any? do |inclusion|
# Try to see if the inclusion is a regex, and if it matches
if inclusion.is_a?(Regexp)
tag_ids.each do |id|
tag_option_matches_with_list = (inclusion =~ id)
break if tag_option_matches_with_list
end
end
end
end
end
tag_option_matches_with_list
end
def tags_list_empty?
!@conf.empty? && @conf.key?("profile") && @conf["profile"].include_tags_list.empty? || @conf.empty?
end
private private
def block_location(block, alternate_caller) def block_location(block, alternate_caller)
@ -214,10 +239,6 @@ module Inspec
!@conf.empty? && @conf.key?("profile") && @conf["profile"].include_controls_list.empty? || @conf.empty? !@conf.empty? && @conf.key?("profile") && @conf["profile"].include_controls_list.empty? || @conf.empty?
end end
def tags_list_empty?
!@conf.empty? && @conf.key?("profile") && @conf["profile"].include_tags_list.empty? || @conf.empty?
end
# Check if the given control exist in the --controls option # Check if the given control exist in the --controls option
def control_exist_in_controls_list?(id) def control_exist_in_controls_list?(id)
id_exist_in_list = false id_exist_in_list = false
@ -229,25 +250,5 @@ module Inspec
end end
id_exist_in_list id_exist_in_list
end end
# Check if the given control exist in the --tags option
def tag_exist_in_control_tags?(tag_ids)
tag_option_matches_with_list = false
if !tag_ids.empty? && !tag_ids.nil? && profile_tag_config_exist?
tag_option_matches_with_list = !(tag_ids & @conf["profile"].include_tags_list).empty?
unless tag_option_matches_with_list
@conf["profile"].include_tags_list.any? do |inclusion|
# Try to see if the inclusion is a regex, and if it matches
if inclusion.is_a?(Regexp)
tag_ids.each do |id|
tag_option_matches_with_list = (inclusion =~ id)
break if tag_option_matches_with_list
end
end
end
end
end
tag_option_matches_with_list
end
end end
end end

View file

@ -93,22 +93,29 @@ module Inspec::DSL
context = dep_entry.profile.runner_context context = dep_entry.profile.runner_context
# if we don't want all the rules, then just make 1 pass to get all rule_IDs # if we don't want all the rules, then just make 1 pass to get all rule_IDs
# that we want to keep from the original # that we want to keep from the original
filter_included_controls(context, dep_entry.profile, &block) unless opts[:include_all] filter_included_controls(context, dep_entry.profile, opts, &block) if !opts[:include_all] || !(opts[:conf]["profile"].include_tags_list.empty?)
# interpret the block and skip/modify as required # interpret the block and skip/modify as required
context.load(block) if block_given? context.load(block) if block_given?
bind_context.add_subcontext(context) bind_context.add_subcontext(context)
end end
def self.filter_included_controls(context, profile, &block) def self.filter_included_controls(context, profile, opts, &block)
mock = Inspec::Backend.create(Inspec::Config.mock) mock = Inspec::Backend.create(Inspec::Config.mock)
include_ctx = Inspec::ProfileContext.for_profile(profile, mock) include_ctx = Inspec::ProfileContext.for_profile(profile, mock)
include_ctx.load(block) if block_given? include_ctx.load(block) if block_given?
include_ctx.control_eval_context.conf = opts[:conf]
control_eval_ctx = include_ctx.control_eval_context
# remove all rules that were not registered # remove all rules that were not registered
context.all_rules.each do |r| context.all_rules.each do |r|
id = Inspec::Rule.rule_id(r) id = Inspec::Rule.rule_id(r)
fid = Inspec::Rule.profile_id(r) + "/" + id fid = Inspec::Rule.profile_id(r) + "/" + id
unless include_ctx.rules[id] || include_ctx.rules[fid] if !opts[:include_all] && !(include_ctx.rules[id] || include_ctx.rules[fid])
context.remove_rule(fid) context.remove_rule(fid)
elsif !control_eval_ctx.tags_list_empty?
# filter included controls using --tags
tag_ids = control_eval_ctx.control_tags(r)
context.remove_rule(fid) unless control_eval_ctx.tag_exist_in_control_tags?(tag_ids)
end end
end end
end end

View file

@ -14,12 +14,14 @@ control 'profilea-1' do # A unique ID for this control
impact 0.7 # The criticality, if this control fails. impact 0.7 # The criticality, if this control fails.
title 'Create / directory' # A human-readable title title 'Create / directory' # A human-readable title
desc 'An optional description...' desc 'An optional description...'
tag "tag-profilea1"
describe file('/') do # The actual test describe file('/') do # The actual test
it { should be_directory } it { should be_directory }
end end
end end
control 'profilea-2' do control 'profilea-2' do
tag "tag-profilea2"
describe example_config do describe example_config do
its('version') { should eq('1.0') } its('version') { should eq('1.0') }
end end

View file

@ -8,13 +8,15 @@ control 'profileb-1' do # A unique ID for this control
impact 0.7 # The criticality, if this control fails. impact 0.7 # The criticality, if this control fails.
title 'Create / directory' # A human-readable title title 'Create / directory' # A human-readable title
desc 'An optional description...' desc 'An optional description...'
tag "tag-profileb1"
describe file('/') do # The actual test describe file('/') do # The actual test
it { should be_directory } it { should be_directory }
end end
end end
control 'profileb-2' do control 'profileb-2' do
tag "tag-profileb2"
describe example_config do describe example_config do
its('version') { should eq('2.0') } its('version') { should eq('2.0') }
end end
end end

View file

@ -3,6 +3,7 @@ control 'profilec-1' do # A unique ID for this control
impact 0.7 # The criticality, if this control fails. impact 0.7 # The criticality, if this control fails.
title 'Create /tmp directory' # A human-readable title title 'Create /tmp directory' # A human-readable title
desc 'An optional description...' desc 'An optional description...'
tag 'tag-profilec1'
describe file('/') do # The actual test describe file('/') do # The actual test
it { should be_directory } it { should be_directory }
end end

View file

@ -291,6 +291,53 @@ Test Summary: 0 successful, 0 failures, 0 skipped
assert_exit_code 0, out assert_exit_code 0, out
end end
it "executes only specified controls of included dependent profile by using literal names of tags" do
inspec("exec " + File.join(profile_path, "dependencies", "profile_a") + " --no-create-lockfile --tags tag-profilea1 tag-profilec1")
_(stdout).must_include "✔ profilea-1: Create / directory\n"
_(stdout).must_include "✔ profilec-1: Create /tmp directory\n"
_(stdout).must_include "✔ File / is expected to be directory\n"
_(stdout).wont_include "✔ profilea-2: example_config\n"
_(stdout).must_include "Test Summary: 2 successful, 0 failures, 0 skipped\n"
_(stderr).must_equal ""
assert_exit_code 0, out
end
it "executes only specified controls of included dependent profile by using regex on tags" do
inspec("exec " + File.join(profile_path, "dependencies", "profile_a") + " --no-create-lockfile --tags '/^tag-profilea/'")
_(stdout).must_include "✔ profilea-1: Create / directory\n"
_(stdout).must_include "✔ profilea-2: example_config\n"
_(stdout).wont_include "✔ profilec-1: Create /tmp directory\n"
_(stdout).must_include "Test Summary: 2 successful, 0 failures, 0 skipped\n"
_(stderr).must_equal ""
assert_exit_code 0, out
end
it "executes only specified controls of required dependent profile by using literal names of tags" do
inspec("exec " + File.join(profile_path, "dependencies", "require_controls_test") + " --no-create-lockfile --tags tag-profileb2")
_(stdout).must_include "✔ profileb-2: example_config\n"
_(stdout).must_include "✔ example_config version is expected to eq \"2.0\"\n"
_(stdout).wont_include "✔ profilea-1: Create / directory\n"
_(stdout).wont_include "✔ profilea-2: example_config\n"
_(stdout).must_include "Test Summary: 2 successful, 0 failures, 0 skipped\n"
_(stderr).must_equal ""
assert_exit_code 0, out
end
it "executes only specified controls of required dependent profile by using regex on tags" do
inspec("exec " + File.join(profile_path, "dependencies", "require_controls_test") + " --no-create-lockfile --tags '/^tag-profileb/'")
_(stdout).must_include "✔ profileb-2: example_config\n"
_(stdout).must_include "✔ example_config version is expected to eq \"2.0\"\n"
_(stdout).wont_include "✔ profilea-1: Create / directory\n"
_(stdout).wont_include "✔ profilea-2: example_config\n"
_(stdout).must_include "Test Summary: 2 successful, 0 failures, 0 skipped\n"
_(stderr).must_equal ""
assert_exit_code 0, out
end
it "reports whan a profile cannot be loaded" do it "reports whan a profile cannot be loaded" do
inspec("exec " + File.join(profile_path, "raise_outside_control") + " --no-create-lockfile") inspec("exec " + File.join(profile_path, "raise_outside_control") + " --no-create-lockfile")
_(stdout).must_match(/Profile:[\W]+InSpec Profile \(raise_outside_control\)/) _(stdout).must_match(/Profile:[\W]+InSpec Profile \(raise_outside_control\)/)