inspec_tools 2.0.7 → 2.3.3
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 +4 -4
- data/README.md +20 -12
- data/Rakefile +9 -1
- data/lib/happy_mapper_tools/benchmark.rb +83 -0
- data/lib/happy_mapper_tools/stig_attributes.rb +10 -1
- data/lib/inspec_tools/csv.rb +42 -39
- data/lib/inspec_tools/generate_map.rb +35 -0
- data/lib/inspec_tools/inspec.rb +19 -91
- data/lib/inspec_tools/plugin_cli.rb +22 -53
- data/lib/inspec_tools/summary.rb +108 -76
- data/lib/inspec_tools/xccdf.rb +12 -3
- data/lib/inspec_tools/xlsx_tool.rb +2 -1
- data/lib/utilities/cci_xml.rb +13 -0
- data/lib/utilities/inspec_util.rb +12 -76
- 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 +42 -36
@@ -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
|