1
|
|
|
# Copyright 2022, Red Hat, Inc. |
2
|
|
|
# SPDX-License-Identifier: LGPL-2.1-or-later |
3
|
|
|
|
4
|
1 |
|
import logging |
5
|
1 |
|
from pathlib import Path |
6
|
|
|
|
7
|
1 |
|
from lxml import etree |
8
|
|
|
|
9
|
1 |
|
from .exceptions import NotSupportedReportingFormat |
10
|
1 |
|
from .namespaces import NAMESPACES |
11
|
1 |
|
from .oval_and_cpe_tree_builder import OVALAndCPETreeBuilder |
12
|
1 |
|
from .parsers import GroupParser, ReportParser, RuleParser |
13
|
|
|
|
14
|
1 |
|
SCHEMAS_DIR = Path(__file__).parent / "schemas" |
15
|
1 |
|
ARF_SCHEMAS_PATH = 'arf/1.1/asset-reporting-format_1.1.0.xsd' |
16
|
1 |
|
XCCDF_1_2_SCHEMAS_PATH = 'xccdf/1.2/xccdf_1.2.xsd' |
17
|
|
|
|
18
|
|
|
|
19
|
1 |
|
class SCAPResultsParser(): |
20
|
1 |
|
def __init__(self, data): |
21
|
1 |
|
self.root = etree.XML(data) |
22
|
1 |
|
self.ref_values = self._get_ref_values() |
23
|
1 |
|
self._validate_xccdf_or_arf() |
24
|
|
|
|
25
|
1 |
|
def _validate_xccdf_or_arf(self): |
26
|
1 |
|
is_valid_arf = self.validate(ARF_SCHEMAS_PATH) |
27
|
1 |
|
is_valid_xccdf_1_2 = self.validate(XCCDF_1_2_SCHEMAS_PATH) |
28
|
|
|
|
29
|
1 |
|
if not is_valid_arf and not is_valid_xccdf_1_2: |
30
|
1 |
|
raise NotSupportedReportingFormat( |
31
|
|
|
"The given input isn't a valid ARF report or XCCDF report!" |
32
|
|
|
) |
33
|
1 |
|
if is_valid_xccdf_1_2: |
34
|
1 |
|
logging.warning(("The given input is the XCCDF report," |
35
|
|
|
" some information will not appear in the report." |
36
|
|
|
" Use the ARF report for the complete report." |
37
|
|
|
)) |
38
|
1 |
|
self._log_info_about_input_report_type(is_valid_arf, is_valid_xccdf_1_2) |
39
|
|
|
|
40
|
1 |
|
@staticmethod |
41
|
1 |
|
def _log_info_about_input_report_type(is_valid_arf, is_valid_xccdf_1_2): |
42
|
1 |
|
if is_valid_arf: |
43
|
1 |
|
logging.info("The given input is a valid ARF report.") |
44
|
1 |
|
if is_valid_xccdf_1_2: |
45
|
1 |
|
logging.info("The given input is a valid XCCDF 1.2 report.") |
46
|
|
|
|
47
|
1 |
|
def validate(self, xsd_path): |
48
|
1 |
|
xsd_path = str(SCHEMAS_DIR / xsd_path) |
49
|
1 |
|
xmlschema_doc = etree.parse(xsd_path) |
50
|
1 |
|
xmlschema = etree.XMLSchema(xmlschema_doc) |
51
|
1 |
|
return xmlschema.validate(self.root) |
52
|
|
|
|
53
|
1 |
|
def _get_ref_values(self): |
54
|
1 |
|
return { |
55
|
|
|
ref_value.get("idref"): ref_value.text if ref_value.text is not None else "" |
56
|
|
|
for ref_value in self.root.findall('.//xccdf:set-value', NAMESPACES) |
57
|
|
|
} |
58
|
|
|
|
59
|
1 |
|
@staticmethod |
60
|
1 |
|
def _debug_show_rules(rules): |
61
|
1 |
|
for rule_id, rule in rules.items(): |
62
|
1 |
|
logging.debug(rule_id) |
63
|
1 |
|
logging.debug(rule) |
64
|
|
|
|
65
|
1 |
|
def _get_benchmark_element(self): |
66
|
1 |
|
benchmark_el = self.root.find(".//xccdf:Benchmark", NAMESPACES) |
67
|
1 |
|
if "Benchmark" in self.root.tag: |
68
|
1 |
|
benchmark_el = self.root |
69
|
1 |
|
return benchmark_el |
70
|
|
|
|
71
|
1 |
|
@staticmethod |
72
|
1 |
|
def _get_oval_definition_references(rules): |
73
|
1 |
|
references = [] |
74
|
1 |
|
for rule in rules.values(): |
75
|
1 |
|
if rule.oval_reference is not None: |
76
|
1 |
|
references.append(rule.oval_reference) |
77
|
1 |
|
return set(tuple(references)) |
78
|
|
|
|
79
|
1 |
|
@staticmethod |
80
|
1 |
|
def _get_map_oval_var_to_value(test_results_el): |
81
|
1 |
|
return { |
82
|
|
|
check_export.attrib.get("export-name"): check_export.attrib.get("value-id", "") |
83
|
|
|
for check_export in test_results_el.findall( |
84
|
|
|
".//xccdf:rule-result//xccdf:check//xccdf:check-export", NAMESPACES |
85
|
|
|
) |
86
|
|
|
} |
87
|
|
|
|
88
|
1 |
|
def parse_report(self): |
89
|
1 |
|
test_results_el = self.root.find('.//xccdf:TestResult', NAMESPACES) |
90
|
1 |
|
benchmark_el = self._get_benchmark_element() |
91
|
|
|
|
92
|
1 |
|
report_parser = ReportParser(self.root, test_results_el, benchmark_el) |
93
|
1 |
|
report = report_parser.get_report() |
94
|
1 |
|
logging.debug(report) |
95
|
|
|
|
96
|
1 |
|
group_parser = GroupParser(self.root, self.ref_values, benchmark_el) |
97
|
1 |
|
groups = group_parser.get_groups() |
98
|
|
|
|
99
|
1 |
|
rule_parser = RuleParser(self.root, test_results_el, self.ref_values) |
100
|
1 |
|
rules = rule_parser.get_rules() |
101
|
1 |
|
oval_definitions_and_results_sources = self._get_oval_definition_references(rules) |
102
|
1 |
|
OVAL_and_CPE_tree_builder = OVALAndCPETreeBuilder( # pylint: disable=C0103 |
103
|
|
|
self.root, group_parser, |
104
|
|
|
report.profile_info.get_list_of_cpe_platforms_that_satisfy_evaluation_target(), |
105
|
|
|
oval_definitions_and_results_sources, |
106
|
|
|
self._get_map_oval_var_to_value(test_results_el), |
107
|
|
|
self.ref_values, |
108
|
|
|
) |
109
|
1 |
|
OVAL_and_CPE_tree_builder.insert_oval_and_cpe_trees_to_rules(rules) |
110
|
|
|
|
111
|
1 |
|
self._debug_show_rules(rules) |
112
|
1 |
|
report.rules = rules |
113
|
1 |
|
report.groups = groups |
114
|
|
|
|
115
|
1 |
|
report.profile_info.select_rules(rule_parser.to_select_rule_ids) |
116
|
1 |
|
report.profile_info.deselect_rules(rule_parser.to_deselect_rule_ids) |
117
|
|
|
|
118
|
|
|
return report |
119
|
|
|
|