|
1
|
|
|
#!/usr/bin/python3 |
|
2
|
|
|
|
|
3
|
|
|
from __future__ import print_function |
|
4
|
|
|
|
|
5
|
|
|
import os |
|
6
|
|
|
import re |
|
7
|
|
|
import shutil |
|
8
|
|
|
import subprocess |
|
9
|
|
|
import sys |
|
10
|
|
|
import tempfile |
|
11
|
|
|
|
|
12
|
|
|
import ssg.build_ovals |
|
13
|
|
|
import ssg.constants |
|
14
|
|
|
import ssg.jinja |
|
15
|
|
|
import ssg.oval |
|
16
|
|
|
import ssg.rules |
|
17
|
|
|
import ssg.utils |
|
18
|
|
|
import ssg.yaml |
|
19
|
|
|
import ssg.build_yaml |
|
20
|
|
|
import ssg.rule_yaml |
|
21
|
|
|
import ssg.id_translate |
|
22
|
|
|
|
|
23
|
|
|
|
|
24
|
|
|
class OVALTester(object): |
|
25
|
|
|
def __init__(self, verbose): |
|
26
|
|
|
self.mock_env_yaml = {"rule_id": "test"} |
|
27
|
|
|
self.result = True |
|
28
|
|
|
self.verbose = verbose |
|
29
|
|
|
|
|
30
|
|
|
def _expand_shorthand(self, shorthand_path, oval_path, env_yaml): |
|
31
|
|
|
shorthand_file_content = ssg.jinja.process_file_with_macros( |
|
32
|
|
|
shorthand_path, env_yaml) |
|
33
|
|
|
wrapped_shorthand = (ssg.constants.oval_header + |
|
34
|
|
|
shorthand_file_content + |
|
35
|
|
|
ssg.constants.oval_footer) |
|
36
|
|
|
shorthand_tree = ssg.xml.ElementTree.fromstring( |
|
37
|
|
|
wrapped_shorthand.encode("utf-8")) |
|
38
|
|
|
|
|
39
|
|
|
definitions = ssg.xml.ElementTree.Element( |
|
40
|
|
|
"{%s}definitions" % ssg.constants.oval_namespace) |
|
41
|
|
|
tests = ssg.xml.ElementTree.Element( |
|
42
|
|
|
"{%s}tests" % ssg.constants.oval_namespace) |
|
43
|
|
|
objects = ssg.xml.ElementTree.Element( |
|
44
|
|
|
"{%s}objects" % ssg.constants.oval_namespace) |
|
45
|
|
|
states = ssg.xml.ElementTree.Element( |
|
46
|
|
|
"{%s}states" % ssg.constants.oval_namespace) |
|
47
|
|
|
variables = ssg.xml.ElementTree.Element( |
|
48
|
|
|
"{%s}variables" % ssg.constants.oval_namespace) |
|
49
|
|
|
for childnode in shorthand_tree.findall(".//{%s}def-group/*" % |
|
50
|
|
|
ssg.constants.oval_namespace): |
|
51
|
|
View Code Duplication |
if childnode.tag is ssg.xml.ElementTree.Comment: |
|
|
|
|
|
|
52
|
|
|
continue |
|
53
|
|
|
elif childnode.tag.endswith("definition"): |
|
54
|
|
|
ssg.build_ovals.append(definitions, childnode) |
|
55
|
|
|
elif childnode.tag.endswith("_test"): |
|
56
|
|
|
ssg.build_ovals.append(tests, childnode) |
|
57
|
|
|
elif childnode.tag.endswith("_object"): |
|
58
|
|
|
ssg.build_ovals.append(objects, childnode) |
|
59
|
|
|
elif childnode.tag.endswith("_state"): |
|
60
|
|
|
ssg.build_ovals.append(states, childnode) |
|
61
|
|
|
elif childnode.tag.endswith("_variable"): |
|
62
|
|
|
ssg.build_ovals.append(variables, childnode) |
|
63
|
|
|
else: |
|
64
|
|
|
sys.stderr.write( |
|
65
|
|
|
"Warning: Unknown element '%s'\n" % (childnode.tag)) |
|
66
|
|
|
|
|
67
|
|
|
header = ssg.xml.oval_generated_header("test", "5.11", "1.0") |
|
68
|
|
|
skeleton = header + ssg.constants.oval_footer |
|
69
|
|
|
root = ssg.xml.ElementTree.fromstring(skeleton.encode("utf-8")) |
|
70
|
|
|
root.append(definitions) |
|
71
|
|
|
root.append(tests) |
|
72
|
|
|
root.append(objects) |
|
73
|
|
|
root.append(states) |
|
74
|
|
|
if list(variables): |
|
75
|
|
|
root.append(variables) |
|
76
|
|
|
id_translator = ssg.id_translate.IDTranslator("test") |
|
77
|
|
|
root_translated = id_translator.translate(root) |
|
78
|
|
|
|
|
79
|
|
|
ssg.xml.ElementTree.ElementTree(root_translated).write(oval_path) |
|
80
|
|
|
|
|
81
|
|
|
def _get_result(self, oscap_output): |
|
82
|
|
|
pattern = re.compile( |
|
83
|
|
|
r"^Definition oval:[A-Za-z0-9_\-\.]+:def:[1-9][0-9]*: (\w+)$") |
|
84
|
|
|
for line in oscap_output.splitlines(): |
|
85
|
|
|
matched = pattern.match(line) |
|
86
|
|
|
if matched: |
|
87
|
|
|
return matched.group(1) |
|
88
|
|
|
return None |
|
89
|
|
|
|
|
90
|
|
|
def _create_config_file(self, config_file_content, tmp_dir): |
|
91
|
|
|
config_file_path = os.path.join(tmp_dir, "config") |
|
92
|
|
|
if config_file_content: |
|
93
|
|
|
with open(config_file_path, "w") as f: |
|
94
|
|
|
f.write(config_file_content) |
|
95
|
|
|
return config_file_path |
|
96
|
|
|
|
|
97
|
|
|
def _create_oval(self, oval_content, config_file_path, tmp_dir): |
|
98
|
|
|
oval_content = oval_content.replace("CONFIG_FILE", config_file_path) |
|
99
|
|
|
shorthand_path = os.path.join(tmp_dir, "shorthand") |
|
100
|
|
|
with open(shorthand_path, "w") as f: |
|
101
|
|
|
f.write(oval_content) |
|
102
|
|
|
oval_path = os.path.join(tmp_dir, "oval.xml") |
|
103
|
|
|
self._expand_shorthand(shorthand_path, oval_path, self.mock_env_yaml) |
|
104
|
|
|
return oval_path |
|
105
|
|
|
|
|
106
|
|
|
def _evaluate_oval(self, oval_path): |
|
107
|
|
|
results_path = oval_path + ".results.xml" |
|
108
|
|
|
oscap_command = [ |
|
109
|
|
|
"oscap", "oval", "eval", "--results", results_path, oval_path] |
|
110
|
|
|
oscap_process = subprocess.Popen( |
|
111
|
|
|
oscap_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
|
112
|
|
|
oscap_stdout, oscap_stderr = oscap_process.communicate() |
|
113
|
|
|
def_result = self._get_result(oscap_stdout.decode("utf-8")) |
|
114
|
|
|
return def_result, results_path |
|
115
|
|
|
|
|
116
|
|
|
def test(self, description, oval_content, config_file_content, |
|
117
|
|
|
expected_result): |
|
118
|
|
|
""" |
|
119
|
|
|
Execute a test. |
|
120
|
|
|
description: a very short description to be displayed in test output |
|
121
|
|
|
oval_content: content of the OVAL shorthand file written in a way you |
|
122
|
|
|
write OVALs in SSG rules (not a valid OVAL) |
|
123
|
|
|
config_file_content: content of the text configuration file that the |
|
124
|
|
|
OVAL will check |
|
125
|
|
|
expected_result: expected result of evaluation of the OVAL definition |
|
126
|
|
|
""" |
|
127
|
|
|
try: |
|
128
|
|
|
tmp_dir = tempfile.mkdtemp() |
|
129
|
|
|
config_file_path = self._create_config_file( |
|
130
|
|
|
config_file_content, tmp_dir) |
|
131
|
|
|
oval_path = self._create_oval( |
|
132
|
|
|
oval_content, config_file_path, tmp_dir) |
|
133
|
|
|
result, results_path = self._evaluate_oval(oval_path) |
|
134
|
|
|
msg = ("OVAL Definition was evaluated as %s, but expected result " |
|
135
|
|
|
"is %s.") % (result, expected_result) |
|
136
|
|
|
assert result == expected_result, msg |
|
137
|
|
|
if self.verbose: |
|
138
|
|
|
print("Test: %s: PASS" % (description)) |
|
139
|
|
|
self.result = self.result and True |
|
140
|
|
|
except AssertionError as e: |
|
141
|
|
|
if self.verbose: |
|
142
|
|
|
print("Test: %s: FAIL" % (description)) |
|
143
|
|
|
print(" " + str(e)) |
|
144
|
|
|
self.result = False |
|
145
|
|
|
finally: |
|
146
|
|
|
shutil.rmtree(tmp_dir) |
|
147
|
|
|
|
|
148
|
|
|
def finish(self): |
|
149
|
|
|
""" |
|
150
|
|
|
Exit test with an appropriate return code. |
|
151
|
|
|
""" |
|
152
|
|
|
sys.exit(0 if self.result else 1) |
|
153
|
|
|
|