|
| 1 | +require 'json' |
| 2 | +require 'csv' |
| 3 | +require 'heimdall_tools/hdf' |
| 4 | +require 'utilities/xml_to_hash' |
| 5 | + |
| 6 | +IMPACT_MAPPING = { |
| 7 | + High: 0.7, |
| 8 | + Medium: 0.5, |
| 9 | + Low: 0.3, |
| 10 | + Informational: 0.0 |
| 11 | +}.freeze |
| 12 | + |
| 13 | +# rubocop:disable Metrics/AbcSize |
| 14 | + |
| 15 | +module HeimdallTools |
| 16 | + class DBProtectMapper |
| 17 | + def initialize(xml, name=nil, verbose = false) |
| 18 | + @verbose = verbose |
| 19 | + |
| 20 | + begin |
| 21 | + dataset = xml_to_hash(xml) |
| 22 | + @entries = compile_findings(dataset['dataset']) |
| 23 | + |
| 24 | + rescue StandardError => e |
| 25 | + raise "Invalid DBProtect XML file provided Exception: #{e};\nNote that XML must be of kind `Check Results Details`." |
| 26 | + end |
| 27 | + |
| 28 | + end |
| 29 | + |
| 30 | + def to_hdf |
| 31 | + controls = [] |
| 32 | + @entries.each do |entry| |
| 33 | + @item = {} |
| 34 | + @item['id'] = entry['Check ID'] |
| 35 | + @item['title'] = entry['Check'] |
| 36 | + @item['desc'] = format_desc(entry) |
| 37 | + @item['impact'] = impact(entry['Risk DV']) |
| 38 | + @item['tags'] = {} |
| 39 | + @item['descriptions'] = [] |
| 40 | + @item['refs'] = NA_ARRAY |
| 41 | + @item['source_location'] = NA_HASH |
| 42 | + @item['code'] = '' |
| 43 | + @item['results'] = finding(entry) |
| 44 | + |
| 45 | + controls << @item |
| 46 | + end |
| 47 | + controls = collapse_duplicates(controls) |
| 48 | + results = HeimdallDataFormat.new(profile_name: @entries.first['Policy'], |
| 49 | + version: "", |
| 50 | + title: @entries.first['Job Name'], |
| 51 | + summary: format_summary(@entries.first), |
| 52 | + controls: controls) |
| 53 | + results.to_hdf |
| 54 | + end |
| 55 | + |
| 56 | + private |
| 57 | + |
| 58 | + def compile_findings(dataset) |
| 59 | + keys = dataset['metadata']['item'].map{ |e| e['name']} |
| 60 | + findings = dataset['data']['row'].map { |e| Hash[keys.zip(e['value'])] } |
| 61 | + findings |
| 62 | + end |
| 63 | + |
| 64 | + def format_desc(entry) |
| 65 | + text = [] |
| 66 | + text << "Task : #{entry['Task']}" |
| 67 | + text << "Check Category : #{entry['Check Category']}" |
| 68 | + text.join("; ") |
| 69 | + end |
| 70 | + |
| 71 | + def format_summary(entry) |
| 72 | + text = [] |
| 73 | + text << "Organization : #{entry['Organization']}" |
| 74 | + text << "Asset : #{entry['Check Asset']}" |
| 75 | + text << "Asset Type : #{entry['Asset Type']}" |
| 76 | + text << "IP Address, Port, Instance : #{entry['Asset Type']}" |
| 77 | + text << "IP Address, Port, Instance : #{entry['IP Address, Port, Instance']}" |
| 78 | + text.join("\n") |
| 79 | + end |
| 80 | + |
| 81 | + def finding(entry) |
| 82 | + finding = {} |
| 83 | + |
| 84 | + finding['code_desc'] = entry['Details'] |
| 85 | + finding['run_time'] = 0.0 |
| 86 | + finding['start_time'] = entry['Date'] |
| 87 | + |
| 88 | + case entry['Result Status'] |
| 89 | + when 'Fact' |
| 90 | + finding['status'] = 'skipped' |
| 91 | + when 'Failed' |
| 92 | + finding['status'] = 'failed' |
| 93 | + finding['backtrace'] = ["DB Protect Failed Check"] |
| 94 | + when 'Finding' |
| 95 | + finding['status'] = 'failed' |
| 96 | + when 'Not A Finding' |
| 97 | + finding['status'] = 'passed' |
| 98 | + when 'Skipped' |
| 99 | + finding['status'] = 'skipped' |
| 100 | + else |
| 101 | + finding['status'] = 'skipped' |
| 102 | + end |
| 103 | + [finding] |
| 104 | + end |
| 105 | + |
| 106 | + def impact(severity) |
| 107 | + IMPACT_MAPPING[severity.to_sym] |
| 108 | + end |
| 109 | + |
| 110 | + # DBProtect report could have multiple issue entries for multiple findings of same issue type. |
| 111 | + # The meta data is identical across entries |
| 112 | + # method collapse_duplicates return unique controls with applicable findings collapsed into it. |
| 113 | + def collapse_duplicates(controls) |
| 114 | + unique_controls = [] |
| 115 | + |
| 116 | + controls.map { |x| x['id'] }.uniq.each do |id| |
| 117 | + collapsed_results = controls.select { |x| x['id'].eql?(id) }.map {|x| x['results']} |
| 118 | + unique_control = controls.find { |x| x['id'].eql?(id) } |
| 119 | + unique_control['results'] = collapsed_results.flatten |
| 120 | + unique_controls << unique_control |
| 121 | + end |
| 122 | + unique_controls |
| 123 | + end |
| 124 | + |
| 125 | + |
| 126 | + end |
| 127 | +end |
0 commit comments