inspec_tools 2.0.3 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -10
- data/Rakefile +82 -8
- data/lib/data/cis_to_nist_critical_controls +0 -0
- data/lib/data/cis_to_nist_mapping +0 -0
- data/lib/data/rubocop.yml +4 -0
- data/lib/happy_mapper_tools/benchmark.rb +83 -0
- data/lib/happy_mapper_tools/stig_attributes.rb +68 -48
- data/lib/inspec_tools/csv.rb +26 -33
- data/lib/inspec_tools/generate_map.rb +35 -0
- data/lib/inspec_tools/inspec.rb +19 -91
- data/lib/inspec_tools/pdf.rb +2 -13
- data/lib/inspec_tools/plugin_cli.rb +14 -25
- data/lib/inspec_tools/xccdf.rb +1 -0
- data/lib/inspec_tools/xlsx_tool.rb +4 -16
- data/lib/utilities/cci_xml.rb +13 -0
- data/lib/utilities/cis_to_nist.rb +11 -0
- data/lib/utilities/inspec_util.rb +16 -75
- data/lib/utilities/mapping_validator.rb +10 -0
- data/lib/utilities/xccdf/from_inspec.rb +89 -0
- data/lib/utilities/xccdf/to_xccdf.rb +388 -0
- data/lib/utilities/xccdf/xccdf_score.rb +116 -0
- metadata +47 -27
- data/CHANGELOG.md +0 -704
- data/lib/data/NIST_Map_02052020_CIS_Controls_Version_7.1_Implementation_Groups_1.2.xlsx +0 -0
- data/lib/data/NIST_Map_09212017B_CSC-CIS_Critical_Security_Controls_VER_6.1_Excel_9.1.2016.xlsx +0 -0
- data/lib/utilities/extract_nist_cis_mapping.rb +0 -57
@@ -0,0 +1,10 @@
|
|
1
|
+
module Utils
|
2
|
+
class MappingValidator
|
3
|
+
def self.validate(mapping)
|
4
|
+
return mapping unless mapping.include?('control.tags') && mapping['control.tags'].include?('nist')
|
5
|
+
|
6
|
+
raise "Mapping file should not contain an entry for 'control.tags.nist'.
|
7
|
+
NIST tags will be autogenerated via 'control.tags.cci'."
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Utils
|
2
|
+
# Data transformation from Inspec result output into usable data for XCCDF conversions.
|
3
|
+
class FromInspec
|
4
|
+
DATA_NOT_FOUND_MESSAGE = 'N/A'.freeze
|
5
|
+
|
6
|
+
# Convert raw Inspec result json into format acceptable for XCCDF transformation.
|
7
|
+
def parse_data_for_xccdf(json) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
8
|
+
data = {}
|
9
|
+
|
10
|
+
controls = []
|
11
|
+
if json['profiles'].nil?
|
12
|
+
controls = json['controls']
|
13
|
+
elsif json['profiles'].length == 1
|
14
|
+
controls = json['profiles'].last['controls']
|
15
|
+
else
|
16
|
+
json['profiles'].each do |profile|
|
17
|
+
controls.concat(profile['controls'])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
c_data = {}
|
21
|
+
|
22
|
+
controls.each do |control|
|
23
|
+
c_id = control['id'].to_sym
|
24
|
+
c_data[c_id] = {}
|
25
|
+
c_data[c_id]['id'] = control['id'] || DATA_NOT_FOUND_MESSAGE
|
26
|
+
c_data[c_id]['title'] = control['title'] if control['title'] # Optional attribute
|
27
|
+
c_data[c_id]['desc'] = control['desc'] || DATA_NOT_FOUND_MESSAGE
|
28
|
+
c_data[c_id]['severity'] = control['tags']['severity'] || 'unknown'
|
29
|
+
c_data[c_id]['gid'] = control['tags']['gid'] || control['id']
|
30
|
+
c_data[c_id]['gtitle'] = control['tags']['gtitle'] if control['tags']['gtitle'] # Optional attribute
|
31
|
+
c_data[c_id]['gdescription'] = control['tags']['gdescription'] if control['tags']['gdescription'] # Optional attribute
|
32
|
+
c_data[c_id]['rid'] = control['tags']['rid'] || 'r_' + c_data[c_id]['gid']
|
33
|
+
c_data[c_id]['rversion'] = control['tags']['rversion'] if control['tags']['rversion'] # Optional attribute
|
34
|
+
c_data[c_id]['rweight'] = control['tags']['rweight'] if control['tags']['rweight'] # Optional attribute where N/A is not schema compliant
|
35
|
+
c_data[c_id]['stig_id'] = control['tags']['stig_id'] || DATA_NOT_FOUND_MESSAGE
|
36
|
+
c_data[c_id]['cci'] = control['tags']['cci'] if control['tags']['cci'] # Optional attribute
|
37
|
+
c_data[c_id]['nist'] = control['tags']['nist'] || ['unmapped']
|
38
|
+
c_data[c_id]['check'] = control['tags']['check'] || DATA_NOT_FOUND_MESSAGE
|
39
|
+
c_data[c_id]['checkref'] = control['tags']['checkref'] || DATA_NOT_FOUND_MESSAGE
|
40
|
+
c_data[c_id]['fix'] = control['tags']['fix'] || DATA_NOT_FOUND_MESSAGE
|
41
|
+
c_data[c_id]['fix_id'] = control['tags']['fix_id'] if control['tags']['fix_id'] # Optional attribute where N/A is not schema compliant
|
42
|
+
c_data[c_id]['rationale'] = control['tags']['rationale'] || DATA_NOT_FOUND_MESSAGE
|
43
|
+
c_data[c_id]['cis_family'] = control['tags']['cis_family'] || DATA_NOT_FOUND_MESSAGE
|
44
|
+
c_data[c_id]['cis_rid'] = control['tags']['cis_rid'] || DATA_NOT_FOUND_MESSAGE
|
45
|
+
c_data[c_id]['cis_level'] = control['tags']['cis_level'] || DATA_NOT_FOUND_MESSAGE
|
46
|
+
c_data[c_id]['impact'] = control['impact'].to_s || DATA_NOT_FOUND_MESSAGE
|
47
|
+
c_data[c_id]['code'] = control['code'].to_s || DATA_NOT_FOUND_MESSAGE
|
48
|
+
c_data[c_id]['results'] = parse_results_for_xccdf(control['results']) if control['results']
|
49
|
+
end
|
50
|
+
|
51
|
+
data['controls'] = c_data.values
|
52
|
+
data['profiles'] = parse_profiles_for_xccdf(json['profiles'])
|
53
|
+
data['status'] = 'success'
|
54
|
+
data['inspec_version'] = json['version']
|
55
|
+
data
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Convert profile information for result processing
|
61
|
+
# @param profiles [Array[Hash]] - The profiles section of the JSON output
|
62
|
+
def parse_profiles_for_xccdf(profiles)
|
63
|
+
return [] unless profiles
|
64
|
+
|
65
|
+
profiles.map do |profile|
|
66
|
+
data = {}
|
67
|
+
data['name'] = profile['name']
|
68
|
+
data['version'] = profile['version']
|
69
|
+
data
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Convert the test result data to a parseable Hash for downstream processing
|
74
|
+
# @param results [Array[Hash]] - The results section of the JSON output
|
75
|
+
def parse_results_for_xccdf(results)
|
76
|
+
results.map do |result|
|
77
|
+
data = {}
|
78
|
+
data['status'] = result['status']
|
79
|
+
data['code_desc'] = result['code_desc']
|
80
|
+
data['run_time'] = result['run_time']
|
81
|
+
data['start_time'] = result['start_time']
|
82
|
+
data['resource'] = result['resource']
|
83
|
+
data['message'] = result['message']
|
84
|
+
data['skip_message'] = result['skip_message']
|
85
|
+
data
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,388 @@
|
|
1
|
+
require_relative 'xccdf_score'
|
2
|
+
|
3
|
+
module Utils
|
4
|
+
# Data conversions for Inspec output into XCCDF format.
|
5
|
+
class ToXCCDF # rubocop:disable Metrics/ClassLength
|
6
|
+
# @param attribute [Hash] XCCDF supplemental attributes
|
7
|
+
# @param data [Hash] Converted Inspec output data
|
8
|
+
def initialize(attribute, data)
|
9
|
+
@attribute = attribute
|
10
|
+
@data = data
|
11
|
+
@benchmark = HappyMapperTools::Benchmark::Benchmark.new
|
12
|
+
end
|
13
|
+
|
14
|
+
# Build entire XML document and produce final output
|
15
|
+
# @param metadata [Hash] Data representing a system under scan
|
16
|
+
def to_xml(metadata)
|
17
|
+
build_benchmark_header
|
18
|
+
build_groups
|
19
|
+
# Only populate results if a target is defined so that conformant XML is produced.
|
20
|
+
@benchmark.testresult = build_test_results(metadata) if metadata['fqdn']
|
21
|
+
@benchmark.to_xml
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
# Sets top level XCCDF Benchmark attributes
|
27
|
+
def build_benchmark_header
|
28
|
+
@benchmark.title = @attribute['benchmark.title']
|
29
|
+
@benchmark.id = @attribute['benchmark.id']
|
30
|
+
@benchmark.description = @attribute['benchmark.description']
|
31
|
+
@benchmark.version = @attribute['benchmark.version']
|
32
|
+
@benchmark.xmlns = 'http://checklists.nist.gov/xccdf/1.1'
|
33
|
+
|
34
|
+
@benchmark.status = HappyMapperTools::Benchmark::Status.new
|
35
|
+
@benchmark.status.status = @attribute['benchmark.status']
|
36
|
+
@benchmark.status.date = @attribute['benchmark.status.date']
|
37
|
+
|
38
|
+
if @attribute['benchmark.notice.id']
|
39
|
+
@benchmark.notice = HappyMapperTools::Benchmark::Notice.new
|
40
|
+
@benchmark.notice.id = @attribute['benchmark.notice.id']
|
41
|
+
end
|
42
|
+
|
43
|
+
if @attribute['benchmark.plaintext'] || @attribute['benchmark.plaintext.id']
|
44
|
+
@benchmark.plaintext = HappyMapperTools::Benchmark::Plaintext.new
|
45
|
+
@benchmark.plaintext.plaintext = @attribute['benchmark.plaintext']
|
46
|
+
@benchmark.plaintext.id = @attribute['benchmark.plaintext.id']
|
47
|
+
end
|
48
|
+
|
49
|
+
@benchmark.reference = HappyMapperTools::Benchmark::ReferenceBenchmark.new
|
50
|
+
@benchmark.reference.href = @attribute['reference.href']
|
51
|
+
@benchmark.reference.dc_publisher = @attribute['reference.dc.publisher']
|
52
|
+
@benchmark.reference.dc_source = @attribute['reference.dc.source']
|
53
|
+
end
|
54
|
+
|
55
|
+
# Translate join of Inspec results and input attributes to XCCDF Groups
|
56
|
+
def build_groups # rubocop:disable Metrics/AbcSize
|
57
|
+
group_array = []
|
58
|
+
@data['controls'].each do |control|
|
59
|
+
group = HappyMapperTools::Benchmark::Group.new
|
60
|
+
group.id = control['id']
|
61
|
+
group.title = control['gtitle']
|
62
|
+
group.description = "<GroupDescription>#{control['gdescription']}</GroupDescription>" if control['gdescription']
|
63
|
+
|
64
|
+
group.rule = HappyMapperTools::Benchmark::Rule.new
|
65
|
+
group.rule.id = control['rid']
|
66
|
+
group.rule.severity = control['severity']
|
67
|
+
group.rule.weight = control['rweight']
|
68
|
+
group.rule.version = control['rversion']
|
69
|
+
group.rule.title = control['title'].tr("\n", ' ') if control['title']
|
70
|
+
group.rule.description = "<VulnDiscussion>#{control['desc'].tr("\n", ' ')}</VulnDiscussion><FalsePositives></FalsePositives><FalseNegatives></FalseNegatives><Documentable>false</Documentable><Mitigations></Mitigations><SeverityOverrideGuidance></SeverityOverrideGuidance><PotentialImpacts></PotentialImpacts><ThirdPartyTools></ThirdPartyTools><MitigationControl></MitigationControl><Responsibility></Responsibility><IAControls></IAControls>"
|
71
|
+
|
72
|
+
if ['reference.dc.publisher', 'reference.dc.title', 'reference.dc.subject', 'reference.dc.type', 'reference.dc.identifier'].any? { |a| @attribute.key?(a) }
|
73
|
+
group.rule.reference = build_rule_reference
|
74
|
+
end
|
75
|
+
|
76
|
+
group.rule.ident = build_rule_idents(control['cci']) if control['cci']
|
77
|
+
|
78
|
+
group.rule.fixtext = HappyMapperTools::Benchmark::Fixtext.new
|
79
|
+
group.rule.fixtext.fixref = control['fix_id']
|
80
|
+
group.rule.fixtext.fixtext = control['fix']
|
81
|
+
|
82
|
+
group.rule.fix = build_rule_fix(control['fix_id']) if control['fix_id']
|
83
|
+
|
84
|
+
group.rule.check = HappyMapperTools::Benchmark::Check.new
|
85
|
+
group.rule.check.system = control['checkref']
|
86
|
+
|
87
|
+
# content_ref is optional for schema compliance
|
88
|
+
if @attribute['content_ref.name'] || @attribute['content_ref.href']
|
89
|
+
group.rule.check.content_ref = HappyMapperTools::Benchmark::ContentRef.new
|
90
|
+
group.rule.check.content_ref.name = @attribute['content_ref.name']
|
91
|
+
group.rule.check.content_ref.href = @attribute['content_ref.href']
|
92
|
+
end
|
93
|
+
|
94
|
+
group.rule.check.content = control['check']
|
95
|
+
|
96
|
+
group_array << group
|
97
|
+
end
|
98
|
+
@benchmark.group = group_array
|
99
|
+
end
|
100
|
+
|
101
|
+
# Construct a Benchmark Testresult from Inspec data. This must be called after all XML processing has occurred for profiles
|
102
|
+
# and groups.
|
103
|
+
# @param metadata [Hash]
|
104
|
+
# @return [TestResult]
|
105
|
+
def build_test_results(metadata)
|
106
|
+
test_result = HappyMapperTools::Benchmark::TestResult.new
|
107
|
+
test_result.version = @benchmark.version
|
108
|
+
populate_remark(test_result)
|
109
|
+
populate_target_facts(test_result, metadata)
|
110
|
+
populate_identity(test_result, metadata)
|
111
|
+
populate_results(test_result)
|
112
|
+
populate_score(test_result, @benchmark.group)
|
113
|
+
|
114
|
+
test_result
|
115
|
+
end
|
116
|
+
|
117
|
+
# Contruct a Rule / RuleResult fix element with the provided id.
|
118
|
+
def build_rule_fix(fix_id)
|
119
|
+
HappyMapperTools::Benchmark::Fix.new.tap { |f| f.id = fix_id }
|
120
|
+
end
|
121
|
+
|
122
|
+
# Construct rule identifiers for rule
|
123
|
+
# @param idents [Array]
|
124
|
+
def build_rule_idents(idents)
|
125
|
+
raise "#{idents} is not an Array type." unless idents.is_a?(Array)
|
126
|
+
|
127
|
+
# Each rule identifier is a different element
|
128
|
+
idents.map do |identifier|
|
129
|
+
ident = HappyMapperTools::Benchmark::Ident.new
|
130
|
+
ident.system = 'https://public.cyber.mil/stigs/cci/'
|
131
|
+
ident.ident = identifier
|
132
|
+
ident
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Contruct a Rule reference element
|
137
|
+
def build_rule_reference
|
138
|
+
reference = HappyMapperTools::Benchmark::ReferenceGroup.new
|
139
|
+
reference.dc_publisher = @attribute['reference.dc.publisher']
|
140
|
+
reference.dc_title = @attribute['reference.dc.title']
|
141
|
+
reference.dc_subject = @attribute['reference.dc.subject']
|
142
|
+
reference.dc_type = @attribute['reference.dc.type']
|
143
|
+
reference.dc_identifier = @attribute['reference.dc.identifier']
|
144
|
+
reference
|
145
|
+
end
|
146
|
+
|
147
|
+
# Create a remark with contextual information about the Inspec version and profiles used
|
148
|
+
# @param result [HappyMapperTools::Benchmark::TestResult]
|
149
|
+
def populate_remark(result)
|
150
|
+
result.remark = "Results created using Inspec version #{@data['inspec_version']}.\n#{@data['profiles'].map { |p| "Profile: #{p['name']} Version: #{p['version']}" }.join("\n")}"
|
151
|
+
end
|
152
|
+
|
153
|
+
# Create all target specific information.
|
154
|
+
# @param result [HappyMapperTools::Benchmark::TestResult]
|
155
|
+
# @param metadata [Hash]
|
156
|
+
def populate_target_facts(result, metadata)
|
157
|
+
result.target = metadata['fqdn']
|
158
|
+
result.target_address = metadata['ip'] if metadata['ip']
|
159
|
+
|
160
|
+
all_facts = []
|
161
|
+
|
162
|
+
if metadata['mac']
|
163
|
+
fact = HappyMapperTools::Benchmark::Fact.new
|
164
|
+
fact.name = 'urn:xccdf:fact:asset:identifier:mac'
|
165
|
+
fact.type = 'string'
|
166
|
+
fact.fact = metadata['mac']
|
167
|
+
all_facts << fact
|
168
|
+
end
|
169
|
+
|
170
|
+
if metadata['ip']
|
171
|
+
fact = HappyMapperTools::Benchmark::Fact.new
|
172
|
+
fact.name = 'urn:xccdf:fact:asset:identifier:ipv4'
|
173
|
+
fact.type = 'string'
|
174
|
+
fact.fact = metadata['ip']
|
175
|
+
all_facts << fact
|
176
|
+
end
|
177
|
+
|
178
|
+
return unless all_facts.size.nonzero?
|
179
|
+
|
180
|
+
facts = HappyMapperTools::Benchmark::TargetFact.new
|
181
|
+
facts.fact = all_facts
|
182
|
+
result.target_facts = facts
|
183
|
+
end
|
184
|
+
|
185
|
+
# Build out the TestResult given all the control and result data.
|
186
|
+
def populate_results(test_result)
|
187
|
+
# Note: id is not an XCCDF 1.2 compliant identifier and will need to be updated when that support is added.
|
188
|
+
test_result.id = 'result_1'
|
189
|
+
test_result.starttime = run_start_time
|
190
|
+
test_result.endtime = run_end_time
|
191
|
+
|
192
|
+
# Build out individual results
|
193
|
+
all_rule_result = []
|
194
|
+
|
195
|
+
@data['controls'].each do |control|
|
196
|
+
next if control['results'].empty?
|
197
|
+
|
198
|
+
control_results =
|
199
|
+
control['results'].map do |result|
|
200
|
+
populate_rule_result(control, result, xccdf_status(result['status'], control['impact']))
|
201
|
+
end
|
202
|
+
|
203
|
+
# Consolidate results into single rule result do to lack of multiple=true attribute on Rule.
|
204
|
+
# 1. Select the unified result status
|
205
|
+
selected_status = control_results.reduce(control_results.first.result) { |f_status, rule_result| xccdf_and_result(f_status, rule_result.result) }
|
206
|
+
|
207
|
+
# 2. Only choose results with that status
|
208
|
+
# 3. Combine those results
|
209
|
+
all_rule_result << combine_results(control_results.select { |r| r.result == selected_status })
|
210
|
+
end
|
211
|
+
|
212
|
+
test_result.rule_result = all_rule_result
|
213
|
+
test_result
|
214
|
+
end
|
215
|
+
|
216
|
+
# Create rule-result from the control and Inspec result information
|
217
|
+
def populate_rule_result(control, result, result_status)
|
218
|
+
rule_result = HappyMapperTools::Benchmark::RuleResultType.new
|
219
|
+
|
220
|
+
rule_result.idref = control['rid']
|
221
|
+
rule_result.severity = control['severity']
|
222
|
+
rule_result.time = end_time(result['start_time'], result['run_time'])
|
223
|
+
rule_result.weight = control['rweight']
|
224
|
+
|
225
|
+
rule_result.result = result_status
|
226
|
+
rule_result.message = result_message(result, result_status) if result_message(result, result_status)
|
227
|
+
rule_result.instance = result['code_desc']
|
228
|
+
|
229
|
+
rule_result.ident = build_rule_idents(control['cci']) if control['cci']
|
230
|
+
|
231
|
+
# Fix information is only necessary when there are failed tests
|
232
|
+
rule_result.fix = build_rule_fix(control['fix_id']) if control['fix_id'] && result_status == 'fail'
|
233
|
+
|
234
|
+
rule_result.check = HappyMapperTools::Benchmark::Check.new
|
235
|
+
rule_result.check.system = control['checkref']
|
236
|
+
rule_result.check.content = result['code_desc']
|
237
|
+
rule_result
|
238
|
+
end
|
239
|
+
|
240
|
+
# Combines rule results with the same result into a single rule result.
|
241
|
+
def combine_results(rule_results) # rubocop:disable Metrics/AbcSize
|
242
|
+
return rule_results.first if rule_results.size == 1
|
243
|
+
|
244
|
+
# Can combine, result, idents (duplicate, take first instance), instance - combine into an array removing duplicates
|
245
|
+
# check.content - Only one value allowed, combine by joining with line feed. Prior to, make sure all values are unique.
|
246
|
+
|
247
|
+
rule_result = HappyMapperTools::Benchmark::RuleResultType.new
|
248
|
+
rule_result.idref = rule_results.first.idref
|
249
|
+
rule_result.severity = rule_results.first.severity
|
250
|
+
# Take latest time
|
251
|
+
rule_result.time = rule_results.reduce(rule_results.first.time) { |time, r| time > r.time ? time : r.time }
|
252
|
+
rule_result.weight = rule_results.first.weight
|
253
|
+
|
254
|
+
rule_result.result = rule_results.first.result
|
255
|
+
rule_result.message = rule_results.reduce([]) { |messages, r| r.message ? messages.push(r.message) : messages }
|
256
|
+
rule_result.instance = rule_results.reduce([]) { |instances, r| r.instance ? instances.push(r.instance) : instances }.join("\n")
|
257
|
+
|
258
|
+
rule_result.ident = rule_results.first.ident
|
259
|
+
rule_result.fix = rule_results.first.fix
|
260
|
+
|
261
|
+
if rule_results.first.check
|
262
|
+
rule_result.check = HappyMapperTools::Benchmark::Check.new
|
263
|
+
rule_result.check.system = rule_results.first.check.system
|
264
|
+
rule_result.check.content = rule_results.map { |r| r.check.content }.join("\n")
|
265
|
+
end
|
266
|
+
|
267
|
+
rule_result
|
268
|
+
end
|
269
|
+
|
270
|
+
# Add information about the the account and organization executing the tests.
|
271
|
+
def populate_identity(test_result, metadata)
|
272
|
+
if metadata['identity']
|
273
|
+
test_result.identity = HappyMapperTools::Benchmark::IdentityType.new
|
274
|
+
test_result.identity.authenticated = true
|
275
|
+
test_result.identity.identity = metadata['identity']['identity']
|
276
|
+
test_result.identity.privileged = metadata['identity']['privileged']
|
277
|
+
end
|
278
|
+
|
279
|
+
test_result.organization = metadata['organization'] if metadata['organization']
|
280
|
+
end
|
281
|
+
|
282
|
+
# Return the earliest time of execution.
|
283
|
+
def run_start_time
|
284
|
+
@data['controls'].map { |control| control['results'].map { |result| DateTime.parse(result['start_time']) } }.flatten.min
|
285
|
+
end
|
286
|
+
|
287
|
+
# Return the latest time of execution accounting for Inspec duration.
|
288
|
+
def run_end_time
|
289
|
+
end_times =
|
290
|
+
@data['controls'].map do |control|
|
291
|
+
control['results'].map { |result| end_time(result['start_time'], result['run_time']) }
|
292
|
+
end
|
293
|
+
|
294
|
+
end_times.flatten.max
|
295
|
+
end
|
296
|
+
|
297
|
+
# Calculate an end time given a start time and second duration
|
298
|
+
def end_time(start, duration)
|
299
|
+
DateTime.parse(start) + (duration / (24*60*60))
|
300
|
+
end
|
301
|
+
|
302
|
+
# Map the Inspec result status to appropriate XCCDF test result status.
|
303
|
+
# XCCDF options include: pass, fail, error, unknown, notapplicable, notchecked, notselected, informational, fixed
|
304
|
+
#
|
305
|
+
# @param inspec_status [String] The reported Inspec status from an individual test
|
306
|
+
# @param impact [String] A value of 0.0 - 1.0
|
307
|
+
# @return A valid Inspec status.
|
308
|
+
def xccdf_status(inspec_status, impact)
|
309
|
+
# Currently, there is no good way to map an Inspec result status to one of XCCDF status unknown or notselected.
|
310
|
+
case inspec_status
|
311
|
+
when 'failed'
|
312
|
+
'fail'
|
313
|
+
when 'passed'
|
314
|
+
'pass'
|
315
|
+
when 'skipped'
|
316
|
+
if impact.to_f.zero?
|
317
|
+
'notapplicable'
|
318
|
+
else
|
319
|
+
'notchecked'
|
320
|
+
end
|
321
|
+
else
|
322
|
+
# In the event Inspec adds a new unaccounted for status, mapping to XCCDF unknown.
|
323
|
+
'unknown'
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# When more than one result occurs for a rule and the specification does not declare multiple, the result must be combined.
|
328
|
+
# This determines the appropriate result to be selected when there are two to compare.
|
329
|
+
# @param one [String] A rule-result status
|
330
|
+
# @param two [String] A rule-result status
|
331
|
+
# @return The result of the AND operation.
|
332
|
+
def xccdf_and_result(one, two) # rubocop:disable Metrics/CyclomaticComplexity
|
333
|
+
# From XCCDF specification truth table
|
334
|
+
# P = pass
|
335
|
+
# F = fail
|
336
|
+
# U = unknown
|
337
|
+
# E = error
|
338
|
+
# N = notapplicable
|
339
|
+
# K = notchecked
|
340
|
+
# S = notselected
|
341
|
+
# I = informational
|
342
|
+
|
343
|
+
case one
|
344
|
+
when 'pass'
|
345
|
+
%w{fail unknown}.any? { |s| s == two } ? two : one
|
346
|
+
when 'fail'
|
347
|
+
one
|
348
|
+
when 'unknown'
|
349
|
+
two == 'fail' ? two : one
|
350
|
+
when 'notapplicable'
|
351
|
+
%w{pass fail unknown}.any? { |s| s == two } ? two : one
|
352
|
+
when 'notchecked'
|
353
|
+
%w{pass fail unknown notapplicable}.any? { |s| s == two } ? two : one
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# Builds the message information for rule results
|
358
|
+
# @param result [Hash] A single Inspec result
|
359
|
+
# @param xccdf_status [String] the xccdf calculated result status for the provided result
|
360
|
+
def result_message(result, xccdf_status)
|
361
|
+
return unless result['message'] || result['skip_message']
|
362
|
+
|
363
|
+
message = HappyMapperTools::Benchmark::MessageType.new
|
364
|
+
# Including the code of the check and the resulting message if there is one.
|
365
|
+
message.message = "#{result['code_desc'] ? result['code_desc'] + "\n\n" : ''}#{result['message'] || result['skip_message']}"
|
366
|
+
message.severity = result_message_severity(xccdf_status)
|
367
|
+
message
|
368
|
+
end
|
369
|
+
|
370
|
+
# All rule-result messages require a defined severity. This determines a value to use based upon the result XCCDF status.
|
371
|
+
def result_message_severity(xccdf_status)
|
372
|
+
case xccdf_status
|
373
|
+
when 'fail'
|
374
|
+
'error'
|
375
|
+
when 'notapplicable'
|
376
|
+
'warning'
|
377
|
+
else
|
378
|
+
'info'
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
# Set scores for all 4 required/recommended scoring systems.
|
383
|
+
def populate_score(test_result, groups)
|
384
|
+
score = Utils::XCCDFScore.new(groups, test_result.rule_result)
|
385
|
+
test_result.score = [score.default_score, score.flat_score, score.flat_unweighted_score, score.absolute_score]
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|