inspec_tools 0.0.0.1.ENOTAG

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +662 -0
  3. data/LICENSE.md +15 -0
  4. data/README.md +329 -0
  5. data/Rakefile +30 -0
  6. data/exe/inspec_tools +14 -0
  7. data/lib/data/NIST_Map_02052020_CIS_Controls_Version_7.1_Implementation_Groups_1.2.xlsx +0 -0
  8. data/lib/data/NIST_Map_09212017B_CSC-CIS_Critical_Security_Controls_VER_6.1_Excel_9.1.2016.xlsx +0 -0
  9. data/lib/data/README.TXT +25 -0
  10. data/lib/data/U_CCI_List.xml +38403 -0
  11. data/lib/data/attributes.yml +23 -0
  12. data/lib/data/cci2html.xsl +136 -0
  13. data/lib/data/mapping.yml +17 -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 +196 -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 +136 -0
  37. data/lib/inspec_tools/plugin.rb +15 -0
  38. data/lib/inspec_tools/plugin_cli.rb +278 -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 +155 -0
  42. data/lib/inspec_tools/xlsx_tool.rb +148 -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/csv_util.rb +14 -0
  50. data/lib/utilities/extract_nist_cis_mapping.rb +57 -0
  51. data/lib/utilities/extract_pdf_text.rb +20 -0
  52. data/lib/utilities/inspec_util.rb +435 -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,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,278 @@
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 :output, required: false, aliases: '-o'
204
+ option :cli, type: :boolean, required: false, aliases: '-c'
205
+ option :verbose, type: :boolean, aliases: '-V'
206
+ option :json_full, type: :boolean, required: false, aliases: '-f'
207
+ option :json_counts, type: :boolean, required: false, aliases: '-k'
208
+
209
+ def summary
210
+ summary = InspecTools::Summary.new(File.read(options[:inspec_json])).to_summary
211
+
212
+ if options[:cli]
213
+ puts "\nOverall compliance: #{summary[:compliance]}%\n\n"
214
+ summary[:status].keys.each do |category|
215
+ puts category
216
+ summary[:status][category].keys.each do |impact|
217
+ puts "\t#{impact} : #{summary[:status][category][impact]}"
218
+ end
219
+ end
220
+ end
221
+
222
+ json_summary = summary.to_json
223
+ File.write(options[:output], json_summary) if options[:output]
224
+ puts json_summary if options[:json_full]
225
+ puts summary[:status].to_json if options[:json_counts]
226
+ end
227
+
228
+ desc 'compliance', 'compliance parses an inspec results json to check if the compliance level meets a specified threshold'
229
+ long_desc InspecTools::Help.text(:compliance)
230
+ option :inspec_json, required: true, aliases: '-j'
231
+ option :threshold_file, required: false, aliases: '-f'
232
+ option :threshold_inline, required: false, aliases: '-i'
233
+ option :verbose, type: :boolean, aliases: '-V'
234
+
235
+ def compliance
236
+ if options[:threshold_file].nil? && options[:threshold_inline].nil?
237
+ puts 'Please provide threshold as a yaml file or inline yaml'
238
+ exit(1)
239
+ end
240
+ threshold = YAML.load_file(options[:threshold_file]) unless options[:threshold_file].nil?
241
+ threshold = YAML.safe_load(options[:threshold_inline]) unless options[:threshold_inline].nil?
242
+ compliance = InspecTools::Summary.new(File.read(options[:inspec_json])).threshold(threshold)
243
+ compliance ? exit(0) : exit(1)
244
+ end
245
+ end
246
+ end
247
+ end
248
+
249
+ #=====================================================================#
250
+ # Pre-Flight Code
251
+ #=====================================================================#
252
+ help_commands = ['-h', '--help', 'help']
253
+ log_commands = ['-l', '--log-directory']
254
+ version_commands = ['-v', '--version', 'version']
255
+
256
+ #---------------------------------------------------------------------#
257
+ # Adjustments for non-required version commands
258
+ #---------------------------------------------------------------------#
259
+ unless (version_commands & ARGV).empty?
260
+ puts InspecTools::VERSION
261
+ exit 0
262
+ end
263
+
264
+ #---------------------------------------------------------------------#
265
+ # Adjustments for non-required log-directory
266
+ #---------------------------------------------------------------------#
267
+ ARGV.push("--log-directory=#{Dir.pwd}/logs") if (log_commands & ARGV).empty? && (help_commands & ARGV).empty?
268
+
269
+ # Push help to front of command so thor recognizes subcommands are called with help
270
+ if help_commands.any? { |cmd| ARGV.include? cmd }
271
+ help_commands.each do |cmd|
272
+ if (match = ARGV.delete(cmd))
273
+ ARGV.unshift match
274
+ end
275
+ end
276
+ end
277
+
278
+ # 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 = %w{failed passed no_impact skipped error}.freeze
14
+ TALLYS = %w{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])).round(1)
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.to_sym][tally.to_sym] > max
108
+ compliance = false
109
+ failure << "Expected #{bucket}.#{tally}.max:#{max} got:#{status[bucket.to_sym][tally.to_sym]}"
110
+ end
111
+ if min != -1 and status[bucket.to_sym][tally.to_sym] < min
112
+ compliance = false
113
+ failure << "Expected #{bucket}.#{tally}.min:#{min} got:#{status[bucket.to_sym][tally.to_sym]}"
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
@@ -0,0 +1,8 @@
1
+ require 'git-version-bump'
2
+
3
+ module InspecTools
4
+ # Enable lite-tags (2nd parameter to git-version-bump version command)
5
+ # Lite tags are tags that are used by GitHub releases that do not contain
6
+ # annotations
7
+ VERSION = GVB.version(false, true)
8
+ end
@@ -0,0 +1,155 @@
1
+ require_relative '../happy_mapper_tools/stig_attributes'
2
+ require_relative '../happy_mapper_tools/cci_attributes'
3
+ require_relative '../utilities/inspec_util'
4
+
5
+ require 'digest'
6
+ require 'json'
7
+
8
+ module InspecTools
9
+ # rubocop:disable Metrics/ClassLength
10
+ # rubocop:disable Metrics/AbcSize
11
+ # rubocop:disable Metrics/PerceivedComplexity
12
+ # rubocop:disable Metrics/CyclomaticComplexity
13
+ # rubocop:disable Metrics/BlockLength
14
+ class XCCDF
15
+ def initialize(xccdf, replace_tags = nil)
16
+ @xccdf = xccdf
17
+ @xccdf = replace_tags_in_xccdf(replace_tags, @xccdf) unless replace_tags.nil?
18
+ cci_list_path = File.join(File.dirname(__FILE__), '../data/U_CCI_List.xml')
19
+ @cci_items = HappyMapperTools::CCIAttributes::CCI_List.parse(File.read(cci_list_path))
20
+ # @cci_items = HappyMapperTools::CCIAttributes::CCI_List.parse(File.read('./data/U_CCI_List.xml'))
21
+ @benchmark = HappyMapperTools::StigAttributes::Benchmark.parse(@xccdf)
22
+ end
23
+
24
+ def to_ckl
25
+ # TODO: to_ckl
26
+ end
27
+
28
+ def to_csv
29
+ # TODO: to_csv
30
+ end
31
+
32
+ def to_inspec
33
+ @profile = {}
34
+ @controls = []
35
+ insert_json_metadata
36
+ insert_controls
37
+ @profile['sha256'] = Digest::SHA256.hexdigest @profile.to_s
38
+ @profile
39
+ end
40
+
41
+ ####
42
+ # extracts non-InSpec attributes
43
+ ###
44
+ # TODO there may be more attributes we want to extract, see data/attributes.yml for example
45
+ def to_attributes # rubocop:disable Metrics/AbcSize
46
+ @attribute = {}
47
+
48
+ @attribute['benchmark.title'] = @benchmark.title
49
+ @attribute['benchmark.id'] = @benchmark.id
50
+ @attribute['benchmark.description'] = @benchmark.description
51
+ @attribute['benchmark.version'] = @benchmark.version
52
+
53
+ @attribute['benchmark.status'] = @benchmark.status
54
+ @attribute['benchmark.status.date'] = @benchmark.release_date.release_date
55
+
56
+ @attribute['benchmark.notice.id'] = @benchmark.notice.id
57
+
58
+ @attribute['benchmark.plaintext'] = @benchmark.plaintext.plaintext
59
+ @attribute['benchmark.plaintext.id'] = @benchmark.plaintext.id
60
+
61
+ @attribute['reference.href'] = @benchmark.reference.href
62
+ @attribute['reference.dc.publisher'] = @benchmark.reference.dc_publisher
63
+ @attribute['reference.dc.source'] = @benchmark.reference.dc_source
64
+ @attribute['reference.dc.title'] = @benchmark.group[0].rule.reference.dc_title if !@benchmark.group[0].nil?
65
+ @attribute['reference.dc.subject'] = @benchmark.group[0].rule.reference.dc_subject if !@benchmark.group[0].nil?
66
+ @attribute['reference.dc.type'] = @benchmark.group[0].rule.reference.dc_type if !@benchmark.group[0].nil?
67
+ @attribute['reference.dc.identifier'] = @benchmark.group[0].rule.reference.dc_identifier if !@benchmark.group[0].nil?
68
+
69
+ @attribute['content_ref.name'] = @benchmark.group[0].rule.check.content_ref.name if !@benchmark.group[0].nil?
70
+ @attribute['content_ref.href'] = @benchmark.group[0].rule.check.content_ref.href if !@benchmark.group[0].nil?
71
+
72
+ @attribute
73
+ end
74
+
75
+ def publisher
76
+ @benchmark.reference.dc_publisher
77
+ end
78
+
79
+ def published
80
+ @benchmark.release_date.release_date
81
+ end
82
+
83
+ def inject_metadata(metadata = '{}')
84
+ json_metadata = JSON.parse(metadata)
85
+ json_metadata.each do |key, value|
86
+ @profile[key] = value
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ def replace_tags_in_xccdf(replace_tags, xccdf_xml)
93
+ replace_tags.each do |tag|
94
+ xccdf_xml = xccdf_xml.gsub(/(&lt;|<)#{tag}(&gt;|>)/, "$#{tag}")
95
+ end
96
+ xccdf_xml
97
+ end
98
+
99
+ def insert_json_metadata
100
+ @profile['name'] = @benchmark.id
101
+ @profile['title'] = @benchmark.title
102
+ @profile['maintainer'] = 'The Authors' if @profile['maintainer'].nil?
103
+ @profile['copyright'] = 'The Authors' if @profile['copyright'].nil?
104
+ @profile['copyright_email'] = 'you@example.com' if @profile['copyright_email'].nil?
105
+ @profile['license'] = 'Apache-2.0' if @profile['license'].nil?
106
+ @profile['summary'] = "\"#{@benchmark.description.gsub('\\', '\\\\\\').gsub('"', '\"')}\""
107
+ @profile['version'] = '0.1.0' if @profile['version'].nil?
108
+ @profile['supports'] = []
109
+ @profile['attributes'] = []
110
+ @profile['generator'] = {
111
+ 'name': 'inspec_tools',
112
+ 'version': VERSION
113
+ }
114
+ @profile['plaintext'] = @benchmark.plaintext.plaintext
115
+ @profile['status'] = "#{@benchmark.status} on #{@benchmark.release_date.release_date}"
116
+ @profile['reference_href'] = @benchmark.reference.href
117
+ @profile['reference_publisher'] = @benchmark.reference.dc_publisher
118
+ @profile['reference_source'] = @benchmark.reference.dc_source
119
+ end
120
+
121
+ def insert_controls
122
+ @benchmark.group.each do |group|
123
+ control = {}
124
+ control['id'] = group.id
125
+ control['title'] = group.rule.title
126
+ control['desc'] = group.rule.description.vuln_discussion.split('Satisfies: ')[0]
127
+ control['impact'] = Utils::InspecUtil.get_impact(group.rule.severity)
128
+ control['tags'] = {}
129
+ control['tags']['severity'] = Utils::InspecUtil.get_impact_string(control['impact'])
130
+ control['tags']['gtitle'] = group.title
131
+ control['tags']['satisfies'] = group.rule.description.vuln_discussion.split('Satisfies: ')[1].split(',').map(&:strip) if group.rule.description.vuln_discussion.split('Satisfies: ').length > 1
132
+ control['tags']['gid'] = group.id
133
+ control['tags']['rid'] = group.rule.id
134
+ control['tags']['stig_id'] = group.rule.version
135
+ control['tags']['fix_id'] = group.rule.fix.id
136
+ control['tags']['cci'] = group.rule.idents
137
+ control['tags']['nist'] = @cci_items.fetch_nists(group.rule.idents)
138
+ control['tags']['false_negatives'] = group.rule.description.false_negatives if group.rule.description.false_negatives != ''
139
+ control['tags']['false_positives'] = group.rule.description.false_positives if group.rule.description.false_positives != ''
140
+ control['tags']['documentable'] = group.rule.description.documentable if group.rule.description.documentable != ''
141
+ control['tags']['mitigations'] = group.rule.description.false_negatives if group.rule.description.mitigations != ''
142
+ control['tags']['severity_override_guidance'] = group.rule.description.severity_override_guidance if group.rule.description.severity_override_guidance != ''
143
+ control['tags']['potential_impacts'] = group.rule.description.potential_impacts if group.rule.description.potential_impacts != ''
144
+ control['tags']['third_party_tools'] = group.rule.description.third_party_tools if group.rule.description.third_party_tools != ''
145
+ control['tags']['mitigation_controls'] = group.rule.description.mitigation_controls if group.rule.description.mitigation_controls != ''
146
+ control['tags']['responsibility'] = group.rule.description.responsibility if group.rule.description.responsibility != ''
147
+ control['tags']['ia_controls'] = group.rule.description.ia_controls if group.rule.description.ia_controls != ''
148
+ control['tags']['check'] = group.rule.check.content
149
+ control['tags']['fix'] = group.rule.fixtext
150
+ @controls << control
151
+ end
152
+ @profile['controls'] = @controls
153
+ end
154
+ end
155
+ end