Passed
Pull Request — master (#63)
by Jan
08:23
created

test_rationale()   A

Complexity

Conditions 1

Size

Total Lines 44
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 44
ccs 5
cts 5
cp 1
rs 9.55
c 0
b 0
f 0
cc 1
nop 2
crap 1
1 1
import pytest
2
3 1
from openscap_report.scap_results_parser import SCAPResultsParser
4 1
from openscap_report.scap_results_parser.data_structures import Report, Rule
5
6 1
from ..constants import (PATH_TO_ARF, PATH_TO_ARF_SCANNED_ON_CONTAINER,
7
                         PATH_TO_ARF_WITH_MULTI_CHECK,
8
                         PATH_TO_ARF_WITH_OS_CPE_CHECK,
9
                         PATH_TO_ARF_WITHOUT_INFO,
10
                         PATH_TO_ARF_WITHOUT_SYSTEM_DATA,
11
                         PATH_TO_EMPTY_XML_FILE, PATH_TO_REMEDIATIONS_SCRIPTS,
12
                         PATH_TO_RULE_AND_CPE_CHECK_ARF,
13
                         PATH_TO_RULE_AND_CPE_CHECK_XCCDF,
14
                         PATH_TO_SIMPLE_RULE_FAIL_ARF,
15
                         PATH_TO_SIMPLE_RULE_FAIL_XCCDF,
16
                         PATH_TO_SIMPLE_RULE_PASS_ARF,
17
                         PATH_TO_SIMPLE_RULE_PASS_XCCDF, PATH_TO_XCCDF,
18
                         PATH_TO_XCCDF_WITH_MULTI_CHECK,
19
                         PATH_TO_XCCDF_WITHOUT_INFO,
20
                         PATH_TO_XCCDF_WITHOUT_SYSTEM_DATA)
21
22
23 1
def get_parser(file_path):
24 1
    xml_data = None
25 1
    with open(file_path, "r", encoding="utf-8") as xml_report:
26 1
        xml_data = xml_report.read().encode()
27 1
    return SCAPResultsParser(xml_data)
28
29
30 1
@pytest.mark.parametrize("file_path, result", [
31
    (PATH_TO_ARF, True),
32
    (PATH_TO_SIMPLE_RULE_PASS_ARF, True),
33
    (PATH_TO_SIMPLE_RULE_FAIL_ARF, True),
34
    (PATH_TO_RULE_AND_CPE_CHECK_ARF, True),
35
    (PATH_TO_ARF_WITHOUT_INFO, True),
36
    (PATH_TO_ARF_WITHOUT_SYSTEM_DATA, True),
37
    (PATH_TO_ARF_SCANNED_ON_CONTAINER, True),
38
    (PATH_TO_ARF_WITH_OS_CPE_CHECK, True),
39
    (PATH_TO_XCCDF, False),
40
    (PATH_TO_SIMPLE_RULE_PASS_XCCDF, False),
41
    (PATH_TO_SIMPLE_RULE_FAIL_XCCDF, False),
42
    (PATH_TO_RULE_AND_CPE_CHECK_XCCDF, False),
43
    (PATH_TO_XCCDF_WITHOUT_INFO, False),
44
    (PATH_TO_XCCDF_WITHOUT_SYSTEM_DATA, False),
45
    (PATH_TO_EMPTY_XML_FILE, False),
46
])
47 1
def test_validation(file_path, result):
48 1
    parser = get_parser(file_path)
49 1
    assert parser.validate(parser.arf_schemas_path) == result
50
51
52 1
@pytest.mark.parametrize("file_path, number_of_cpe_platforms, os_cpe_platform", [
53
    (PATH_TO_ARF, 13, "cpe:/o:fedoraproject:fedora:32"),
54
    (PATH_TO_XCCDF, 13, "cpe:/o:fedoraproject:fedora:32"),
55
    (PATH_TO_SIMPLE_RULE_PASS_ARF, 0, ""),
56
    (PATH_TO_SIMPLE_RULE_FAIL_ARF, 0, ""),
57
    (PATH_TO_ARF_WITHOUT_INFO, 0, ""),
58
    (PATH_TO_ARF_WITHOUT_SYSTEM_DATA, 0, ""),
59
    (PATH_TO_ARF_SCANNED_ON_CONTAINER, 6, "cpe:/o:fedoraproject:fedora:35"),
60
    (PATH_TO_RULE_AND_CPE_CHECK_ARF, 1, "cpe:/o:example:applicable:5"),
61
    (PATH_TO_ARF_WITH_OS_CPE_CHECK, 0, "cpe:/o:fedoraproject:fedora:1"),
62
    (PATH_TO_SIMPLE_RULE_PASS_XCCDF, 0, ""),
63
    (PATH_TO_SIMPLE_RULE_FAIL_XCCDF, 0, ""),
64
    (PATH_TO_XCCDF_WITHOUT_INFO, 0, ""),
65
    (PATH_TO_XCCDF_WITHOUT_SYSTEM_DATA, 0, ""),
66
    (PATH_TO_RULE_AND_CPE_CHECK_XCCDF, 1, "cpe:/o:example:applicable:5"),
67
])
68 1
def test_get_profile_info(file_path, number_of_cpe_platforms, os_cpe_platform):
69 1
    parser = get_parser(file_path)
70 1
    report = parser.get_profile_info()
71 1
    assert len(report.cpe_platforms) == number_of_cpe_platforms
72 1
    assert report.platform == os_cpe_platform
73
74
75 1
@pytest.mark.parametrize("file_path, number_of_rules", [
76
    (PATH_TO_ARF, 714),
77
    (PATH_TO_XCCDF, 714),
78
    (PATH_TO_ARF_SCANNED_ON_CONTAINER, 121),
79
    (PATH_TO_SIMPLE_RULE_PASS_ARF, 1),
80
    (PATH_TO_SIMPLE_RULE_FAIL_ARF, 1),
81
    (PATH_TO_ARF_WITHOUT_INFO, 1),
82
    (PATH_TO_ARF_WITHOUT_SYSTEM_DATA, 1),
83
    (PATH_TO_ARF_WITH_OS_CPE_CHECK, 1),
84
    (PATH_TO_RULE_AND_CPE_CHECK_ARF, 3),
85
    (PATH_TO_SIMPLE_RULE_PASS_XCCDF, 1),
86
    (PATH_TO_SIMPLE_RULE_FAIL_XCCDF, 1),
87
    (PATH_TO_XCCDF_WITHOUT_INFO, 1),
88
    (PATH_TO_XCCDF_WITHOUT_SYSTEM_DATA, 1),
89
    (PATH_TO_RULE_AND_CPE_CHECK_XCCDF, 3),
90
])
91 1
def test_get_info_about_rules_in_profile(file_path, number_of_rules):
92 1
    parser = get_parser(file_path)
93 1
    parser.process_groups_or_rules()
94 1
    assert len(parser.rules.keys()) == number_of_rules
95 1
    for rule in parser.rules.values():
96 1
        assert isinstance(rule, Rule)
97
98
99 1
@pytest.mark.parametrize("file_path, contains_oval_tree", [
100
    (PATH_TO_ARF, True),
101
    (PATH_TO_XCCDF, False),
102
])
103 1
def test_parse_report(file_path, contains_oval_tree):
104 1
    parser = get_parser(file_path)
105 1
    report = parser.parse_report()
106 1
    assert isinstance(report, Report)
107 1
    assert report.profile_name is not None
108 1
    assert report.rules is not None
109 1
    rule_id = "xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny"
110 1
    assert isinstance(report.rules[rule_id], Rule)
111 1
    assert (report.rules[rule_id].oval_tree is not None) == contains_oval_tree
112
113
114 1
@pytest.mark.parametrize("file_path, contains_rules_some_multi_check_rule", [
115
    (PATH_TO_ARF, False),
116
    (PATH_TO_XCCDF, False),
117
    (PATH_TO_XCCDF_WITH_MULTI_CHECK, True),
118
    (PATH_TO_ARF_WITH_MULTI_CHECK, True),
119
])
120 1
def test_multi_check(file_path, contains_rules_some_multi_check_rule):
121 1
    parser = get_parser(file_path)
122 1
    parser.process_groups_or_rules()
123 1
    result = False
124 1
    for rule in parser.rules.values():
125 1
        if rule.multi_check:
126 1
            result = True
127 1
    assert result == contains_rules_some_multi_check_rule
128
129
130 1
@pytest.mark.parametrize("rule, result", [
131
    (
132
        "xccdf_org.ssgproject.content_rule_prefer_64bit_os",
133
        "Prefer installation of 64-bit operating systems when the CPU supports it."
134
    ),
135
    (
136
        "xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_enabled",
137
        (
138
            "\nTo activate locking of the screensaver in the GNOME3 desktop"
139
            " when it is activated,\nadd or set <code>lock-enabled</code>"
140
            " to <code>true</code> in\n<code>/etc/dconf/db/local.d/00-security-settings</code>."
141
            " For example:\n<pre>[org/gnome/desktop/screensaver]\nlock-enabled=true\n</pre>\n"
142
            "Once the settings have been added, add a lock to\n"
143
            "<code>/etc/dconf/db/local.d/locks/00-security-settings-lock</code> "
144
            "to prevent user modification.\nFor example:\n"
145
            "<pre>/org/gnome/desktop/screensaver/lock-enabled</pre>\n"
146
            "After the settings have been set, run <code>dconf update</code>."
147
        )
148
    ),
149
    (
150
        "xccdf_org.ssgproject.content_rule_auditd_data_retention_action_mail_acct",
151
        (
152
            "The <code>auditd</code> service can be configured to send email to\n"
153
            "a designated account in certain situations. Add or correct the following line\n"
154
            "in <code>/etc/audit/auditd.conf</code> to ensure that administrators are notified\n"
155
            "via email for those situations:\n<pre>action_mail_acct = root</pre>"
156
        )
157
    ),
158
    (
159
        "xccdf_org.ssgproject.content_rule_chronyd_specify_remote_server",
160
        (
161
            "<code>Chrony</code> is a daemon which implements"
162
            " the Network Time Protocol (NTP). It is designed to\n"
163
            "synchronize system clocks across a variety of systems and"
164
            " use a source that is highly\naccurate. More information on"
165
            " <code>chrony</code> can be found at\n\n    "
166
            "<a href=\"http://chrony.tuxfamily.org/\">http://chrony.tuxfamily.org/</a>.\n"
167
            "<code>Chrony</code> can be configured to be a client and/or a server.\n"
168
            "Add or edit server or pool lines to <code>/etc/chrony.conf</code> as appropriate:\n"
169
            "<pre>server &lt;remote-server&gt;</pre>\nMultiple servers may be configured."
170
        )
171
    ),
172
])
173 1
def test_description(rule, result):
174 1
    parser = get_parser(PATH_TO_ARF)
175 1
    parser.process_groups_or_rules()
176 1
    assert parser.rules[rule].description == result
177
178
179 1
@pytest.mark.parametrize("rule, result", [
180
    (
181
        "xccdf_org.ssgproject.content_rule_prefer_64bit_os",
182
        (
183
            "Use of a 64-bit operating system offers a few advantages, "
184
            "like a larger address space range for\nAddress Space Layout"
185
            " Randomization (ASLR) and systematic presence of No eXecute"
186
            " and Execute Disable (NX/XD) protection bits."
187
        )
188
    ),
189
    (
190
        "xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_enabled",
191
        (
192
            "A session lock is a temporary action taken when a user stops work and"
193
            " moves away from the immediate physical vicinity\nof the information "
194
            "system but does not want to logout because of the temporary nature of the absense."
195
        )
196
    ),
197
    (
198
        "xccdf_org.ssgproject.content_rule_auditd_data_retention_action_mail_acct",
199
        (
200
            "Email sent to the root account is typically aliased to the\n"
201
            "administrators of the system, who can take appropriate action."
202
        )
203
    ),
204
    (
205
        "xccdf_org.ssgproject.content_rule_sudoers_explicit_command_args",
206
        (
207
            "Any argument can modify quite significantly the behavior of a"
208
            " program, whether regarding the\nrealized operation (read, write, delete, etc.)"
209
            " or accessed resources (path in a file system tree). To\navoid any possibility of"
210
            " misuse of a command by a user, the ambiguities must be removed at the\nlevel of its"
211
            " specification.\n\nFor example, on some systems, the kernel messages are only "
212
            "accessible by root.\nIf a user nevertheless must have the privileges to read them,"
213
            " the argument of the dmesg command has to be restricted\nin order to prevent "
214
            "the user from flushing the buffer through the -c option:\n"
215
            "<pre>\nuser ALL = dmesg &quot;&quot;\n</pre>"
216
        )
217
    )
218
])
219 1
def test_rationale(rule, result):
220 1
    parser = get_parser(PATH_TO_ARF)
221 1
    parser.process_groups_or_rules()
222 1
    assert parser.rules[rule].rationale == result
223
224
225 1
@pytest.mark.parametrize("rule, result", [
226
    (
227
        "xccdf_org.ssgproject.content_rule_prefer_64bit_os",
228
        ["There is no remediation besides installing a 64-bit operating system."]
229
    ),
230
    (
231
        "xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_enabled",
232
        []
233
    ),
234
    (
235
        "xccdf_org.ssgproject.content_rule_auditd_data_retention_action_mail_acct",
236
        []
237
    ),
238
    (
239
        "xccdf_org.ssgproject.content_rule_sudoers_explicit_command_args",
240
        [
241
            (
242
                "This rule doesn&#x27;t come with a remediation, as absence of arguments in"
243
                " the user spec doesn&#x27;t mean that the command is intended to be executed "
244
                "with no arguments."
245
            ),
246
            (
247
                "The rule can produce false findings when an argument contains a"
248
                " comma - sudoers syntax allows comma escaping using backslash, but"
249
                " the check doesn&#x27;t support that. For example,"
250
                " <code>root ALL=(ALL) echo 1\\,2</code> allows root to execute"
251
                " <code>echo 1,2</code>, but the check would interpret it as two commands "
252
                "<code>echo 1\\</code> and <code>2</code>."
253
            )
254
        ]
255
    )
256
])
257 1
def test_warnings(rule, result):
258 1
    parser = get_parser(PATH_TO_ARF)
259 1
    parser.process_groups_or_rules()
260 1
    assert parser.rules[rule].warnings == result
261
262
263 1
@pytest.mark.parametrize("rule, remediation_id, scripts", [
264
    (
265
        "xccdf_org.ssgproject.content_rule_prefer_64bit_os",
266
        None,
267
        {}
268
    ),
269
    (
270
        "xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_enabled",
271
        "dconf_gnome_screensaver_lock_enabled",
272
        {
273
            "urn:xccdf:fix:script:ansible": "dconf_gnome_screensaver_lock_enabled_ansible.txt",
274
            "urn:xccdf:fix:script:sh": "dconf_gnome_screensaver_lock_enabled_sh.txt"
275
        }
276
    ),
277
    (
278
        "xccdf_org.ssgproject.content_rule_auditd_data_retention_action_mail_acct",
279
        "auditd_data_retention_action_mail_acct",
280
        {
281
            "urn:xccdf:fix:script:sh": "auditd_data_retention_action_mail_acct_sh.txt",
282
            "urn:xccdf:fix:script:ansible": "auditd_data_retention_action_mail_acct_ansible.txt"
283
        }
284
    )
285
])
286 1
def test_remediations(rule, remediation_id, scripts):
287 1
    parser = get_parser(PATH_TO_ARF)
288 1
    parser.process_groups_or_rules()
289 1
    for remediation in parser.rules[rule].remediations:
290 1
        assert remediation.remediation_id == remediation_id
291 1
        assert remediation.system in scripts
292 1
        path = PATH_TO_REMEDIATIONS_SCRIPTS / str(scripts[remediation.system])
293 1
        with open(path, "r", encoding="utf-8") as script:
294 1
            data = script.read()
295
            assert data == remediation.fix
296