inspec_tools 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +15 -0
  3. data/README.md +373 -0
  4. data/Rakefile +96 -0
  5. data/exe/inspec_tools +14 -0
  6. data/lib/data/README.TXT +25 -0
  7. data/lib/data/U_CCI_List.xml +38403 -0
  8. data/lib/data/attributes.yml +23 -0
  9. data/lib/data/cci2html.xsl +136 -0
  10. data/lib/data/cis_to_nist_critical_controls +0 -0
  11. data/lib/data/cis_to_nist_mapping +0 -0
  12. data/lib/data/mapping.yml +17 -0
  13. data/lib/data/rubocop.yml +4 -0
  14. data/lib/data/stig.csv +1 -0
  15. data/lib/data/threshold.yaml +83 -0
  16. data/lib/exceptions/impact_input_error.rb +6 -0
  17. data/lib/exceptions/severity_input_error.rb +6 -0
  18. data/lib/happy_mapper_tools/benchmark.rb +161 -0
  19. data/lib/happy_mapper_tools/cci_attributes.rb +66 -0
  20. data/lib/happy_mapper_tools/stig_attributes.rb +216 -0
  21. data/lib/happy_mapper_tools/stig_checklist.rb +99 -0
  22. data/lib/inspec_tools.rb +17 -0
  23. data/lib/inspec_tools/ckl.rb +20 -0
  24. data/lib/inspec_tools/cli.rb +31 -0
  25. data/lib/inspec_tools/csv.rb +101 -0
  26. data/lib/inspec_tools/help.rb +9 -0
  27. data/lib/inspec_tools/help/compliance.md +7 -0
  28. data/lib/inspec_tools/help/csv2inspec.md +5 -0
  29. data/lib/inspec_tools/help/inspec2ckl.md +5 -0
  30. data/lib/inspec_tools/help/inspec2csv.md +5 -0
  31. data/lib/inspec_tools/help/inspec2xccdf.md +5 -0
  32. data/lib/inspec_tools/help/pdf2inspec.md +6 -0
  33. data/lib/inspec_tools/help/summary.md +5 -0
  34. data/lib/inspec_tools/help/xccdf2inspec.md +5 -0
  35. data/lib/inspec_tools/inspec.rb +331 -0
  36. data/lib/inspec_tools/pdf.rb +125 -0
  37. data/lib/inspec_tools/plugin.rb +15 -0
  38. data/lib/inspec_tools/plugin_cli.rb +275 -0
  39. data/lib/inspec_tools/summary.rb +126 -0
  40. data/lib/inspec_tools/version.rb +8 -0
  41. data/lib/inspec_tools/xccdf.rb +156 -0
  42. data/lib/inspec_tools/xlsx_tool.rb +135 -0
  43. data/lib/inspec_tools_plugin.rb +7 -0
  44. data/lib/overrides/false_class.rb +5 -0
  45. data/lib/overrides/nil_class.rb +5 -0
  46. data/lib/overrides/object.rb +5 -0
  47. data/lib/overrides/string.rb +5 -0
  48. data/lib/overrides/true_class.rb +5 -0
  49. data/lib/utilities/cis_to_nist.rb +11 -0
  50. data/lib/utilities/csv_util.rb +14 -0
  51. data/lib/utilities/extract_pdf_text.rb +20 -0
  52. data/lib/utilities/inspec_util.rb +441 -0
  53. data/lib/utilities/parser.rb +373 -0
  54. data/lib/utilities/text_cleaner.rb +69 -0
  55. metadata +359 -0
@@ -0,0 +1,125 @@
1
+ require 'digest'
2
+
3
+ require_relative '../utilities/inspec_util'
4
+ require_relative '../utilities/extract_pdf_text'
5
+ require_relative '../utilities/parser'
6
+ require_relative '../utilities/text_cleaner'
7
+ require_relative '../utilities/cis_to_nist'
8
+
9
+ # rubocop:disable Metrics/AbcSize
10
+ # rubocop:disable Metrics/PerceivedComplexity
11
+ # rubocop:disable Metrics/CyclomaticComplexity
12
+
13
+ module InspecTools
14
+ class PDF
15
+ def initialize(pdf, profile_name, debug = false)
16
+ raise ArgumentError if pdf.nil?
17
+
18
+ @pdf = pdf
19
+ @name = profile_name
20
+ @debug = debug
21
+ end
22
+
23
+ def to_inspec
24
+ @controls = []
25
+ @csv_handle = nil
26
+ @cci_xml = nil
27
+ @nist_mapping = Utils::CisToNist.get_mapping('cis_to_nist_critical_controls')
28
+ @pdf_text = ''
29
+ @clean_text = ''
30
+ @transformed_data = ''
31
+ @profile = {}
32
+ read_pdf
33
+ @title ||= extract_title
34
+ clean_pdf_text
35
+ transform_data
36
+ insert_json_metadata
37
+ @profile['controls'] = parse_controls
38
+ @profile['sha256'] = Digest::SHA256.hexdigest @profile.to_s
39
+ @profile
40
+ end
41
+
42
+ def to_csv
43
+ # TODO: to_csv
44
+ end
45
+
46
+ def to_xccdf
47
+ # TODO: to_xccdf
48
+ end
49
+
50
+ def to_ckl
51
+ # TODO: to_ckl
52
+ end
53
+
54
+ private
55
+
56
+ # converts passed in data into InSpec format
57
+ def parse_controls
58
+ controls = []
59
+ @transformed_data.each do |contr|
60
+ nist = find_nist(contr[:cis]) unless contr[:cis] == 'No CIS Control'
61
+ control = {}
62
+ control['id'] = 'M-' + contr[:title].split(' ')[0]
63
+ control['title'] = contr[:title]
64
+ control['desc'] = contr[:descr]
65
+ control['impact'] = Utils::InspecUtil.get_impact('medium')
66
+ control['tags'] = {}
67
+ control['tags']['severity'] = Utils::InspecUtil.get_impact_string(control['impact'])
68
+ control['tags']['ref'] = contr[:ref] unless contr[:ref].nil?
69
+ control['tags']['applicability'] = contr[:applicability] unless contr[:applicability].nil?
70
+ control['tags']['cis_id'] = contr[:title].split(' ')[0] unless contr[:title].nil?
71
+ control['tags']['cis_control'] = [contr[:cis], @nist_mapping[0][:cis_ver]] unless contr[:cis].nil? # tag cis_control: [5, 6.1] ##6.1 is the version
72
+ control['tags']['cis_level'] = contr[:level] unless contr[:level].nil?
73
+ control['tags']['nist'] = nist unless nist.nil? # tag nist: [AC-3, 4] ##4 is the version
74
+ control['tags']['check'] = contr[:check] unless contr[:check].nil?
75
+ control['tags']['fix'] = contr[:fix] unless contr[:fix].nil?
76
+ control['tags']['Default Value'] = contr[:default] unless contr[:default].nil?
77
+ controls << control
78
+ end
79
+ controls
80
+ end
81
+
82
+ def insert_json_metadata
83
+ @profile['name'] = @name
84
+ @profile['title'] = @title
85
+ @profile['maintainer'] = 'The Authors'
86
+ @profile['copyright'] = 'The Authors'
87
+ @profile['copyright_email'] = 'you@example.com'
88
+ @profile['license'] = 'Apache-2.0'
89
+ @profile['summary'] = 'An InSpec Compliance Profile'
90
+ @profile['version'] = '0.1.0'
91
+ @profile['supports'] = []
92
+ @profile['attributes'] = []
93
+ @profile['generator'] = {
94
+ 'name': 'inspec_tools',
95
+ 'version': VERSION
96
+ }
97
+ end
98
+
99
+ def extract_title
100
+ @pdf_text.match(/([^\n]*)\n/).captures[0]
101
+ end
102
+
103
+ def read_pdf
104
+ @pdf_text = Util::ExtractPdfText.new(@pdf).extracted_text
105
+ write_pdf_text if @debug
106
+ end
107
+
108
+ def clean_pdf_text
109
+ @clean_text = Util::TextCleaner.new.clean_data(@pdf_text)
110
+ write_clean_text if @debug
111
+ end
112
+
113
+ def transform_data
114
+ @transformed_data = Util::PrepareData.new(@clean_text).transformed_data
115
+ end
116
+
117
+ def write_pdf_text
118
+ File.write('pdf_text', @pdf_text)
119
+ end
120
+
121
+ def write_clean_text
122
+ File.write('debug_text', @clean_text)
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module InspecToolsPlugin
4
+ class Plugin < Inspec.plugin(2)
5
+ # Metadata
6
+ # Must match entry in plugins.json
7
+ plugin_name :'inspec-tools_plugin'
8
+
9
+ # Activation hooks (CliCommand as an example)
10
+ cli_command :tools do
11
+ require_relative 'plugin_cli'
12
+ InspecPlugins::InspecToolsPlugin::CliCommand
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,275 @@
1
+ require 'yaml'
2
+ require 'json'
3
+ require 'roo'
4
+ require_relative '../utilities/inspec_util'
5
+ require_relative '../utilities/csv_util'
6
+
7
+ module InspecTools
8
+ autoload :Help, 'inspec_tools/help'
9
+ autoload :Command, 'inspec_tools/command'
10
+ autoload :XCCDF, 'inspec_tools/xccdf'
11
+ autoload :PDF, 'inspec_tools/pdf'
12
+ autoload :CSVTool, 'inspec_tools/csv'
13
+ autoload :CKL, 'inspec_tools/ckl'
14
+ autoload :Inspec, 'inspec_tools/inspec'
15
+ autoload :Summary, 'inspec_tools/summary'
16
+ autoload :Threshold, 'inspec_tools/threshold'
17
+ autoload :XLSXTool, 'inspec_tools/xlsx_tool'
18
+ end
19
+
20
+ # rubocop:disable Style/GuardClause
21
+ module InspecPlugins
22
+ module InspecToolsPlugin
23
+ class CliCommand < Inspec.plugin(2, :cli_command) # rubocop:disable Metrics/ClassLength
24
+ POSSIBLE_LOG_LEVELS = %w{debug info warn error fatal}.freeze
25
+
26
+ class_option :log_directory, type: :string, aliases: :l, desc: 'Provie log location'
27
+ class_option :log_level, type: :string, desc: "Set the logging level: #{POSSIBLE_LOG_LEVELS}"
28
+
29
+ subcommand_desc 'tools [COMMAND]', 'Runs inspec_tools commands through Inspec'
30
+
31
+ desc 'xccdf2inspec', 'xccdf2inspec translates an xccdf file to an inspec profile'
32
+ long_desc InspecTools::Help.text(:xccdf2inspec)
33
+ option :xccdf, required: true, aliases: '-x'
34
+ option :attributes, required: false, aliases: '-a'
35
+ option :output, required: false, aliases: '-o', default: 'profile'
36
+ option :format, required: false, aliases: '-f', enum: %w{ruby hash}, default: 'ruby'
37
+ option :separate_files, required: false, type: :boolean, default: true, aliases: '-s'
38
+ option :replace_tags, type: :array, required: false, aliases: '-r'
39
+ option :metadata, required: false, aliases: '-m'
40
+ def xccdf2inspec
41
+ xccdf = InspecTools::XCCDF.new(File.read(options[:xccdf]), options[:replace_tags])
42
+ profile = xccdf.to_inspec
43
+
44
+ if !options[:metadata].nil?
45
+ xccdf.inject_metadata(File.read(options[:metadata]))
46
+ end
47
+
48
+ Utils::InspecUtil.unpack_inspec_json(options[:output], profile, options[:separate_files], options[:format])
49
+ if !options[:attributes].nil?
50
+ attributes = xccdf.to_attributes
51
+ File.write(options[:attributes], YAML.dump(attributes))
52
+ end
53
+ end
54
+
55
+ desc 'inspec2xccdf', 'inspec2xccdf translates an inspec profile and attributes files to an xccdf file'
56
+ long_desc InspecTools::Help.text(:inspec2xccdf)
57
+ option :inspec_json, required: true, aliases: '-j'
58
+ option :attributes, required: true, aliases: '-a'
59
+ option :output, required: true, aliases: '-o'
60
+ def inspec2xccdf
61
+ json = File.read(options[:inspec_json])
62
+ inspec_tool = InspecTools::Inspec.new(json)
63
+ attr_hsh = YAML.load_file(options[:attributes])
64
+ xccdf = inspec_tool.to_xccdf(attr_hsh)
65
+ File.write(options[:output], xccdf)
66
+ end
67
+
68
+ desc 'csv2inspec', 'csv2inspec translates CSV to Inspec controls using a mapping file'
69
+ long_desc InspecTools::Help.text(:csv2inspec)
70
+ option :csv, required: true, aliases: '-c'
71
+ option :mapping, required: true, aliases: '-m'
72
+ option :verbose, required: false, type: :boolean, aliases: '-V'
73
+ option :output, required: false, aliases: '-o', default: 'profile'
74
+ option :format, required: false, aliases: '-f', enum: %w{ruby hash}, default: 'ruby'
75
+ option :separate_files, required: false, type: :boolean, default: true, aliases: '-s'
76
+ def csv2inspec
77
+ csv = CSV.read(options[:csv], encoding: 'ISO8859-1')
78
+ mapping = YAML.load_file(options[:mapping])
79
+ profile = InspecTools::CSVTool.new(csv, mapping, options[:csv].split('/')[-1].split('.')[0], options[:verbose]).to_inspec
80
+ Utils::InspecUtil.unpack_inspec_json(options[:output], profile, options[:separate_files], options[:format])
81
+ end
82
+
83
+ desc 'xlsx2inspec', 'xlsx2inspec translates CIS XLSX to Inspec controls using a mapping file'
84
+ long_desc InspecTools::Help.text(:xlsx2inspec)
85
+ option :xlsx, required: true, aliases: '-x'
86
+ option :mapping, required: true, aliases: '-m'
87
+ option :control_name_prefix, required: true, aliases: '-p'
88
+ option :verbose, required: false, type: :boolean, aliases: '-V'
89
+ option :output, required: false, aliases: '-o', default: 'profile'
90
+ option :format, required: false, aliases: '-f', enum: %w{ruby hash}, default: 'ruby'
91
+ option :separate_files, required: false, type: :boolean, default: true, aliases: '-s'
92
+ def xlsx2inspec
93
+ xlsx = Roo::Spreadsheet.open(options[:xlsx])
94
+ mapping = YAML.load_file(options[:mapping])
95
+ profile = InspecTools::XLSXTool.new(xlsx, mapping, options[:xlsx].split('/')[-1].split('.')[0], options[:verbose]).to_inspec(options[:control_name_prefix])
96
+ Utils::InspecUtil.unpack_inspec_json(options[:output], profile, options[:separate_files], options[:format])
97
+ end
98
+
99
+ desc 'inspec2csv', 'inspec2csv translates Inspec controls to CSV'
100
+ long_desc InspecTools::Help.text(:inspec2csv)
101
+ option :inspec_json, required: true, aliases: '-j'
102
+ option :output, required: true, aliases: '-o'
103
+ option :verbose, required: false, type: :boolean, aliases: '-V'
104
+ def inspec2csv
105
+ csv = InspecTools::Inspec.new(File.read(options[:inspec_json])).to_csv
106
+ Utils::CSVUtil.unpack_csv(csv, options[:output])
107
+ end
108
+
109
+ desc 'inspec2ckl', 'inspec2ckl translates an inspec json file to a Checklist file'
110
+ long_desc InspecTools::Help.text(:inspec2ckl)
111
+ option :inspec_json, required: true, aliases: '-j'
112
+ option :output, required: true, aliases: '-o'
113
+ option :verbose, type: :boolean, aliases: '-V'
114
+ option :metadata, required: false, aliases: '-m'
115
+ def inspec2ckl
116
+ metadata = '{}'
117
+ if !options[:metadata].nil?
118
+ metadata = File.read(options[:metadata])
119
+ end
120
+ ckl = InspecTools::Inspec.new(File.read(options[:inspec_json]), metadata).to_ckl
121
+ File.write(options[:output], ckl)
122
+ end
123
+
124
+ desc 'pdf2inspec', 'pdf2inspec translates a PDF Security Control Speficication to Inspec Security Profile'
125
+ long_desc InspecTools::Help.text(:pdf2inspec)
126
+ option :pdf, required: true, aliases: '-p'
127
+ option :output, required: false, aliases: '-o', default: 'profile'
128
+ option :debug, required: false, aliases: '-d', type: :boolean, default: false
129
+ option :format, required: false, aliases: '-f', enum: %w{ruby hash}, default: 'ruby'
130
+ option :separate_files, required: false, type: :boolean, default: true, aliases: '-s'
131
+ def pdf2inspec
132
+ pdf = File.open(options[:pdf])
133
+ profile = InspecTools::PDF.new(pdf, options[:output], options[:debug]).to_inspec
134
+ Utils::InspecUtil.unpack_inspec_json(options[:output], profile, options[:separate_files], options[:format])
135
+ end
136
+
137
+ desc 'generate_map', 'Generates mapping template from CSV to Inspec Controls'
138
+ def generate_map
139
+ template = '
140
+ # Setting csv_header to true will skip the csv file header
141
+ skip_csv_header: true
142
+ width : 80
143
+
144
+
145
+ control.id: 0
146
+ control.title: 15
147
+ control.desc: 16
148
+ control.tags:
149
+ severity: 1
150
+ rid: 8
151
+ stig_id: 3
152
+ cci: 2
153
+ check: 12
154
+ fix: 10
155
+ '
156
+ myfile = File.new('mapping.yml', 'w')
157
+ myfile.puts template
158
+ myfile.close
159
+ end
160
+
161
+ desc 'generate_ckl_metadata', 'Generate metadata file that can be passed to inspec2ckl'
162
+ def generate_ckl_metadata
163
+ metadata = {}
164
+
165
+ metadata['stigid'] = ask('STID ID: ')
166
+ metadata['role'] = ask('Role: ')
167
+ metadata['type'] = ask('Type: ')
168
+ metadata['hostname'] = ask('Hostname: ')
169
+ metadata['ip'] = ask('IP Address: ')
170
+ metadata['mac'] = ask('MAC Address: ')
171
+ metadata['fqdn'] = ask('FQDN: ')
172
+ metadata['tech_area'] = ask('Tech Area: ')
173
+ metadata['target_key'] = ask('Target Key: ')
174
+ metadata['web_or_database'] = ask('Web or Database: ')
175
+ metadata['web_db_site'] = ask('Web DB Site: ')
176
+ metadata['web_db_instance'] = ask('Web DB Instance: ')
177
+
178
+ metadata.delete_if { |_key, value| value.empty? }
179
+ File.open('metadata.json', 'w') do |f|
180
+ f.write(metadata.to_json)
181
+ end
182
+ end
183
+
184
+ desc 'generate_inspec_metadata', 'Generate mapping file that can be passed to xccdf2inspec'
185
+ def generate_inspec_metadata
186
+ metadata = {}
187
+
188
+ metadata['maintainer'] = ask('Maintainer: ')
189
+ metadata['copyright'] = ask('Copyright: ')
190
+ metadata['copyright_email'] = ask('Copyright Email: ')
191
+ metadata['license'] = ask('License: ')
192
+ metadata['version'] = ask('Version: ')
193
+
194
+ metadata.delete_if { |_key, value| value.empty? }
195
+ File.open('metadata.json', 'w') do |f|
196
+ f.write(metadata.to_json)
197
+ end
198
+ end
199
+
200
+ desc 'summary', 'summary parses an inspec results json to create a summary json'
201
+ long_desc InspecTools::Help.text(:summary)
202
+ option :inspec_json, required: true, aliases: '-j'
203
+ option :verbose, type: :boolean, aliases: '-V'
204
+ option :json_full, type: :boolean, required: false, aliases: '-f'
205
+ option :json_counts, type: :boolean, required: false, aliases: '-k'
206
+
207
+ def summary
208
+ summary = InspecTools::Summary.new(File.read(options[:inspec_json])).to_summary
209
+
210
+ unless options.include?('json_full') || options.include?('json_counts')
211
+ puts "\nOverall compliance: #{summary[:compliance]}%\n\n"
212
+ summary[:status].keys.each do |category|
213
+ puts category
214
+ summary[:status][category].keys.each do |impact|
215
+ puts "\t#{impact} : #{summary[:status][category][impact]}"
216
+ end
217
+ end
218
+ end
219
+
220
+ json_summary = summary.to_json
221
+ puts json_summary if options[:json_full]
222
+ puts summary[:status].to_json if options[:json_counts]
223
+ end
224
+
225
+ desc 'compliance', 'compliance parses an inspec results json to check if the compliance level meets a specified threshold'
226
+ long_desc InspecTools::Help.text(:compliance)
227
+ option :inspec_json, required: true, aliases: '-j'
228
+ option :threshold_file, required: false, aliases: '-f'
229
+ option :threshold_inline, required: false, aliases: '-i'
230
+ option :verbose, type: :boolean, aliases: '-V'
231
+
232
+ def compliance
233
+ if options[:threshold_file].nil? && options[:threshold_inline].nil?
234
+ puts 'Please provide threshold as a yaml file or inline yaml'
235
+ exit(1)
236
+ end
237
+ threshold = YAML.load_file(options[:threshold_file]) unless options[:threshold_file].nil?
238
+ threshold = YAML.safe_load(options[:threshold_inline]) unless options[:threshold_inline].nil?
239
+ compliance = InspecTools::Summary.new(File.read(options[:inspec_json])).threshold(threshold)
240
+ compliance ? exit(0) : exit(1)
241
+ end
242
+ end
243
+ end
244
+ end
245
+
246
+ #=====================================================================#
247
+ # Pre-Flight Code
248
+ #=====================================================================#
249
+ help_commands = ['-h', '--help', 'help']
250
+ log_commands = ['-l', '--log-directory']
251
+ version_commands = ['-v', '--version', 'version']
252
+
253
+ #---------------------------------------------------------------------#
254
+ # Adjustments for non-required version commands
255
+ #---------------------------------------------------------------------#
256
+ unless (version_commands & ARGV).empty?
257
+ puts InspecTools::VERSION
258
+ exit 0
259
+ end
260
+
261
+ #---------------------------------------------------------------------#
262
+ # Adjustments for non-required log-directory
263
+ #---------------------------------------------------------------------#
264
+ ARGV.push("--log-directory=#{Dir.pwd}/logs") if (log_commands & ARGV).empty? && (help_commands & ARGV).empty?
265
+
266
+ # Push help to front of command so thor recognizes subcommands are called with help
267
+ if help_commands.any? { |cmd| ARGV.include? cmd }
268
+ help_commands.each do |cmd|
269
+ if (match = ARGV.delete(cmd))
270
+ ARGV.unshift match
271
+ end
272
+ end
273
+ end
274
+
275
+ # rubocop:enable Style/GuardClause
@@ -0,0 +1,126 @@
1
+ require 'json'
2
+ require 'yaml'
3
+ require_relative '../utilities/inspec_util'
4
+
5
+ # rubocop:disable Metrics/AbcSize
6
+
7
+ # Impact Definitions
8
+ CRITICAL = 0.9
9
+ HIGH = 0.7
10
+ MEDIUM = 0.5
11
+ LOW = 0.3
12
+
13
+ BUCKETS = %i(failed passed no_impact skipped error).freeze
14
+ TALLYS = %i(total critical high medium low).freeze
15
+
16
+ THRESHOLD_TEMPLATE = File.expand_path('../data/threshold.yaml', File.dirname(__FILE__))
17
+
18
+ module InspecTools
19
+ class Summary
20
+ def initialize(inspec_json)
21
+ @json = JSON.parse(inspec_json)
22
+ end
23
+
24
+ def to_summary
25
+ @data = Utils::InspecUtil.parse_data_for_ckl(@json)
26
+ @summary = {}
27
+ @data.keys.each do |control_id|
28
+ current_control = @data[control_id]
29
+ current_control[:compliance_status] = Utils::InspecUtil.control_status(current_control, true)
30
+ current_control[:finding_details] = Utils::InspecUtil.control_finding_details(current_control, current_control[:compliance_status])
31
+ end
32
+ compute_summary
33
+ @summary
34
+ end
35
+
36
+ def threshold(threshold = nil)
37
+ @summary = to_summary
38
+ @threshold = Utils::InspecUtil.to_dotted_hash(YAML.load_file(THRESHOLD_TEMPLATE))
39
+ parse_threshold(Utils::InspecUtil.to_dotted_hash(threshold))
40
+ threshold_compliance
41
+ end
42
+
43
+ private
44
+
45
+ def compute_summary
46
+ @summary[:buckets] = {}
47
+ @summary[:buckets][:failed] = select_by_status(@data, 'Open')
48
+ @summary[:buckets][:passed] = select_by_status(@data, 'NotAFinding')
49
+ @summary[:buckets][:no_impact] = select_by_status(@data, 'Not_Applicable')
50
+ @summary[:buckets][:skipped] = select_by_status(@data, 'Not_Reviewed')
51
+ @summary[:buckets][:error] = select_by_status(@data, 'Profile_Error')
52
+
53
+ @summary[:status] = {}
54
+ @summary[:status][:failed] = tally_by_impact(@summary[:buckets][:failed])
55
+ @summary[:status][:passed] = tally_by_impact(@summary[:buckets][:passed])
56
+ @summary[:status][:no_impact] = tally_by_impact(@summary[:buckets][:no_impact])
57
+ @summary[:status][:skipped] = tally_by_impact(@summary[:buckets][:skipped])
58
+ @summary[:status][:error] = tally_by_impact(@summary[:buckets][:error])
59
+
60
+ @summary[:compliance] = compute_compliance
61
+ end
62
+
63
+ def select_by_impact(controls, impact)
64
+ controls.select { |_key, value| value[:impact].to_f.eql?(impact) }
65
+ end
66
+
67
+ def select_by_status(controls, status)
68
+ controls.select { |_key, value| value[:compliance_status].eql?(status) }
69
+ end
70
+
71
+ def tally_by_impact(controls)
72
+ tally = {}
73
+ tally[:total] = controls.count
74
+ tally[:critical] = select_by_impact(controls, CRITICAL).count
75
+ tally[:high] = select_by_impact(controls, HIGH).count
76
+ tally[:medium] = select_by_impact(controls, MEDIUM).count
77
+ tally[:low] = select_by_impact(controls, LOW).count
78
+ tally
79
+ end
80
+
81
+ def compute_compliance
82
+ (@summary[:status][:passed][:total]*100.0/
83
+ (@summary[:status][:passed][:total]+
84
+ @summary[:status][:failed][:total]+
85
+ @summary[:status][:skipped][:total]+
86
+ @summary[:status][:error][:total])).floor
87
+ end
88
+
89
+ def threshold_compliance
90
+ compliance = true
91
+ failure = []
92
+ max = @threshold['compliance.max']
93
+ min = @threshold['compliance.min']
94
+ if max != -1 and @summary[:compliance] > max
95
+ compliance = false
96
+ failure << "Expected compliance.max:#{max} got:#{@summary[:compliance]}"
97
+ end
98
+ if min != -1 and @summary[:compliance] < min
99
+ compliance = false
100
+ failure << "Expected compliance.min:#{min} got:#{@summary[:compliance]}"
101
+ end
102
+ status = @summary[:status]
103
+ BUCKETS.each do |bucket|
104
+ TALLYS.each do |tally|
105
+ max = @threshold["#{bucket}.#{tally}.max"]
106
+ min = @threshold["#{bucket}.#{tally}.min"]
107
+ if max != -1 and status[bucket][tally] > max
108
+ compliance = false
109
+ failure << "Expected #{bucket}.#{tally}.max:#{max} got:#{status[bucket][tally]}"
110
+ end
111
+ if min != -1 and status[bucket][tally] < min
112
+ compliance = false
113
+ failure << "Expected #{bucket}.#{tally}.min:#{min} got:#{status[bucket][tally]}"
114
+ end
115
+ end
116
+ end
117
+ puts failure.join("\n") unless compliance
118
+ puts 'Compliance threshold met' if compliance
119
+ compliance
120
+ end
121
+
122
+ def parse_threshold(new_threshold)
123
+ @threshold.merge!(new_threshold)
124
+ end
125
+ end
126
+ end