Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added ability to compare new benchmarks to old profiles still using deprecated IDs (V-XXXXXX) #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion lib/inspec_delta/commands/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,30 @@ class Profile < Thor
aliases: %w[-s --st],
desc: 'The path to the stig file to apply to the profile.',
required: true
method_option :rule_id,
aliases: '-r',
desc: 'Sets inspec_delta to use STIG Rule IDs (SV-XXXXXX) as the primary key for comparison between the benchmark and the profile. Set to false to use the Vuln ID (V-XXXXXX) as the comparator.',
type: :boolean,
default: true

def update
prof = InspecDelta::Object::Profile.new(options[:profile_path])
prof.update(options[:stig_file_path])
prof.update(options[:stig_file_path], options[:rule_id])
prof.format
end

desc 'update_id', 'Relabel the controls in the profile with the updated IDs from the benchmark. Run this first if the profile uses the old-stlye V-XXXXXX IDs and you want to rename the files with the right IDs in a separate commit.'
method_option :profile_path,
aliases: %w[-p --pr],
desc: 'The path to the directory that contains the profile to modify.',
required: true
method_option :stig_file_path,
aliases: %w[-s --st],
desc: 'The path to the stig file to apply to the profile.',
required: true
def update_id
prof = InspecDelta::Object::Profile.new(options[:profile_path])
prof.update_id(options[:stig_file_path])
prof.format
end
end
Expand Down
59 changes: 46 additions & 13 deletions lib/inspec_delta/objects/control.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,28 @@ def initialize
#
# @return [control_string] String updated string with the changes from other
def apply_updates(other)
apply_updates_id(other.id)
apply_updates_title(other.title)
apply_updates_desc(other.descriptions[:default])
apply_updates_impact(other.impact)
apply_updates_tags(other)
@control_string
return {
:control_string => @control_string,
:id => other.id
}
end

# Updates a string representation of a Control's ID with string substitutions
# Only updates the ID if the Other ID does not match the one in the Control
#
# @param [String] title - The title to be applied to the Control
def apply_updates_id(id)
wrap_length = MAX_LINE_LENGTH - WORD_WRAP_INDENT

@control_string.sub!(
/(control\s+)(\S+)(\s+do)/,
"control \'#{id}\' do".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
)
end

# Updates a string representation of a Control's title with string substitutions
Expand All @@ -62,7 +79,6 @@ def apply_updates_desc(desc)
return if desc.to_s.empty?

wrap_length = MAX_LINE_LENGTH - WORD_WRAP_INDENT

@control_string.sub!(
/desc\s+(((").*?(?<!\\)")|((').*?(?<!\\)')|((%q{).*?(?<!\\)[}])|(nil))\n/m,
"desc %q{#{desc}}".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
Expand All @@ -82,8 +98,12 @@ def apply_updates_impact(impact)
#
# @param [Control] other - The Control to be merged in
def apply_updates_tags(other)

# we might want to add tags in from the new benchmark that
# did not appear in the old profile, such as legacy tags
expanded_tags = @tags.append(other.tags.find{ |x| x.key == 'legacy' })
other.tags.each do |ot|
tag = @tags.detect { |t| t.key == ot.key }
tag = expanded_tags.detect { |t| t.key == ot.key }
next unless tag

if ot.value.instance_of?(String)
Expand All @@ -100,18 +120,21 @@ def apply_updates_tags(other)
#
# @param [Tag] ot - The Tag to be merged in
def apply_updates_tags_string(tag)
if tag.value.empty?
@control_string.sub!(
/tag\s+['"]?#{tag.key}['"]?:\s+(((").*?(?<!\\)")|((').*?(?<!\\)')|((%q{).*?(?<!\\)[}])|(nil))\n/m,
"tag '#{tag.key}': nil\n"
)
else
wrap_length = MAX_LINE_LENGTH - WORD_WRAP_INDENT
wrap_length = MAX_LINE_LENGTH - WORD_WRAP_INDENT
updated_control_string = @control_string.sub(
/tag\s+['"]?#{tag.key}['"]?:\s+(((").*?(?<!\\)")|((').*?(?<!\\)')|((%q{).*?(?<!\\)[}])|(nil))\n/m,
"tag '#{tag.key}': %q{#{tag.value ? tag.value : 'nil'}}".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
)

if updated_control_string == @control_string
# the sub call failed -- this tag isn't in the profile already and must be added
@control_string.sub!(
/tag\s+['"]?#{tag.key}['"]?:\s+(((").*?(?<!\\)")|((').*?(?<!\\)')|((%q{).*?(?<!\\)[}])|(nil))\n/m,
/(?=tag\s+[\'\"])/m,
"tag '#{tag.key}': %q{#{tag.value}}".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
)

else
@control_string = updated_control_string
end
end

Expand All @@ -120,11 +143,19 @@ def apply_updates_tags_string(tag)
# @param [Tag] ot - The Tag to be merged in
def apply_updates_tags_array(tag)
wrap_length = MAX_LINE_LENGTH - WORD_WRAP_INDENT

@control_string.sub!(
updated_control_string = @control_string.sub(
/tag\s+['"]?#{tag.key}['"]?:\s+(((%w\().*?(?<!\\)(\)))|((\[).*?(?<!\\)(\]))|(nil))\n/m,
"tag '#{tag.key}': #{tag.value}".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
)
if updated_control_string == @control_string
# the sub call failed -- this tag isn't in the profile already and must be added
@control_string.sub!(
/(?=tag\s+[\'\"])/m,
"tag '#{tag.key}': #{tag.value}".word_wrap(wrap_length).indent(WORD_WRAP_INDENT)
)
else
@control_string = updated_control_string
end
end

# Updates a string representation of a Control's tags with string substitutions
Expand All @@ -150,6 +181,7 @@ def self.benchmark_tags
'stig_id' => :stig_id,
'fix_id' => :fix_id,
'cci' => :cci,
'legacy' => :legacy,
'false_negatives' => :false_negatives,
'false_positives' => :false_positives,
'documentable' => :documentable,
Expand Down Expand Up @@ -203,6 +235,7 @@ def self.from_ruby(ruby_control_path)
# @return [string] severity if no match is found
def self.impact(severity)
{
'none' => 0.0,
'low' => 0.3,
'medium' => 0.5,
'high' => 0.7
Expand Down
46 changes: 42 additions & 4 deletions lib/inspec_delta/objects/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ def format
#
# @param [profile_path] String - path to the inspec profile's root directory.
# @param [stig_file_path] String - The STIG file to be applied to profile.
def update(stig_file_path)
def update(stig_file_path, rule_id)
raise StandardError, "STIG file at #{stig_file_path} not found" unless File.exist?(stig_file_path)

control_dir = "#{@profile_path}/controls"
benchmark = InspecDelta::Parser::Benchmark.get_benchmark(stig_file_path)
benchmark.each do |control_id, control|
benchmark_control = InspecDelta::Object::Control.from_benchmark(control)
profile_control_path = File.join(File.expand_path(control_dir), "#{control_id}.rb")
control_filename = (rule_id || control[:legacy].nil? || control[:legacy].empty?) ? "#{control_id}.rb" :
"#{control[:legacy].select{ |x| x.start_with? ('V-') }.first}.rb"
profile_control_path = File.join(File.expand_path(control_dir), control_filename)
if File.file?(profile_control_path)
update_existing_control_file(profile_control_path, benchmark_control)
else
Expand All @@ -47,14 +48,51 @@ def update(stig_file_path)
end
end

# Updates ONLY the filenames of the profile's controls metadata with definitions from a STIG xml file
#
# @param [profile_path] String - path to the inspec profile's root directory.
# @param [stig_file_path] String - The STIG file to be applied to profile.
def update_id(stig_file_path)
raise StandardError, "STIG file at #{stig_file_path} not found" unless File.exist?(stig_file_path)
control_dir = "#{@profile_path}/controls"
benchmark = InspecDelta::Parser::Benchmark.get_benchmark(stig_file_path)
benchmark.each do |control_id, control| unless control[:legacy].nil? || control[:legacy].empty?
benchmark_control = InspecDelta::Object::Control.from_benchmark(control)
control_filename = "#{control[:legacy].select{ |x| x.start_with? ('V-') }.first}.rb"
profile_control_path = File.join(File.expand_path(control_dir), control_filename)
#require 'pry'; binding.pry
if File.file?(profile_control_path)
puts "Updating \"#{control_filename}\" ==> \"#{control[:id]}.rb\""
updated_path = profile_control_path.sub(
/[^\/\\]+.rb/,
control[:id] + '.rb'
)
system("cd #{@profile_path} && git mv #{profile_control_path} #{updated_path}")
#require 'pry'; binding.pry
end
end
end
puts "Done updating."
end


# Updates a control file with the updates from the stig
#
# @param [profile_control_path] String - The location of the Inspec profile on disk
# @param [benchmark_control] Control - Control built from the Inspec Benchmark
def update_existing_control_file(profile_control_path, benchmark_control)
profile_control = InspecDelta::Object::Control.from_ruby(profile_control_path)
updated_control = profile_control.apply_updates(benchmark_control)
File.open(profile_control_path, 'w') { |f| f.puts updated_control }
updated_path = profile_control_path.sub(
/[^\/\\]+.rb/,
updated_control[:id] + '.rb'
)
if updated_path != profile_control_path
#require 'pry'; binding.pry
system("cd #{@profile_path} && git mv #{profile_control_path} #{updated_path}")
profile_control_path = updated_path
end
File.open(profile_control_path, 'w') { |f| f.puts updated_control[:control_string] }
end

# Creates a control file with the string representation of the benchmark control
Expand Down
5 changes: 3 additions & 2 deletions lib/inspec_delta/parsers/benchmark.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ def self.get_benchmark(file)

g[:stig_title] = benchmark_title

g[:id] = b.id
g[:gtitle] = b.title
g[:description] = b.description
g[:gid] = b.id

rule = b.rule
g[:id] = rule.id.scan(/SV-[\d]+/).first
g[:rid] = rule.id
g[:severity] = rule.severity
g[:stig_id] = rule.version
Expand Down Expand Up @@ -62,14 +62,15 @@ def self.get_benchmark(file)
g[:dc_type] = reference_group.dc_type

g[:cci] = rule.idents.select { |t| t.start_with?('CCI-') } # XCCDFReader
g[:legacy] = rule.idents.select { |t| !t.start_with?('CCI-') }

g[:fix] = rule.fixtext
g[:fix_id] = rule.fix.id

g[:check] = rule.check.content
g[:check_ref_name] = rule.check.content_ref.name
g[:check_ref] = rule.check.content_ref.href
[b.id, g]
[g[:id], g]
end
mapped_benchmark_group.to_h
end
Expand Down