From 1295ca0a0335d8529fc1fcf901548bc008c628e0 Mon Sep 17 00:00:00 2001 From: Will Dower Date: Mon, 31 Jan 2022 23:56:05 -0500 Subject: [PATCH 1/3] added ability to compare new benchmarks to old profiles still using deprecated IDs (V-XXXXXX) Signed-off-by: Will Dower --- lib/inspec_delta/commands/profile.rb | 7 +++- lib/inspec_delta/objects/control.rb | 59 +++++++++++++++++++++------ lib/inspec_delta/objects/profile.rb | 17 ++++++-- lib/inspec_delta/parsers/benchmark.rb | 5 ++- 4 files changed, 68 insertions(+), 20 deletions(-) diff --git a/lib/inspec_delta/commands/profile.rb b/lib/inspec_delta/commands/profile.rb index b14e03d..1305441 100644 --- a/lib/inspec_delta/commands/profile.rb +++ b/lib/inspec_delta/commands/profile.rb @@ -13,9 +13,14 @@ 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 end diff --git a/lib/inspec_delta/objects/control.rb b/lib/inspec_delta/objects/control.rb index a3d9384..ab1ae3e 100644 --- a/lib/inspec_delta/objects/control.rb +++ b/lib/inspec_delta/objects/control.rb @@ -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 @@ -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+(((").*?(? :stig_id, 'fix_id' => :fix_id, 'cci' => :cci, + 'legacy' => :legacy, 'false_negatives' => :false_negatives, 'false_positives' => :false_positives, 'documentable' => :documentable, @@ -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 diff --git a/lib/inspec_delta/objects/profile.rb b/lib/inspec_delta/objects/profile.rb index a76abb0..65bfe5e 100644 --- a/lib/inspec_delta/objects/profile.rb +++ b/lib/inspec_delta/objects/profile.rb @@ -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 @@ -54,7 +55,15 @@ def update(stig_file_path) 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' + ) + + File.open(profile_control_path, 'w') { |f| f.puts updated_control[:control_string] } + if updated_path != profile_control_path + File.rename(profile_control_path, updated_path) + end end # Creates a control file with the string representation of the benchmark control diff --git a/lib/inspec_delta/parsers/benchmark.rb b/lib/inspec_delta/parsers/benchmark.rb index c3e5ef9..1a00cfb 100644 --- a/lib/inspec_delta/parsers/benchmark.rb +++ b/lib/inspec_delta/parsers/benchmark.rb @@ -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 @@ -62,6 +62,7 @@ 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 @@ -69,7 +70,7 @@ def self.get_benchmark(file) 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 From 5f156abe24023f83dfd1e52ee28dec047f2168de Mon Sep 17 00:00:00 2001 From: Will Dower Date: Tue, 1 Feb 2022 14:40:40 -0500 Subject: [PATCH 2/3] added update_id function to allow user to ONLY update the control filenames, so that they can split the filename and file content changes into separate commits Signed-off-by: Will Dower --- lib/inspec_delta/commands/profile.rb | 16 +++++++++++++ lib/inspec_delta/objects/profile.rb | 35 +++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/lib/inspec_delta/commands/profile.rb b/lib/inspec_delta/commands/profile.rb index 1305441..accc4c3 100644 --- a/lib/inspec_delta/commands/profile.rb +++ b/lib/inspec_delta/commands/profile.rb @@ -18,11 +18,27 @@ class Profile < Thor 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], 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 end end diff --git a/lib/inspec_delta/objects/profile.rb b/lib/inspec_delta/objects/profile.rb index 65bfe5e..eb2e476 100644 --- a/lib/inspec_delta/objects/profile.rb +++ b/lib/inspec_delta/objects/profile.rb @@ -48,6 +48,34 @@ def update(stig_file_path, rule_id) 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 + puts "Done updating." + end + end + + # Updates a control file with the updates from the stig # # @param [profile_control_path] String - The location of the Inspec profile on disk @@ -59,11 +87,12 @@ def update_existing_control_file(profile_control_path, benchmark_control) /[^\/\\]+.rb/, updated_control[:id] + '.rb' ) - - File.open(profile_control_path, 'w') { |f| f.puts updated_control[:control_string] } if updated_path != profile_control_path - File.rename(profile_control_path, updated_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 From c40700fd1882f451dea3a8bd6a04fa89f4da9656 Mon Sep 17 00:00:00 2001 From: Will Dower Date: Tue, 1 Feb 2022 17:42:17 -0500 Subject: [PATCH 3/3] added update_id feature to just update the filenames Signed-off-by: Will Dower --- lib/inspec_delta/objects/profile.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/inspec_delta/objects/profile.rb b/lib/inspec_delta/objects/profile.rb index eb2e476..8a35f41 100644 --- a/lib/inspec_delta/objects/profile.rb +++ b/lib/inspec_delta/objects/profile.rb @@ -71,8 +71,8 @@ def update_id(stig_file_path) #require 'pry'; binding.pry end end - puts "Done updating." end + puts "Done updating." end