Test Failed
Push — master ( 05baab...0cd690 )
by Jan
02:26 queued 13s
created

utils.compare_results.Status.get_wining_status()   B

Complexity

Conditions 7

Size

Total Lines 15
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
cc 7
eloc 15
nop 3
dl 0
loc 15
ccs 0
cts 15
cp 0
crap 56
rs 8
c 0
b 0
f 0
1
#!/usr/bin/env python3
2
3
import argparse
4
import os
5
import sys
6
7
try:
8
    import ssg.xml
9
    from ssg.constants import PREFIX_TO_NS, SSG_REF_URIS, XCCDF12_NS
10
except ImportError:
11
    print("The ssg module could not be found.")
12
    print("Run .pyenv.sh available in the project root directory,"
13
          " or add it to PYTHONPATH manually.")
14
    print("$ source .pyenv.sh")
15
    exit(1)
16
17
from xml.etree import ElementTree as ElementTree
18
19
# So we don't hard code "xccdf-1.2"
20
XCCDF12 = list(PREFIX_TO_NS.keys())[list(PREFIX_TO_NS.values()).index(XCCDF12_NS)]
21
22
23
class Status:
24
    PASS = "pass"
25
    FAIL = "fail"
26
    ERROR = "error"
27
    NOT_CHECKED = "notchecked"
28
    NOT_SELECTED = "notselected"
29
    NOT_APPLICABLE = "notapplicable"
30
    INFORMATION = "informational"
31
32
    @classmethod
33
    def get_wining_status(cls, current_status: str, proposed: str) -> str:
34
        if current_status == cls.ERROR:
35
            return current_status
36
        elif current_status == cls.FAIL:
37
            return current_status
38
        elif current_status == cls.NOT_APPLICABLE:
39
            return current_status
40
        elif current_status == cls.NOT_SELECTED:
41
            return current_status
42
        elif current_status == cls.NOT_CHECKED:
43
            return current_status
44
        elif current_status == cls.INFORMATION:
45
            return current_status
46
        return proposed
47
48
49
def parse_args() -> argparse.Namespace:
50
    parser = argparse.ArgumentParser(description='Compare two result ARF files.')
51
    parser.add_argument('base', help='Path to the first ARF file to compare')
52
    parser.add_argument('target', help='Path to the second ARF file to compare')
53
    return parser.parse_args()
54
55
56
def is_file_disa(xml: ElementTree.ElementTree) -> bool:
57
    return 'DISA' in xml.find(f'.//{XCCDF12}:metadata/dc:creator', PREFIX_TO_NS).text
58
59
60
def is_file_ssg(xml: ElementTree.ElementTree) -> bool:
61
    return 'SCAP Security Guide Project' in \
62
           xml.find(f'.//{XCCDF12}:metadata/dc:creator', PREFIX_TO_NS).text
63
64
65
def check_file(path: str) -> bool:
66
    if not os.path.exists(path):
67
        sys.stderr.write(f"File not found: {path}")
68
        exit(1)
69
    return True
70
71
72
def get_rule_to_stig_dict(xml: ElementTree.ElementTree, is_disa: bool) -> dict:
73
    rules = dict()
74
    for group in xml.findall(f'{XCCDF12}:Group', PREFIX_TO_NS):
75
        for sub_groups in group.findall(f'{XCCDF12}:Group', PREFIX_TO_NS):
76
            rules.update(get_rule_to_stig_dict(ElementTree.ElementTree(sub_groups), is_disa))
77
        for rule in group.findall(f'{XCCDF12}:Rule', PREFIX_TO_NS):
78
            rule_id = rule.attrib['id']
79
            if is_disa:
80
                stig_id = rule.find(f'{XCCDF12}:version', PREFIX_TO_NS).text
81
            else:
82
                elm = rule.find(f"{XCCDF12}:reference[@href='{SSG_REF_URIS['stigid']}']",
83
                                PREFIX_TO_NS)
84
                if elm is not None:
85
                    stig_id = elm.text
86
                else:
87
                    continue
88
            if stig_id in rules:
89
                rules[stig_id].append(rule_id)
90
            else:
91
                rules[stig_id] = [rule_id]
92
    return rules
93
94
95
def get_results(xml: ElementTree.ElementTree) -> dict:
96
    rules = dict()
97
98
    results_xml = xml.findall('.//xccdf-1.2:TestResult/xccdf-1.2:rule-result',
99
                              ssg.constants.PREFIX_TO_NS)
100
    for result in results_xml:
101
        idref = result.attrib['idref']
102
        rules[idref] = result.find('xccdf-1.2:result', ssg.constants.PREFIX_TO_NS).text
103
104
    return rules
105
106
107
def file_a_different_type(base_tree: ElementTree.ElementTree,
108
                          target_tree: ElementTree.ElementTree) \
109
        -> bool:
110
    """
111
    Check if both files are the same type.
112
113
    :param base_tree: base tree to check
114
    :param target_tree: target tree to check
115
    :return: true if the give trees are not the same type, otherwise return false.
116
    """
117
    return is_file_disa(base_tree) != is_file_disa(target_tree) or \
118
           is_file_ssg(base_tree) != is_file_ssg(target_tree)
119
120
121
def flatten_stig_results(stig_results: dict) -> dict:
122
    base_stig_flat_results = dict()
123
    for stig, results in stig_results.items():
124
        if len(results) == 1:
125
            base_stig_flat_results[stig] = results[0]
126
        status = results[0]
127
        for result in results:
128
            if result == status:
129
                continue
130
            else:
131
                status = Status.get_wining_status(status, result)
132
        base_stig_flat_results[stig] = status
133
    return base_stig_flat_results
134
135
136
def get_results_by_stig(results: dict, stigs: dict) -> dict:
137
    base_stig_results = dict()
138
    for base_stig, rules in stigs.items():
139
        base_stig_results[base_stig] = list()
140
        for rule in rules:
141
            base_stig_results[base_stig].append(results.get(rule))
142
    return base_stig_results
143
144
145
def print_summary(base_stig_flat_results: dict, different_results: dict, missing_in_target: list,
146
                  same_status: list) -> None:
147
    print(f'Missing in target: {len(missing_in_target)}')
148
    for rule in missing_in_target:
149
        print(f'\t{rule}')
150
    print(f'Same Status: {len(same_status)}')
151
    for rule in same_status:
152
        print(f'\t{rule}\t\t{base_stig_flat_results[rule]}')
153
    print(f'Different results: {len(different_results)}')
154
    for rule, value in different_results.items():
155
        print(f'\t{rule}\t\t{value[0]} - {value[1]}')
156
157
158
def process_stig_results(base_results: dict, target_results: dict,
159
                         base_tree: ElementTree.ElementTree,
160
                         target_tree: ElementTree.ElementTree) -> (dict, dict):
161
    base_stigs = get_rule_to_stig_dict(base_tree, is_file_disa(base_tree))
162
    target_stigs = get_rule_to_stig_dict(target_tree, is_file_disa(target_tree))
163
    base_stig_results = get_results_by_stig(base_results, base_stigs)
164
    target_stig_results = get_results_by_stig(target_results, target_stigs)
165
    base_stig_flat_results = flatten_stig_results(base_stig_results)
166
    target_stig_flat_results = flatten_stig_results(target_stig_results)
167
    return base_stig_flat_results, target_stig_flat_results
168
169
170
def do_compare(base_results: dict, target_results: dict) -> None:
171
    same_status = list()
172
    missing_in_target = list()
173
    different_results = dict()
174
    for base_result_id, base_result in base_results.items():
175
        target_result = target_results.get(base_result_id)
176
        if not target_result:
177
            missing_in_target.append(base_result_id)
178
            continue
179
        if base_result != target_result:
180
            different_results[base_result_id] = (base_result, target_result)
181
        else:
182
            same_status.append(base_result_id)
183
    print_summary(base_results, different_results, missing_in_target, same_status)
184
185
186
def match_results(base_tree: ElementTree.ElementTree, target_tree: ElementTree.ElementTree):
187
    diff_type = file_a_different_type(base_tree, target_tree)
188
    base_results = get_results(base_tree)
189
    target_results = get_results(target_tree)
190
191
    if diff_type:
192
        base_stig_flat_results, target_stig_flat_results = process_stig_results(base_results,
193
                                                                                target_results,
194
                                                                                base_tree,
195
                                                                                target_tree)
196
197
        do_compare(base_stig_flat_results, target_stig_flat_results)
198
        exit(0)
199
200
    do_compare(base_results, target_results)
201
202
203
def main():
204
    args = parse_args()
205
    check_file(args.base)
206
    check_file(args.target)
207
    base_tree = ssg.xml.open_xml(args.base)
208
    target_tree = ssg.xml.open_xml(args.target)
209
    match_results(base_tree, target_tree)
210
211
212
if __name__ == '__main__':
213
    main()
214