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.
- checksums.yaml +7 -0
- data/LICENSE.md +15 -0
- data/README.md +373 -0
- data/Rakefile +96 -0
- data/exe/inspec_tools +14 -0
- data/lib/data/README.TXT +25 -0
- data/lib/data/U_CCI_List.xml +38403 -0
- data/lib/data/attributes.yml +23 -0
- data/lib/data/cci2html.xsl +136 -0
- data/lib/data/cis_to_nist_critical_controls +0 -0
- data/lib/data/cis_to_nist_mapping +0 -0
- data/lib/data/mapping.yml +17 -0
- data/lib/data/rubocop.yml +4 -0
- data/lib/data/stig.csv +1 -0
- data/lib/data/threshold.yaml +83 -0
- data/lib/exceptions/impact_input_error.rb +6 -0
- data/lib/exceptions/severity_input_error.rb +6 -0
- data/lib/happy_mapper_tools/benchmark.rb +161 -0
- data/lib/happy_mapper_tools/cci_attributes.rb +66 -0
- data/lib/happy_mapper_tools/stig_attributes.rb +216 -0
- data/lib/happy_mapper_tools/stig_checklist.rb +99 -0
- data/lib/inspec_tools.rb +17 -0
- data/lib/inspec_tools/ckl.rb +20 -0
- data/lib/inspec_tools/cli.rb +31 -0
- data/lib/inspec_tools/csv.rb +101 -0
- data/lib/inspec_tools/help.rb +9 -0
- data/lib/inspec_tools/help/compliance.md +7 -0
- data/lib/inspec_tools/help/csv2inspec.md +5 -0
- data/lib/inspec_tools/help/inspec2ckl.md +5 -0
- data/lib/inspec_tools/help/inspec2csv.md +5 -0
- data/lib/inspec_tools/help/inspec2xccdf.md +5 -0
- data/lib/inspec_tools/help/pdf2inspec.md +6 -0
- data/lib/inspec_tools/help/summary.md +5 -0
- data/lib/inspec_tools/help/xccdf2inspec.md +5 -0
- data/lib/inspec_tools/inspec.rb +331 -0
- data/lib/inspec_tools/pdf.rb +125 -0
- data/lib/inspec_tools/plugin.rb +15 -0
- data/lib/inspec_tools/plugin_cli.rb +275 -0
- data/lib/inspec_tools/summary.rb +126 -0
- data/lib/inspec_tools/version.rb +8 -0
- data/lib/inspec_tools/xccdf.rb +156 -0
- data/lib/inspec_tools/xlsx_tool.rb +135 -0
- data/lib/inspec_tools_plugin.rb +7 -0
- data/lib/overrides/false_class.rb +5 -0
- data/lib/overrides/nil_class.rb +5 -0
- data/lib/overrides/object.rb +5 -0
- data/lib/overrides/string.rb +5 -0
- data/lib/overrides/true_class.rb +5 -0
- data/lib/utilities/cis_to_nist.rb +11 -0
- data/lib/utilities/csv_util.rb +14 -0
- data/lib/utilities/extract_pdf_text.rb +20 -0
- data/lib/utilities/inspec_util.rb +441 -0
- data/lib/utilities/parser.rb +373 -0
- data/lib/utilities/text_cleaner.rb +69 -0
- metadata +359 -0
@@ -0,0 +1,156 @@
|
|
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(/(<|<)#{tag}(>|>)/, "$#{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']['security_override_guidance'] = group.rule.description.security_override_guidance if group.rule.description.security_override_guidance != ''
|
144
|
+
control['tags']['potential_impacts'] = group.rule.description.potential_impacts if group.rule.description.potential_impacts != ''
|
145
|
+
control['tags']['third_party_tools'] = group.rule.description.third_party_tools if group.rule.description.third_party_tools != ''
|
146
|
+
control['tags']['mitigation_controls'] = group.rule.description.mitigation_controls if group.rule.description.mitigation_controls != ''
|
147
|
+
control['tags']['responsibility'] = group.rule.description.responsibility if group.rule.description.responsibility != ''
|
148
|
+
control['tags']['ia_controls'] = group.rule.description.ia_controls if group.rule.description.ia_controls != ''
|
149
|
+
control['tags']['check'] = group.rule.check.content
|
150
|
+
control['tags']['fix'] = group.rule.fixtext
|
151
|
+
@controls << control
|
152
|
+
end
|
153
|
+
@profile['controls'] = @controls
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'inspec-objects'
|
3
|
+
require 'word_wrap'
|
4
|
+
require 'yaml'
|
5
|
+
require 'digest'
|
6
|
+
|
7
|
+
require_relative '../utilities/inspec_util'
|
8
|
+
require_relative '../utilities/cis_to_nist'
|
9
|
+
|
10
|
+
# rubocop:disable Metrics/AbcSize
|
11
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
12
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
13
|
+
|
14
|
+
module InspecTools
|
15
|
+
# Methods for converting from XLS to various formats
|
16
|
+
class XLSXTool
|
17
|
+
LATEST_NIST_REV = 'Rev_4'.freeze
|
18
|
+
|
19
|
+
def initialize(xlsx, mapping, name, verbose = false)
|
20
|
+
@name = name
|
21
|
+
@xlsx = xlsx
|
22
|
+
@mapping = mapping
|
23
|
+
@verbose = verbose
|
24
|
+
@cis_to_nist = Utils::CisToNist.get_mapping('cis_to_nist_mapping')
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_ckl
|
28
|
+
# TODO
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_xccdf
|
32
|
+
# TODO
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_inspec(control_prefix)
|
36
|
+
@controls = []
|
37
|
+
@cci_xml = nil
|
38
|
+
@profile = {}
|
39
|
+
insert_json_metadata
|
40
|
+
parse_cis_controls(control_prefix)
|
41
|
+
@profile['controls'] = @controls
|
42
|
+
@profile['sha256'] = Digest::SHA256.hexdigest @profile.to_s
|
43
|
+
@profile
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def insert_json_metadata
|
49
|
+
@profile['name'] = @name
|
50
|
+
@profile['title'] = 'InSpec Profile'
|
51
|
+
@profile['maintainer'] = 'The Authors'
|
52
|
+
@profile['copyright'] = 'The Authors'
|
53
|
+
@profile['copyright_email'] = 'you@example.com'
|
54
|
+
@profile['license'] = 'Apache-2.0'
|
55
|
+
@profile['summary'] = 'An InSpec Compliance Profile'
|
56
|
+
@profile['version'] = '0.1.0'
|
57
|
+
@profile['supports'] = []
|
58
|
+
@profile['attributes'] = []
|
59
|
+
@profile['generator'] = {
|
60
|
+
'name': 'inspec_tools',
|
61
|
+
'version': ::InspecTools::VERSION
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_cis_controls(control_prefix)
|
66
|
+
[1, 2].each do |level|
|
67
|
+
@xlsx.sheet(level).each_row_streaming do |row|
|
68
|
+
if row[@mapping['control.id']].nil? || !/^\d+(\.?\d)*$/.match(row[@mapping['control.id']].formatted_value)
|
69
|
+
next
|
70
|
+
end
|
71
|
+
|
72
|
+
tag_pos = @mapping['control.tags']
|
73
|
+
control = {}
|
74
|
+
control['tags'] = {}
|
75
|
+
control['id'] = control_prefix + '-' + row[@mapping['control.id']].formatted_value unless cell_empty?(@mapping['control.id']) || cell_empty?(row[@mapping['control.id']])
|
76
|
+
control['title'] = row[@mapping['control.title']].formatted_value unless cell_empty?(@mapping['control.title']) || cell_empty?(row[@mapping['control.title']])
|
77
|
+
control['desc'] = ''
|
78
|
+
control['desc'] = row[@mapping['control.desc']].formatted_value unless cell_empty?(row[@mapping['control.desc']])
|
79
|
+
control['tags']['rationale'] = row[tag_pos['rationale']].formatted_value unless cell_empty?(row[tag_pos['rationale']])
|
80
|
+
|
81
|
+
control['tags']['severity'] = level == 1 ? 'medium' : 'high'
|
82
|
+
control['impact'] = Utils::InspecUtil.get_impact(control['tags']['severity'])
|
83
|
+
control['tags']['ref'] = row[@mapping['control.ref']].formatted_value unless cell_empty?(@mapping['control.ref']) || cell_empty?(row[@mapping['control.ref']])
|
84
|
+
control['tags']['cis_level'] = level unless level.nil?
|
85
|
+
|
86
|
+
unless cell_empty?(row[tag_pos['cis_controls']])
|
87
|
+
# cis_control must be extracted from CIS control column via regex
|
88
|
+
cis_tags_array = row[tag_pos['cis_controls']].formatted_value.scan(/CONTROL:v(\d) (\d+)\.?(\d*)/).flatten
|
89
|
+
cis_tags = %i(revision section sub_section).zip(cis_tags_array).to_h
|
90
|
+
control = apply_cis_and_nist_controls(control, cis_tags)
|
91
|
+
end
|
92
|
+
|
93
|
+
control['tags']['cis_rid'] = row[@mapping['control.id']].formatted_value unless cell_empty?(@mapping['control.id']) || cell_empty?(row[@mapping['control.id']])
|
94
|
+
control['tags']['check'] = row[tag_pos['check']].formatted_value unless cell_empty?(tag_pos['check']) || cell_empty?(row[tag_pos['check']])
|
95
|
+
control['tags']['fix'] = row[tag_pos['fix']].formatted_value unless cell_empty?(tag_pos['fix']) || cell_empty?(row[tag_pos['fix']])
|
96
|
+
|
97
|
+
@controls << control
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def cell_empty?(cell)
|
103
|
+
return cell.empty? if cell.respond_to?(:empty?)
|
104
|
+
|
105
|
+
cell.nil?
|
106
|
+
end
|
107
|
+
|
108
|
+
def apply_cis_and_nist_controls(control, cis_tags)
|
109
|
+
control['tags']['cis_controls'], control['tags']['nist'] = [], []
|
110
|
+
|
111
|
+
if cis_tags[:sub_section].nil? || cis_tags[:sub_section].blank?
|
112
|
+
control['tags']['cis_controls'] << cis_tags[:section]
|
113
|
+
control['tags']['nist'] << get_nist_control_for_cis(cis_tags[:section])
|
114
|
+
else
|
115
|
+
control['tags']['cis_controls'] << "#{cis_tags[:section]}.#{cis_tags[:sub_section]}"
|
116
|
+
control['tags']['nist'] << get_nist_control_for_cis(cis_tags[:section], cis_tags[:sub_section])
|
117
|
+
end
|
118
|
+
|
119
|
+
control['tags']['nist'] << LATEST_NIST_REV unless control['tags']['nist'].nil?
|
120
|
+
control['tags']['cis_controls'] << "Rev_#{cis_tags[:revision]}" unless cis_tags[:revision].nil?
|
121
|
+
|
122
|
+
control
|
123
|
+
end
|
124
|
+
|
125
|
+
def get_nist_control_for_cis(section, sub_section = nil)
|
126
|
+
return @cis_to_nist[section] if sub_section.nil?
|
127
|
+
|
128
|
+
@cis_to_nist["#{section}.#{sub_section}"]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# rubocop:enable Metrics/AbcSize
|
134
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
135
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Utils
|
2
|
+
class CisToNist
|
3
|
+
def self.get_mapping(mapping_file)
|
4
|
+
path = File.expand_path(File.join(File.expand_path(__dir__), '..', 'data', mapping_file))
|
5
|
+
raise "CIS to NIST control mapping does not exist at #{path}. Has it been generated?" unless File.exist?(path)
|
6
|
+
|
7
|
+
mapping = File.open(path)
|
8
|
+
Marshal.load(mapping)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'pdf-reader'
|
2
|
+
|
3
|
+
module Util
|
4
|
+
class ExtractPdfText
|
5
|
+
def initialize(pdf)
|
6
|
+
@pdf = pdf
|
7
|
+
@extracted_text = ''
|
8
|
+
read_text
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :extracted_text
|
12
|
+
|
13
|
+
def read_text
|
14
|
+
reader = PDF::Reader.new(@pdf.path)
|
15
|
+
reader.pages.each do |page|
|
16
|
+
@extracted_text += page.text
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,441 @@
|
|
1
|
+
require 'inspec-objects'
|
2
|
+
require 'word_wrap'
|
3
|
+
require 'pp'
|
4
|
+
require 'uri'
|
5
|
+
require 'net/http'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'exceptions/impact_input_error'
|
8
|
+
require 'exceptions/severity_input_error'
|
9
|
+
require 'overrides/false_class'
|
10
|
+
require 'overrides/true_class'
|
11
|
+
require 'overrides/nil_class'
|
12
|
+
require 'overrides/object'
|
13
|
+
require 'overrides/string'
|
14
|
+
require 'rubocop'
|
15
|
+
|
16
|
+
# rubocop:disable Metrics/ClassLength
|
17
|
+
# rubocop:disable Metrics/AbcSize
|
18
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
19
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
20
|
+
# rubocop:disable Metrics/MethodLength
|
21
|
+
|
22
|
+
module Utils
|
23
|
+
class InspecUtil
|
24
|
+
DATA_NOT_FOUND_MESSAGE = 'N/A'.freeze
|
25
|
+
WIDTH = 80
|
26
|
+
IMPACT_SCORES = {
|
27
|
+
'none' => 0.0,
|
28
|
+
'low' => 0.1,
|
29
|
+
'medium' => 0.4,
|
30
|
+
'high' => 0.7,
|
31
|
+
'critical' => 0.9
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
def self.parse_data_for_xccdf(json)
|
35
|
+
data = {}
|
36
|
+
|
37
|
+
controls = []
|
38
|
+
if json['profiles'].nil?
|
39
|
+
controls = json['controls']
|
40
|
+
elsif json['profiles'].length == 1
|
41
|
+
controls = json['profiles'].last['controls']
|
42
|
+
else
|
43
|
+
json['profiles'].each do |profile|
|
44
|
+
controls.concat(profile['controls'])
|
45
|
+
end
|
46
|
+
end
|
47
|
+
c_data = {}
|
48
|
+
|
49
|
+
controls.each do |control|
|
50
|
+
c_id = control['id'].to_sym
|
51
|
+
c_data[c_id] = {}
|
52
|
+
c_data[c_id]['id'] = control['id'] || DATA_NOT_FOUND_MESSAGE
|
53
|
+
c_data[c_id]['title'] = control['title'] || DATA_NOT_FOUND_MESSAGE
|
54
|
+
c_data[c_id]['desc'] = control['desc'] || DATA_NOT_FOUND_MESSAGE
|
55
|
+
c_data[c_id]['severity'] = control['tags']['severity'] || DATA_NOT_FOUND_MESSAGE
|
56
|
+
c_data[c_id]['gid'] = control['tags']['gid'] || DATA_NOT_FOUND_MESSAGE
|
57
|
+
c_data[c_id]['gtitle'] = control['tags']['gtitle'] || DATA_NOT_FOUND_MESSAGE
|
58
|
+
c_data[c_id]['gdescription'] = control['tags']['gdescription'] || DATA_NOT_FOUND_MESSAGE
|
59
|
+
c_data[c_id]['rid'] = control['tags']['rid'] || DATA_NOT_FOUND_MESSAGE
|
60
|
+
c_data[c_id]['rversion'] = control['tags']['rversion'] || DATA_NOT_FOUND_MESSAGE
|
61
|
+
c_data[c_id]['rweight'] = control['tags']['rweight'] || DATA_NOT_FOUND_MESSAGE
|
62
|
+
c_data[c_id]['stig_id'] = control['tags']['stig_id'] || DATA_NOT_FOUND_MESSAGE
|
63
|
+
c_data[c_id]['cci'] = control['tags']['cci'] || DATA_NOT_FOUND_MESSAGE
|
64
|
+
c_data[c_id]['nist'] = control['tags']['nist'] || ['unmapped']
|
65
|
+
c_data[c_id]['check'] = control['tags']['check'] || DATA_NOT_FOUND_MESSAGE
|
66
|
+
c_data[c_id]['checkref'] = control['tags']['checkref'] || DATA_NOT_FOUND_MESSAGE
|
67
|
+
c_data[c_id]['fix'] = control['tags']['fix'] || DATA_NOT_FOUND_MESSAGE
|
68
|
+
c_data[c_id]['fixref'] = control['tags']['fixref'] || DATA_NOT_FOUND_MESSAGE
|
69
|
+
c_data[c_id]['fix_id'] = control['tags']['fix_id'] || DATA_NOT_FOUND_MESSAGE
|
70
|
+
c_data[c_id]['rationale'] = control['tags']['rationale'] || DATA_NOT_FOUND_MESSAGE
|
71
|
+
c_data[c_id]['cis_family'] = control['tags']['cis_family'] || DATA_NOT_FOUND_MESSAGE
|
72
|
+
c_data[c_id]['cis_rid'] = control['tags']['cis_rid'] || DATA_NOT_FOUND_MESSAGE
|
73
|
+
c_data[c_id]['cis_level'] = control['tags']['cis_level'] || DATA_NOT_FOUND_MESSAGE
|
74
|
+
c_data[c_id]['impact'] = control['impact'].to_s || DATA_NOT_FOUND_MESSAGE
|
75
|
+
c_data[c_id]['code'] = control['code'].to_s || DATA_NOT_FOUND_MESSAGE
|
76
|
+
end
|
77
|
+
|
78
|
+
data['controls'] = c_data.values
|
79
|
+
data['status'] = 'success'
|
80
|
+
data
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.parse_data_for_ckl(json)
|
84
|
+
data = {}
|
85
|
+
|
86
|
+
# Parse for inspec profile results json
|
87
|
+
json['profiles'].each do |profile|
|
88
|
+
profile['controls'].each do |control|
|
89
|
+
c_id = control['id'].to_sym
|
90
|
+
data[c_id] = {}
|
91
|
+
|
92
|
+
data[c_id][:vuln_num] = control['id'] unless control['id'].nil?
|
93
|
+
data[c_id][:rule_title] = control['title'] unless control['title'].nil?
|
94
|
+
data[c_id][:vuln_discuss] = control['desc'] unless control['desc'].nil?
|
95
|
+
|
96
|
+
unless control['tags'].nil?
|
97
|
+
data[c_id][:severity] = control['tags']['severity'] unless control['tags']['severity'].nil?
|
98
|
+
data[c_id][:gid] = control['tags']['gid'] unless control['tags']['gid'].nil?
|
99
|
+
data[c_id][:group_title] = control['tags']['gtitle'] unless control['tags']['gtitle'].nil?
|
100
|
+
data[c_id][:rule_id] = control['tags']['rid'] unless control['tags']['rid'].nil?
|
101
|
+
data[c_id][:rule_ver] = control['tags']['stig_id'] unless control['tags']['stig_id'].nil?
|
102
|
+
data[c_id][:cci_ref] = control['tags']['cci'] unless control['tags']['cci'].nil?
|
103
|
+
data[c_id][:nist] = control['tags']['nist'].join(' ') unless control['tags']['nist'].nil?
|
104
|
+
end
|
105
|
+
|
106
|
+
if control['descriptions'].respond_to?(:find)
|
107
|
+
data[c_id][:check_content] = control['descriptions'].find { |c| c['label'] == 'fix' }&.dig('data')
|
108
|
+
data[c_id][:fix_text] = control['descriptions'].find { |c| c['label'] == 'check' }&.dig('data')
|
109
|
+
end
|
110
|
+
|
111
|
+
data[c_id][:impact] = control['impact'].to_s unless control['impact'].nil?
|
112
|
+
data[c_id][:profile_name] = profile['name'].to_s unless profile['name'].nil?
|
113
|
+
data[c_id][:profile_shasum] = profile['sha256'].to_s unless profile['sha256'].nil?
|
114
|
+
|
115
|
+
data[c_id][:status] = []
|
116
|
+
data[c_id][:message] = []
|
117
|
+
|
118
|
+
if control.key?('results')
|
119
|
+
control['results'].each do |result|
|
120
|
+
if !result['backtrace'].nil?
|
121
|
+
result['status'] = 'error'
|
122
|
+
end
|
123
|
+
data[c_id][:status].push(result['status'])
|
124
|
+
data[c_id][:message].push("SKIPPED -- Test: #{result['code_desc']}\nMessage: #{result['skip_message']}\n") if result['status'] == 'skipped'
|
125
|
+
data[c_id][:message].push("FAILED -- Test: #{result['code_desc']}\nMessage: #{result['message']}\n") if result['status'] == 'failed'
|
126
|
+
data[c_id][:message].push("PASS -- #{result['code_desc']}\n") if result['status'] == 'passed'
|
127
|
+
data[c_id][:message].push("PROFILE_ERROR -- Test: #{result['code_desc']}\nMessage: #{result['backtrace']}\n") if result['status'] == 'error'
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
if data[c_id][:impact].to_f.zero?
|
132
|
+
data[c_id][:message].unshift("NOT_APPLICABLE -- Description: #{control['desc']}\n\n")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
data
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.get_platform(json)
|
140
|
+
json['profiles'].find { |profile| !profile[:platform].nil? }
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.to_dotted_hash(hash, recursive_key = '')
|
144
|
+
hash.each_with_object({}) do |(k, v), ret|
|
145
|
+
key = recursive_key + k.to_s
|
146
|
+
if v.is_a? Hash
|
147
|
+
ret.merge! to_dotted_hash(v, key + '.')
|
148
|
+
else
|
149
|
+
ret[key] = v
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.control_status(control, for_summary = false)
|
155
|
+
status_list = control[:status].uniq
|
156
|
+
if control[:impact].to_f.zero?
|
157
|
+
'Not_Applicable'
|
158
|
+
elsif status_list.include?('failed')
|
159
|
+
'Open'
|
160
|
+
elsif status_list.include?('passed')
|
161
|
+
'NotAFinding'
|
162
|
+
elsif status_list.include?('error') && for_summary
|
163
|
+
'Profile_Error'
|
164
|
+
else
|
165
|
+
# profile skipped or profile error
|
166
|
+
'Not_Reviewed'
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def self.control_finding_details(control, control_clk_status)
|
171
|
+
result = "One or more of the automated tests failed or was inconclusive for the control \n\n #{control[:message].sort.join}" if control_clk_status == 'Open'
|
172
|
+
result = "All Automated tests passed for the control \n\n #{control[:message].join}" if control_clk_status == 'NotAFinding'
|
173
|
+
result = "Automated test skipped due to known accepted condition in the control : \n\n#{control[:message].join}" if control_clk_status == 'Not_Reviewed'
|
174
|
+
result = "Justification: \n #{control[:message].join}" if control_clk_status == 'Not_Applicable'
|
175
|
+
result = 'No test available or some test errors occurred for this control' if control_clk_status == 'Profile_Error'
|
176
|
+
result
|
177
|
+
end
|
178
|
+
|
179
|
+
# @!method get_impact(severity)
|
180
|
+
# Takes in the STIG severity tag and converts it to the InSpec #{impact}
|
181
|
+
# control tag.
|
182
|
+
# At the moment the mapping is static, so that:
|
183
|
+
# high => 0.7
|
184
|
+
# medium => 0.5
|
185
|
+
# low => 0.3
|
186
|
+
# @param severity [String] the string value you want to map to an InSpec
|
187
|
+
# 'impact' level.
|
188
|
+
#
|
189
|
+
# @return impact [Float] the impact level level mapped to the XCCDF severity
|
190
|
+
# mapped to a float between 0.0 - 1.0.
|
191
|
+
#
|
192
|
+
# @todo Allow for the user to pass in a hash for the desired mapping of text
|
193
|
+
# values to numbers or to override our hard coded values.
|
194
|
+
#
|
195
|
+
def self.get_impact(severity, use_cvss_terms: true)
|
196
|
+
return float_to_impact(severity, use_cvss_terms) if severity.is_a?(Float)
|
197
|
+
|
198
|
+
return string_to_impact(severity, use_cvss_terms) if severity.is_a?(String)
|
199
|
+
|
200
|
+
raise SeverityInputError, "'#{severity}' is not a valid severity value. It should be a Float between 0.0 and " \
|
201
|
+
'1.0 or one of the approved keywords.'
|
202
|
+
end
|
203
|
+
|
204
|
+
private_class_method def self.float_to_impact(severity, use_cvss_terms)
|
205
|
+
unless severity.between?(0, 1)
|
206
|
+
raise SeverityInputError, "'#{severity}' is not a valid severity value. It should be a Float between 0.0 and " \
|
207
|
+
'1.0 or one of the approved keywords.'
|
208
|
+
end
|
209
|
+
|
210
|
+
if severity <= 0.01
|
211
|
+
0.0 # Informative
|
212
|
+
elsif severity < 0.4
|
213
|
+
0.3 # Low Impact
|
214
|
+
elsif severity < 0.7
|
215
|
+
0.5 # Medium Impact
|
216
|
+
elsif severity < 0.9 || use_cvss_terms
|
217
|
+
0.7 # High Impact
|
218
|
+
else
|
219
|
+
1.0 # Critical Controls
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
private_class_method def self.string_to_impact(severity, use_cvss_terms)
|
224
|
+
if /none|na|n\/a|not[_|(\s*)]?applicable/i.match?(severity)
|
225
|
+
impact = 0.0 # Informative
|
226
|
+
elsif /low|cat(egory)?\s*(iii|3)/i.match?(severity)
|
227
|
+
impact = 0.3 # Low Impact
|
228
|
+
elsif /med(ium)?|cat(egory)?\s*(ii|2)/i.match?(severity)
|
229
|
+
impact = 0.5 # Medium Impact
|
230
|
+
elsif /high|cat(egory)?\s*(i|1)/i.match?(severity)
|
231
|
+
impact = 0.7 # High Impact
|
232
|
+
elsif /crit(ical)?|severe/i.match?(severity)
|
233
|
+
impact = 1.0 # Critical Controls
|
234
|
+
else
|
235
|
+
raise SeverityInputError, "'#{severity}' is not a valid severity value. It should be a Float between 0.0 and " \
|
236
|
+
'1.0 or one of the approved keywords.'
|
237
|
+
end
|
238
|
+
|
239
|
+
impact == 1.0 && use_cvss_terms ? 0.7 : impact
|
240
|
+
end
|
241
|
+
|
242
|
+
def self.get_impact_string(impact, use_cvss_terms: true)
|
243
|
+
return if impact.nil?
|
244
|
+
|
245
|
+
value = impact.to_f
|
246
|
+
unless value.between?(0, 1)
|
247
|
+
raise ImpactInputError, "'#{value}' is not a valid impact score. Valid impact scores: [0.0 - 1.0]."
|
248
|
+
end
|
249
|
+
|
250
|
+
IMPACT_SCORES.reverse_each do |name, impact_score|
|
251
|
+
if name == 'critical' && value >= impact_score && use_cvss_terms
|
252
|
+
return 'high'
|
253
|
+
elsif value >= impact_score
|
254
|
+
return name
|
255
|
+
else
|
256
|
+
next
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def self.unpack_inspec_json(directory, inspec_json, separated, output_format)
|
262
|
+
if directory == 'id'
|
263
|
+
directory = inspec_json['name']
|
264
|
+
end
|
265
|
+
controls = generate_controls(inspec_json)
|
266
|
+
unpack_profile(directory || 'profile', controls, separated, output_format || 'json')
|
267
|
+
create_inspec_yml(directory || 'profile', inspec_json)
|
268
|
+
create_license(directory || 'profile', inspec_json)
|
269
|
+
create_readme_md(directory || 'profile', inspec_json)
|
270
|
+
end
|
271
|
+
|
272
|
+
private_class_method def self.wrap(str, width = WIDTH)
|
273
|
+
str.gsub!("desc \"\n ", 'desc "')
|
274
|
+
str.gsub!(/\\r/, "\n")
|
275
|
+
str.gsub!(/\\n/, "\n")
|
276
|
+
|
277
|
+
WordWrap.ww(str.to_s, width)
|
278
|
+
end
|
279
|
+
|
280
|
+
private_class_method def self.generate_controls(inspec_json)
|
281
|
+
controls = []
|
282
|
+
inspec_json['controls'].each do |json_control|
|
283
|
+
control = ::Inspec::Object::Control.new
|
284
|
+
if (defined? control.desc).nil?
|
285
|
+
control.descriptions[:default] = json_control['desc']
|
286
|
+
control.descriptions[:rationale] = json_control['tags']['rationale']
|
287
|
+
control.descriptions[:check] = json_control['tags']['check']
|
288
|
+
control.descriptions[:fix] = json_control['tags']['fix']
|
289
|
+
else
|
290
|
+
control.desc = json_control['desc']
|
291
|
+
end
|
292
|
+
control.id = json_control['id']
|
293
|
+
control.title = json_control['title']
|
294
|
+
control.impact = get_impact(json_control['impact'])
|
295
|
+
|
296
|
+
# json_control['tags'].each do |tag|
|
297
|
+
# control.add_tag(Inspec::Object::Tag.new(tag.key, tag.value)
|
298
|
+
# end
|
299
|
+
|
300
|
+
control.add_tag(::Inspec::Object::Tag.new('severity', json_control['tags']['severity']))
|
301
|
+
control.add_tag(::Inspec::Object::Tag.new('gtitle', json_control['tags']['gtitle']))
|
302
|
+
control.add_tag(::Inspec::Object::Tag.new('satisfies', json_control['tags']['satisfies'])) if json_control['tags']['satisfies']
|
303
|
+
control.add_tag(::Inspec::Object::Tag.new('gid', json_control['tags']['gid']))
|
304
|
+
control.add_tag(::Inspec::Object::Tag.new('rid', json_control['tags']['rid']))
|
305
|
+
control.add_tag(::Inspec::Object::Tag.new('stig_id', json_control['tags']['stig_id']))
|
306
|
+
control.add_tag(::Inspec::Object::Tag.new('fix_id', json_control['tags']['fix_id']))
|
307
|
+
control.add_tag(::Inspec::Object::Tag.new('cci', json_control['tags']['cci']))
|
308
|
+
control.add_tag(::Inspec::Object::Tag.new('nist', json_control['tags']['nist']))
|
309
|
+
control.add_tag(::Inspec::Object::Tag.new('cis_level', json_control['tags']['cis_level'])) unless json_control['tags']['cis_level'].blank?
|
310
|
+
control.add_tag(::Inspec::Object::Tag.new('cis_controls', json_control['tags']['cis_controls'])) unless json_control['tags']['cis_controls'].blank?
|
311
|
+
control.add_tag(::Inspec::Object::Tag.new('cis_rid', json_control['tags']['cis_rid'])) unless json_control['tags']['cis_rid'].blank?
|
312
|
+
control.add_tag(::Inspec::Object::Tag.new('ref', json_control['tags']['ref'])) unless json_control['tags']['ref'].blank?
|
313
|
+
control.add_tag(::Inspec::Object::Tag.new('false_negatives', json_control['tags']['false_negatives'])) unless json_control['tags']['false_positives'].blank?
|
314
|
+
control.add_tag(::Inspec::Object::Tag.new('false_positives', json_control['tags']['false_positives'])) unless json_control['tags']['false_positives'].blank?
|
315
|
+
control.add_tag(::Inspec::Object::Tag.new('documentable', json_control['tags']['documentable'])) unless json_control['tags']['documentable'].blank?
|
316
|
+
control.add_tag(::Inspec::Object::Tag.new('mitigations', json_control['tags']['mitigations'])) unless json_control['tags']['mitigations'].blank?
|
317
|
+
control.add_tag(::Inspec::Object::Tag.new('severity_override_guidance', json_control['tags']['severity_override_guidance'])) unless json_control['tags']['severity_override_guidance'].blank?
|
318
|
+
control.add_tag(::Inspec::Object::Tag.new('security_override_guidance', json_control['tags']['security_override_guidance'])) unless json_control['tags']['security_override_guidance'].blank?
|
319
|
+
control.add_tag(::Inspec::Object::Tag.new('potential_impacts', json_control['tags']['potential_impacts'])) unless json_control['tags']['potential_impacts'].blank?
|
320
|
+
control.add_tag(::Inspec::Object::Tag.new('third_party_tools', json_control['tags']['third_party_tools'])) unless json_control['tags']['third_party_tools'].blank?
|
321
|
+
control.add_tag(::Inspec::Object::Tag.new('mitigation_controls', json_control['tags']['mitigation_controls'])) unless json_control['tags']['mitigation_controls'].blank?
|
322
|
+
control.add_tag(::Inspec::Object::Tag.new('responsibility', json_control['tags']['responsibility'])) unless json_control['tags']['responsibility'].blank?
|
323
|
+
control.add_tag(::Inspec::Object::Tag.new('ia_controls', json_control['tags']['ia_controls'])) unless json_control['tags']['ia_controls'].blank?
|
324
|
+
|
325
|
+
controls << control
|
326
|
+
end
|
327
|
+
controls
|
328
|
+
end
|
329
|
+
|
330
|
+
# @!method print_benchmark_info(info)
|
331
|
+
# writes benchmark info to profile inspec.yml file
|
332
|
+
#
|
333
|
+
private_class_method def self.create_inspec_yml(directory, inspec_json)
|
334
|
+
benchmark_info =
|
335
|
+
"name: #{inspec_json['name']}\n" \
|
336
|
+
"title: #{inspec_json['title']}\n" \
|
337
|
+
"maintainer: #{inspec_json['maintainer']}\n" \
|
338
|
+
"copyright: #{inspec_json['copyright']}\n" \
|
339
|
+
"copyright_email: #{inspec_json['copyright_email']}\n" \
|
340
|
+
"license: #{inspec_json['license']}\n" \
|
341
|
+
"summary: #{inspec_json['summary']}\n" \
|
342
|
+
"version: #{inspec_json['version']}\n"
|
343
|
+
|
344
|
+
myfile = File.new("#{directory}/inspec.yml", 'w')
|
345
|
+
myfile.puts benchmark_info
|
346
|
+
end
|
347
|
+
|
348
|
+
private_class_method def self.create_license(directory, inspec_json)
|
349
|
+
license_content = ''
|
350
|
+
if !inspec_json['license'].nil?
|
351
|
+
begin
|
352
|
+
response = Net::HTTP.get_response(URI(inspec_json['license']))
|
353
|
+
if response.code == '200'
|
354
|
+
license_content = response.body
|
355
|
+
else
|
356
|
+
license_content = inspec_json['license']
|
357
|
+
end
|
358
|
+
rescue StandardError => _e
|
359
|
+
license_content = inspec_json['license']
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
myfile = File.new("#{directory}/LICENSE", 'w')
|
364
|
+
myfile.puts license_content
|
365
|
+
end
|
366
|
+
|
367
|
+
private_class_method def self.create_readme_md(directory, inspec_json)
|
368
|
+
readme_contents =
|
369
|
+
"\# #{inspec_json['title']}\n" \
|
370
|
+
"#{inspec_json['summary']}\n" \
|
371
|
+
"---\n" \
|
372
|
+
"Name: #{inspec_json['name']}\n" \
|
373
|
+
"Author: #{inspec_json['maintainer']}\n" \
|
374
|
+
"Status: #{inspec_json['status']}\n" \
|
375
|
+
"Copyright: #{inspec_json['copyright']}\n" \
|
376
|
+
"Copyright Email: #{inspec_json['copyright_email']}\n" \
|
377
|
+
"Version: #{inspec_json['version']}\n" \
|
378
|
+
"#{inspec_json['plaintext']}\n" \
|
379
|
+
"Reference: #{inspec_json['reference_href']}\n" \
|
380
|
+
"Reference by: #{inspec_json['reference_publisher']}\n" \
|
381
|
+
"Reference source: #{inspec_json['reference_source']}\n"
|
382
|
+
|
383
|
+
myfile = File.new("#{directory}/README.md", 'w')
|
384
|
+
myfile.puts readme_contents
|
385
|
+
end
|
386
|
+
|
387
|
+
private_class_method def self.unpack_profile(directory, controls, separated, output_format)
|
388
|
+
FileUtils.rm_rf(directory) if Dir.exist?(directory)
|
389
|
+
Dir.mkdir directory unless Dir.exist?(directory)
|
390
|
+
Dir.mkdir "#{directory}/controls" unless Dir.exist?("#{directory}/controls")
|
391
|
+
Dir.mkdir "#{directory}/libraries" unless Dir.exist?("#{directory}/libraries")
|
392
|
+
if separated
|
393
|
+
if output_format == 'ruby'
|
394
|
+
controls.each do |control|
|
395
|
+
file_name = control.id.to_s
|
396
|
+
myfile = File.new("#{directory}/controls/#{file_name}.rb", 'w')
|
397
|
+
myfile.puts "# encoding: UTF-8\n\n"
|
398
|
+
myfile.puts wrap(control.to_ruby, WIDTH) + "\n"
|
399
|
+
myfile.close
|
400
|
+
end
|
401
|
+
else
|
402
|
+
controls.each do |control|
|
403
|
+
file_name = control.id.to_s
|
404
|
+
myfile = File.new("#{directory}/controls/#{file_name}.rb", 'w')
|
405
|
+
PP.pp(control.to_hash, myfile)
|
406
|
+
myfile.close
|
407
|
+
end
|
408
|
+
end
|
409
|
+
else
|
410
|
+
myfile = File.new("#{directory}/controls/controls.rb", 'w')
|
411
|
+
if output_format == 'ruby'
|
412
|
+
controls.each do |control|
|
413
|
+
myfile.puts "# encoding: UTF-8\n\n"
|
414
|
+
myfile.puts wrap(control.to_ruby.gsub('"', "\'"), WIDTH) + "\n"
|
415
|
+
end
|
416
|
+
else
|
417
|
+
controls.each do |control|
|
418
|
+
if (defined? control.desc).nil?
|
419
|
+
control.descriptions[:default].strip!
|
420
|
+
else
|
421
|
+
control.desc.strip!
|
422
|
+
end
|
423
|
+
|
424
|
+
PP.pp(control.to_hash, myfile)
|
425
|
+
end
|
426
|
+
end
|
427
|
+
myfile.close
|
428
|
+
end
|
429
|
+
config_store = ::RuboCop::ConfigStore.new
|
430
|
+
config_store.options_config = File.join(File.dirname(__FILE__), '../data/rubocop.yml')
|
431
|
+
rubocop = ::RuboCop::Runner.new({ auto_correct: true }, config_store)
|
432
|
+
rubocop.run([directory])
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
# rubocop:enable Metrics/ClassLength
|
438
|
+
# rubocop:enable Metrics/AbcSize
|
439
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
440
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
441
|
+
# rubocop:enable Metrics/MethodLength
|