Skip to content

Commit b806308

Browse files
authored
Cleanup xlsx2inspec Process of Adding NIST and CIS Controls to Inspec Controls (#127)
1 parent f77b880 commit b806308

File tree

1 file changed

+58
-36
lines changed

1 file changed

+58
-36
lines changed

lib/inspec_tools/xlsx.rb

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@
1313
module InspecTools
1414
# Methods for converting from XLS to various formats
1515
class XLSXTool
16+
CIS_2_NIST_XLSX = Roo::Spreadsheet.open(File.join(File.dirname(__FILE__), '../data/NIST_Map_02052020_CIS_Controls_Version_7.1_Implementation_Groups_1.2.xlsx'))
17+
LATEST_NIST_REV = 'Rev_4'.freeze
18+
1619
def initialize(xlsx, mapping, name, verbose = false)
1720
@name = name
1821
@xlsx = xlsx
1922
@mapping = mapping
2023
@verbose = verbose
24+
@cis_to_nist = get_cis_to_nist_control_mapping(CIS_2_NIST_XLSX)
2125
end
2226

2327
def to_ckl
@@ -41,6 +45,18 @@ def to_inspec(control_prefix)
4145

4246
private
4347

48+
def get_cis_to_nist_control_mapping(spreadsheet)
49+
cis_to_nist = {}
50+
spreadsheet.sheet(3).each do |row|
51+
if row[3].is_a? Numeric
52+
cis_to_nist[row[3].to_s] = row[0]
53+
else
54+
cis2Nist[row[2].to_s] = row[0] unless (row[2] == '') || row[2].to_i.nil?
55+
end
56+
end
57+
cis_to_nist
58+
end
59+
4460
def insert_json_metadata
4561
@profile['name'] = @name
4662
@profile['title'] = 'InSpec Profile'
@@ -59,67 +75,73 @@ def insert_json_metadata
5975
end
6076

6177
def parse_cis_controls(control_prefix)
62-
cis2NistXls = Roo::Spreadsheet.open(File.join(File.dirname(__FILE__), "../data/NIST_Map_02052020_CIS_Controls_Version_7.1_Implementation_Groups_1.2.xlsx"))
63-
cis2Nist = {}
64-
cis2NistXls.sheet(3).each do |row|
65-
if row[3].is_a? Numeric
66-
cis2Nist[row[3].to_s] = row[0]
67-
else
68-
cis2Nist[row[2].to_s] = row[0] unless (row[2] == "") || (row[2].to_i.nil?)
69-
end
70-
end
71-
[ 1, 2 ].each do |level|
78+
[1, 2].each do |level|
7279
@xlsx.sheet(level).each_row_streaming do |row|
7380
if row[@mapping['control.id']].nil? || !/^\d+(\.?\d)*$/.match(row[@mapping['control.id']].formatted_value)
7481
next
7582
end
83+
7684
tag_pos = @mapping['control.tags']
7785
control = {}
7886
control['tags'] = {}
79-
control['id'] = control_prefix + '-' + row[@mapping['control.id']].formatted_value unless @mapping['control.id'].nil? || row[@mapping['control.id']].nil?
80-
control['title'] = row[@mapping['control.title']].formatted_value unless @mapping['control.title'].nil? || row[@mapping['control.title']].nil?
81-
control['desc'] = ""
82-
control['desc'] = row[@mapping['control.desc']].formatted_value unless row[@mapping['control.desc']].nil?
83-
control['tags']['rationale'] = row[tag_pos['rationale']].formatted_value unless row[tag_pos['rationale']].empty?
87+
control['id'] = control_prefix + '-' + row[@mapping['control.id']].formatted_value unless cell_empty?(@mapping['control.id']) || cell_empty?(row[@mapping['control.id']])
88+
control['title'] = row[@mapping['control.title']].formatted_value unless cell_empty?(@mapping['control.title']) || cell_empty?(row[@mapping['control.title']])
89+
control['desc'] = ''
90+
control['desc'] = row[@mapping['control.desc']].formatted_value unless cell_empty?(row[@mapping['control.desc']])
91+
control['tags']['rationale'] = row[tag_pos['rationale']].formatted_value unless cell_empty?(row[tag_pos['rationale']])
8492

8593
control['tags']['severity'] = level == 1 ? 'medium' : 'high'
8694
control['impact'] = Utils::InspecUtil.get_impact(control['tags']['severity'])
87-
control['tags']['ref'] = row[@mapping['control.ref']].formatted_value unless @mapping['control.ref'].nil? || row[@mapping['control.ref']].nil?
95+
control['tags']['ref'] = row[@mapping['control.ref']].formatted_value unless cell_empty?(@mapping['control.ref']) || cell_empty?(row[@mapping['control.ref']])
8896
control['tags']['cis_level'] = level unless level.nil?
8997

90-
unless row[tag_pos['cis_controls']].empty?
98+
unless cell_empty?(row[tag_pos['cis_controls']])
9199
# cis_control must be extracted from CIS control column via regex
92-
control = handle_cis_tags(control, row[tag_pos['cis_controls']].formatted_value.scan(/CONTROL:v(\d) (\d+)\.?(\d*)/))
100+
cis_tags_array = row[tag_pos['cis_controls']].formatted_value.scan(/CONTROL:v(\d) (\d+)\.?(\d*)/).flatten
101+
cis_tags = %i(revision section sub_section).zip(cis_tags_array).to_h
102+
control = apply_cis_and_nist_controls(control, cis_tags)
93103
end
94104

95-
control['tags']['cis_rid'] = row[@mapping['control.id']].formatted_value unless @mapping['control.id'].nil? || row[@mapping['control.id']].nil?
96-
control['tags']['check'] = row[tag_pos['check']].formatted_value unless tag_pos['check'].nil? || row[tag_pos['check']].empty?
97-
control['tags']['fix'] = row[tag_pos['fix']].formatted_value unless tag_pos['fix'].nil? || row[tag_pos['fix']].empty?
105+
control['tags']['cis_rid'] = row[@mapping['control.id']].formatted_value unless cell_empty?(@mapping['control.id']) || cell_empty?(row[@mapping['control.id']])
106+
control['tags']['check'] = row[tag_pos['check']].formatted_value unless cell_empty?(tag_pos['check']) || cell_empty?(row[tag_pos['check']])
107+
control['tags']['fix'] = row[tag_pos['fix']].formatted_value unless cell_empty?(tag_pos['fix']) || cell_empty?(row[tag_pos['fix']])
98108

99109
@controls << control
100110
end
101111
end
102112
end
103113

104-
def handle_cis_tags(control, cis_tags)
105-
control['tags']['cis_controls'] = []
106-
control['tags']['nist'] = []
114+
def cell_empty?(cell)
115+
return cell.empty? if cell.respond_to?(:empty?)
107116

108-
cis_tags.each do |cis_tag|
109-
if cis_tag[2].nil? || cis_tag[2] == ""
110-
control['tags']['cis_controls'] << cis_tag[1].to_s
111-
control['tags']['nist'] << cis2Nist[cis_tag[1]]
112-
else
113-
control['tags']['cis_controls'] << cis_tag[1].to_s + "." + cis_tag[2].to_s
114-
control['tags']['nist'] << cis2Nist[cis_tag[1].to_s + "." + cis_tag[2].to_s]
115-
end
116-
end
117+
cell.nil?
118+
end
119+
120+
def apply_cis_and_nist_controls(control, cis_tags)
121+
control['tags']['cis_controls'], control['tags']['nist'] = [], []
117122

118-
if not control['tags']['nist'].nil?
119-
control['tags']['nist'] << "Rev_4"
123+
if cis_tags[:sub_section].nil? || cis_tags[:sub_section].blank?
124+
control['tags']['cis_controls'] << cis_tags[:section]
125+
control['tags']['nist'] << get_nist_control_for_cis([cis_tags[:section]])
126+
else
127+
control['tags']['cis_controls'] << "#{cis_tags[:section]}.#{cis_tags[:sub_section]}"
128+
control['tags']['nist'] << get_nist_control_for_cis([cis_tags[:section], cis_tags[:sub_section]])
120129
end
121-
control['tags']['cis_controls'] << "Rev_" + cis_tags.first[0] unless cis_tags[0].nil?
130+
131+
control['tags']['nist'] << LATEST_NIST_REV unless control['tags']['nist'].nil?
132+
control['tags']['cis_controls'] << "Rev_#{cis_tags[:revision]}" unless cis_tags[:revision].nil?
133+
122134
control
123135
end
136+
137+
def get_nist_control_for_cis(section, sub_section=nil)
138+
return @cis_to_nist[section] if sub_section.nil?
139+
140+
@cis_to_nist["#{section}.#{sub_section}"]
141+
end
124142
end
125143
end
144+
145+
# rubocop:enable Metrics/AbcSize
146+
# rubocop:enable Metrics/PerceivedComplexity
147+
# rubocop:enable Metrics/CyclomaticComplexity

0 commit comments

Comments
 (0)