# encoding: utf-8 module HappyMapperTools module StigAttributes require 'happymapper' require 'nokogiri' require 'colorize' class ContentRef include HappyMapper tag 'check-content-ref' attribute :name, String, tag: 'name' attribute :href, String, tag: 'href' end class Check include HappyMapper tag 'check' element :content_ref, ContentRef, tag: 'check-content-ref' element :content, String, tag: 'check-content' end class Fix include HappyMapper tag 'fix' attribute :id, String, tag: 'id' end class DescriptionDetails include HappyMapper tag 'Details' element :vuln_discussion, String, tag: 'VulnDiscussion' element :false_positives, String, tag: 'FalsePositives' element :false_negatives, String, tag: 'FalseNegatives' element :documentable, Boolean, tag: 'Documentable' element :mitigations, String, tag: 'Mitigations' element :severity_override_guidance, String, tag: 'SeverityOverrideGuidance' element :potential_impacts, String, tag: 'PotentialImpacts' element :third_party_tools, String, tag: 'ThirdPartyTools' element :mitigation_controls, String, tag: 'MitigationControl' element :responsibility, String, tag: 'Responsibility' element :ia_controls, String, tag: 'IAControls' end class Description include HappyMapper tag 'description' content :details, DescriptionDetails detail_tags = %i(vuln_discussion false_positives false_negatives documentable mitigations severity_override_guidance potential_impacts third_party_tools mitigation_controls responsibility ia_controls) detail_tags.each do |name| define_method name do details.send(name) end end end class ReferenceInfo include HappyMapper tag 'reference' attribute :href, String, tag: 'href' element :dc_publisher, String, tag: 'publisher', namespace: 'dc' element :dc_source, String, tag: 'source', namespace: 'dc' element :dc_title, String, tag: 'title', namespace: 'dc' element :dc_type, String, tag: 'type', namespace: 'dc' element :dc_subject, String, tag: 'subject', namespace: 'dc' element :dc_identifier, String, tag: 'identifier', namespace: 'dc' end class Rule include HappyMapper tag 'Rule' attribute :id, String, tag: 'id' attribute :severity, String, tag: 'severity' element :version, String, tag: 'version' element :title, String, tag: 'title' has_one :description, Description, tag: 'description' element :reference, ReferenceInfo, tag: 'reference' has_many :idents, String, tag: 'ident' element :fixtext, String, tag: 'fixtext' has_one :fix, Fix, tag: 'fix' has_one :check, Check, tag: 'check' end class Group include HappyMapper tag 'Group' attribute :id, String, tag: 'id' element :title, String, tag: 'title' element :description, String, tag: 'description' has_one :rule, Rule, tag: 'Rule' end class ReleaseDate include HappyMapper tag 'status' attribute :release_date, String, tag: 'date' end class Notice include HappyMapper tag 'notice' attribute :id, String, tag: 'id' attribute :xml_lang, String, namespace: 'xml', tag: 'lang' content :notice, String, tag: 'notice' end class Plaintext include HappyMapper tag 'plain-text' attribute :id, String, tag: 'id' content :plaintext, String end class Benchmark include HappyMapper tag 'Benchmark' has_one :release_date, ReleaseDate, tag: 'status' attribute :id, String, tag: 'id' element :status, String, tag: 'status' element :title, String, tag: 'title' element :description, String, tag: 'description' element :version, String, tag: 'version' element :notice, Notice, tag: 'notice' has_one :reference, ReferenceInfo, tag: 'reference' element :plaintext, Plaintext, tag: 'plain-text' has_many :group, Group, tag: 'Group' end class DescriptionDetailsType class << self def type DescriptionDetails end def apply(value) value = value.gsub('&', 'and') DescriptionDetails.parse "
#{value}
" rescue Nokogiri::XML::SyntaxError => e if e.to_s.include?('StartTag') report_invalid_start_tag(value, e) else report_disallowed_tags(value) end end def apply?(value, _convert_to_type) value.is_a?(String) end private def report_invalid_start_tag(value, error) puts error.to_s.colorize(:red) column = error.column - '
'.length - 2 puts "Error around #{value[column-10..column+10].colorize(:light_yellow)}" exit(1) end def report_disallowed_tags(value) allowed_tags = %w{VulnDiscussion FalsePositives FalseNegatives Documentable Mitigations SeverityOverrideGuidance PotentialImpacts PotentialImpacts ThirdPartyTools MitigationControl Responsibility IAControl SecurityOverrideGuidance} tags_found = value.scan(%r{(?<=<)([^\/]*?)((?= \/>)|(?=>))}).to_a tags_found = tags_found.uniq.flatten.reject!(&:empty?) offending_tags = tags_found - allowed_tags if offending_tags.count > 1 puts "\n\nThe non-standard tags: #{offending_tags.to_s.colorize(:red)}" \ ' were found in: ' + "\n\n#{value}" else puts "\n\nThe non-standard tag: #{offending_tags.to_s.colorize(:red)}" \ ' was found in: ' + "\n\n#{value}" end puts "\n\nPlease:\n " option_one = '(1) ' + '(best)'.colorize(:green) + ' Use the ' + '`-r --replace-tags array` '.colorize(:light_yellow) + '(case sensitive) option to replace the offending tags ' \ 'during processing of the XCCDF ' \ 'file to use the ' + "`$#{offending_tags[0]}` " .colorize(:light_green) + 'syntax in your InSpec profile.' option_two = '(2) Update your XCCDF file to *not use* non-standard XCCDF ' \ 'elements within ' + '`<`,`>`, `<` '.colorize(:red) + 'or '.colorize(:default) + '`>` '.colorize(:red) + 'as "placeholders", and use something that doesn\'t confuse ' \ 'the XML parser, such as : ' + "`$#{offending_tags[0]}`" .colorize(:light_green) puts option_one puts "\n" puts option_two end end HappyMapper::SupportedTypes.register DescriptionDetailsType end end end