Passed
Push — main ( 4906c7...73043a )
by
unknown
06:35 queued 13s
created

get_rules()   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 6
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
# Copyright 2022, Red Hat, Inc.
2
# SPDX-License-Identifier: LGPL-2.1-or-later
3
4
import logging
5
from contextlib import nullcontext as does_not_raise
6
7
import pytest
8
from lxml.etree import XMLSyntaxError
9
10
from openscap_report.scap_results_parser import ARF_SCHEMAS_PATH
11
from openscap_report.scap_results_parser.data_structures import (Report, Rule,
12
                                                                 RuleWarning)
13
from openscap_report.scap_results_parser.exceptions import \
14
    NotSupportedReportingFormat
15
from openscap_report.scap_results_parser.parsers.report_parser import \
16
    ReportParser
17
18
from ..constants import (PATH_TO_ARF, PATH_TO_ARF_SCANNED_ON_CONTAINER,
19
                         PATH_TO_ARF_WITH_MULTI_CHECK,
20
                         PATH_TO_ARF_WITH_OS_CPE_CHECK,
21
                         PATH_TO_ARF_WITHOUT_INFO,
22
                         PATH_TO_ARF_WITHOUT_SYSTEM_DATA, PATH_TO_EMPTY_FILE,
23
                         PATH_TO_EMPTY_XML_FILE, PATH_TO_REMEDIATIONS_SCRIPTS,
24
                         PATH_TO_RULE_AND_CPE_CHECK_ARF,
25
                         PATH_TO_RULE_AND_CPE_CHECK_XCCDF,
26
                         PATH_TO_SIMPLE_RULE_FAIL_ARF,
27
                         PATH_TO_SIMPLE_RULE_FAIL_XCCDF,
28
                         PATH_TO_SIMPLE_RULE_PASS_ARF,
29
                         PATH_TO_SIMPLE_RULE_PASS_XCCDF, PATH_TO_XCCDF,
30
                         PATH_TO_XCCDF_WITH_MULTI_CHECK,
31
                         PATH_TO_XCCDF_WITHOUT_INFO,
32
                         PATH_TO_XCCDF_WITHOUT_SYSTEM_DATA, PATH_TO_XML_FILE)
33
from ..test_utils import (BASIC_REPORT, DEFAULT_RULES, get_benchmark,
34
                          get_parser, get_root, get_rules, get_test_results)
35
36
37
@pytest.mark.unit_test
38
@pytest.mark.parametrize("file_path, result", [
39
    (PATH_TO_ARF, True),
40
    (PATH_TO_SIMPLE_RULE_PASS_ARF, True),
41
    (PATH_TO_SIMPLE_RULE_FAIL_ARF, True),
42
    (PATH_TO_RULE_AND_CPE_CHECK_ARF, True),
43
    (PATH_TO_ARF_WITHOUT_INFO, True),
44
    (PATH_TO_ARF_WITHOUT_SYSTEM_DATA, True),
45
    (PATH_TO_ARF_SCANNED_ON_CONTAINER, True),
46
    (PATH_TO_ARF_WITH_OS_CPE_CHECK, True),
47
    (PATH_TO_XCCDF, False),
48
    (PATH_TO_SIMPLE_RULE_PASS_XCCDF, False),
49
    (PATH_TO_SIMPLE_RULE_FAIL_XCCDF, False),
50
    (PATH_TO_RULE_AND_CPE_CHECK_XCCDF, False),
51
    (PATH_TO_XCCDF_WITHOUT_INFO, False),
52
    (PATH_TO_XCCDF_WITHOUT_SYSTEM_DATA, False),
53
])
54
def test_validation(file_path, result):
55
    parser = get_parser(file_path)
56
    assert parser.validate(ARF_SCHEMAS_PATH) == result
57
58
59
@pytest.mark.unit_test
60
@pytest.mark.parametrize("file_path, expectation, e_msg", [
61
    (PATH_TO_ARF, does_not_raise(), ""),
62
    (PATH_TO_XCCDF, does_not_raise(), "input is the XCCDF"),
63
    (PATH_TO_EMPTY_XML_FILE, pytest.raises(NotSupportedReportingFormat), "isn't a valid"),
64
    (PATH_TO_EMPTY_FILE, pytest.raises(XMLSyntaxError), "empty"),
65
    (PATH_TO_XML_FILE, pytest.raises(NotSupportedReportingFormat), "isn't a valid"),
66
])
67
def test_parsers_init(file_path, expectation, e_msg, caplog):
68
    caplog.set_level(logging.WARNING)
69
    with expectation as excinfo:
70
        get_parser(file_path)
71
    if excinfo is not None:
72
        assert e_msg in str(excinfo.value)
73
    else:
74
        assert e_msg in str(caplog.text)
75
76
77
@pytest.mark.unit_test
78
@pytest.mark.parametrize("file_path, number_of_cpe_platforms, os_cpe_platform", [
79
    (PATH_TO_ARF, 13, {"cpe:/o:fedoraproject:fedora:32": True}),
80
    (PATH_TO_XCCDF, 13, {"cpe:/o:fedoraproject:fedora:32": True}),
81
    (PATH_TO_SIMPLE_RULE_PASS_ARF, 0, {}),
82
    (PATH_TO_SIMPLE_RULE_FAIL_ARF, 0, {}),
83
    (PATH_TO_ARF_WITHOUT_INFO, 0, {}),
84
    (PATH_TO_ARF_WITHOUT_SYSTEM_DATA, 0, {}),
85
    (PATH_TO_ARF_SCANNED_ON_CONTAINER, 6, {
86
        'cpe:/o:fedoraproject:fedora:35': True,
87
        'cpe:/o:fedoraproject:fedora:34': True,
88
        'cpe:/o:fedoraproject:fedora:33': True
89
    }),
90
    (PATH_TO_RULE_AND_CPE_CHECK_ARF, 1, {}),
91
    (PATH_TO_ARF_WITH_OS_CPE_CHECK, 0, {"cpe:/o:fedoraproject:fedora:1": False}),
92
    (PATH_TO_SIMPLE_RULE_PASS_XCCDF, 0, {}),
93
    (PATH_TO_SIMPLE_RULE_FAIL_XCCDF, 0, {}),
94
    (PATH_TO_XCCDF_WITHOUT_INFO, 0, {}),
95
    (PATH_TO_XCCDF_WITHOUT_SYSTEM_DATA, 0, {}),
96
    (PATH_TO_RULE_AND_CPE_CHECK_XCCDF, 1, {}),
97
])
98
def test_get_profile_info(file_path, number_of_cpe_platforms, os_cpe_platform):
99
    root = get_root(file_path)
100
    report_parser = ReportParser(root, get_test_results(root), get_benchmark(root))
101
    report = report_parser.get_report()
102
    assert len(report.scan_result.cpe_platforms) == number_of_cpe_platforms
103
    assert report.profile_info.cpe_platforms_for_profile == os_cpe_platform
104
105
106
@pytest.mark.unit_test
107
@pytest.mark.parametrize("file_path, number_of_rules", [
108
    (PATH_TO_ARF, 714),
109
    (PATH_TO_XCCDF, 714),
110
    (PATH_TO_ARF_SCANNED_ON_CONTAINER, 121),
111
    (PATH_TO_SIMPLE_RULE_PASS_ARF, 1),
112
    (PATH_TO_SIMPLE_RULE_FAIL_ARF, 1),
113
    (PATH_TO_ARF_WITHOUT_INFO, 1),
114
    (PATH_TO_ARF_WITHOUT_SYSTEM_DATA, 1),
115
    (PATH_TO_ARF_WITH_OS_CPE_CHECK, 1),
116
    (PATH_TO_RULE_AND_CPE_CHECK_ARF, 3),
117
    (PATH_TO_SIMPLE_RULE_PASS_XCCDF, 1),
118
    (PATH_TO_SIMPLE_RULE_FAIL_XCCDF, 1),
119
    (PATH_TO_XCCDF_WITHOUT_INFO, 1),
120
    (PATH_TO_XCCDF_WITHOUT_SYSTEM_DATA, 1),
121
    (PATH_TO_RULE_AND_CPE_CHECK_XCCDF, 3),
122
])
123
def test_get_info_about_rules_in_profile(file_path, number_of_rules):
124
    rules = get_rules(file_path)
125
    assert len(rules.keys()) == number_of_rules
126
    for rule in rules.values():
127
        assert isinstance(rule, Rule)
128
129
130
@pytest.mark.unit_test
131
@pytest.mark.parametrize("file_path, contains_oval_tree", [
132
    (PATH_TO_ARF, True),
133
    (PATH_TO_XCCDF, False),
134
])
135
def test_parse_report(file_path, contains_oval_tree):
136
    parser = get_parser(file_path)
137
    report = parser.parse_report()
138
    assert isinstance(report, Report)
139
    assert report.profile_info.profile_id is not None
140
    assert report.rules is not None
141
    rule_id = "xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny"
142
    assert isinstance(report.rules[rule_id], Rule)
143
    assert (report.rules[rule_id].oval_definition is not None) == contains_oval_tree
144
145
146
@pytest.mark.unit_test
147
@pytest.mark.parametrize("file_path, contains_rules_some_multi_check_rule", [
148
    (PATH_TO_ARF, False),
149
    (PATH_TO_XCCDF, False),
150
    (PATH_TO_XCCDF_WITH_MULTI_CHECK, True),
151
    (PATH_TO_ARF_WITH_MULTI_CHECK, True),
152
])
153
def test_multi_check(file_path, contains_rules_some_multi_check_rule):
154
    rules = get_rules(file_path)
155
    result = False
156
    for rule in rules.values():
157
        if rule.multi_check:
158
            result = True
159
    assert result == contains_rules_some_multi_check_rule
160
161
162
@pytest.mark.unit_test
163
@pytest.mark.parametrize("rule, result", [
164
    (
165
        "xccdf_org.ssgproject.content_rule_prefer_64bit_os",
166
        "Prefer installation of 64-bit operating systems when the CPU supports it."
167
    ),
168
    (
169
        "xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_enabled",
170
        (
171
            "\nTo activate locking of the screensaver in the GNOME3 desktop"
172
            " when it is activated,\nadd or set <code>lock-enabled</code>"
173
            " to <code>true</code> in\n<code>/etc/dconf/db/local.d/00-security-settings</code>."
174
            " For example:\n<pre>[org/gnome/desktop/screensaver]\nlock-enabled=true\n</pre>\n"
175
            "Once the settings have been added, add a lock to\n"
176
            "<code>/etc/dconf/db/local.d/locks/00-security-settings-lock</code> "
177
            "to prevent user modification.\nFor example:\n"
178
            "<pre>/org/gnome/desktop/screensaver/lock-enabled</pre>\n"
179
            "After the settings have been set, run <code>dconf update</code>."
180
        )
181
    ),
182
    (
183
        "xccdf_org.ssgproject.content_rule_auditd_data_retention_action_mail_acct",
184
        (
185
            "The <code>auditd</code> service can be configured to send email to\n"
186
            "a designated account in certain situations. Add or correct the following line\n"
187
            "in <code>/etc/audit/auditd.conf</code> to ensure that administrators are notified\n"
188
            "via email for those situations:\n<pre>action_mail_acct = root</pre>"
189
        )
190
    ),
191
    (
192
        "xccdf_org.ssgproject.content_rule_chronyd_specify_remote_server",
193
        (
194
            "<code>Chrony</code> is a daemon which implements"
195
            " the Network Time Protocol (NTP). It is designed to\n"
196
            "synchronize system clocks across a variety of systems and"
197
            " use a source that is highly\naccurate. More information on"
198
            " <code>chrony</code> can be found at\n\n    "
199
            "<a href=\"http://chrony.tuxfamily.org/\">http://chrony.tuxfamily.org/</a>.\n"
200
            "<code>Chrony</code> can be configured to be a client and/or a server.\n"
201
            "Add or edit server or pool lines to <code>/etc/chrony.conf</code> as appropriate:\n"
202
            "<pre>server &lt;remote-server&gt;</pre>\nMultiple servers may be configured."
203
        )
204
    ),
205
])
206
def test_description(rule, result):
207
    assert DEFAULT_RULES[rule].description == result
208
209
210
@pytest.mark.unit_test
211
@pytest.mark.parametrize("rule, result", [
212
    (
213
        "xccdf_org.ssgproject.content_rule_prefer_64bit_os",
214
        (
215
            "Use of a 64-bit operating system offers a few advantages, "
216
            "like a larger address space range for\nAddress Space Layout"
217
            " Randomization (ASLR) and systematic presence of No eXecute"
218
            " and Execute Disable (NX/XD) protection bits."
219
        )
220
    ),
221
    (
222
        "xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_enabled",
223
        (
224
            "A session lock is a temporary action taken when a user stops work and"
225
            " moves away from the immediate physical vicinity\nof the information "
226
            "system but does not want to logout because of the temporary nature of the absense."
227
        )
228
    ),
229
    (
230
        "xccdf_org.ssgproject.content_rule_auditd_data_retention_action_mail_acct",
231
        (
232
            "Email sent to the root account is typically aliased to the\n"
233
            "administrators of the system, who can take appropriate action."
234
        )
235
    ),
236
    (
237
        "xccdf_org.ssgproject.content_rule_sudoers_explicit_command_args",
238
        (
239
            "Any argument can modify quite significantly the behavior of a"
240
            " program, whether regarding the\nrealized operation (read, write, delete, etc.)"
241
            " or accessed resources (path in a file system tree). To\navoid any possibility of"
242
            " misuse of a command by a user, the ambiguities must be removed at the\nlevel of its"
243
            " specification.\n\nFor example, on some systems, the kernel messages are only "
244
            "accessible by root.\nIf a user nevertheless must have the privileges to read them,"
245
            " the argument of the dmesg command has to be restricted\nin order to prevent "
246
            "the user from flushing the buffer through the -c option:\n"
247
            "<pre>\nuser ALL = dmesg &quot;&quot;\n</pre>"
248
        )
249
    )
250
])
251
def test_rationale(rule, result):
252
    assert DEFAULT_RULES[rule].rationale == result
253
254
255
@pytest.mark.unit_test
256
@pytest.mark.parametrize("rule, result", [
257
    (
258
        "xccdf_org.ssgproject.content_rule_prefer_64bit_os",
259
        [RuleWarning(
260
            "There is no remediation besides installing a 64-bit operating system.", "general"
261
        )]
262
    ),
263
    (
264
        "xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_enabled",
265
        []
266
    ),
267
    (
268
        "xccdf_org.ssgproject.content_rule_auditd_data_retention_action_mail_acct",
269
        []
270
    ),
271
    (
272
        "xccdf_org.ssgproject.content_rule_sudoers_explicit_command_args",
273
        [
274
            RuleWarning(
275
                "This rule doesn&#x27;t come with a remediation, as absence of arguments in"
276
                " the user spec doesn&#x27;t mean that the command is intended to be executed "
277
                "with no arguments.", "general"
278
            ),
279
            RuleWarning(
280
                "The rule can produce false findings when an argument contains a"
281
                " comma - sudoers syntax allows comma escaping using backslash, but"
282
                " the check doesn&#x27;t support that. For example,"
283
                " <code>root ALL=(ALL) echo 1\\,2</code> allows root to execute"
284
                " <code>echo 1,2</code>, but the check would interpret it as two commands "
285
                "<code>echo 1\\</code> and <code>2</code>.", "general"
286
            )
287
        ]
288
    )
289
])
290
def test_warnings(rule, result):
291
    assert DEFAULT_RULES[rule].warnings == result
292
293
294
@pytest.mark.unit_test
295
@pytest.mark.parametrize("rule, remediation_id, scripts", [
296
    (
297
        "xccdf_org.ssgproject.content_rule_prefer_64bit_os",
298
        None,
299
        {}
300
    ),
301
    (
302
        "xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_enabled",
303
        "dconf_gnome_screensaver_lock_enabled",
304
        {
305
            "urn:xccdf:fix:script:ansible": "dconf_gnome_screensaver_lock_enabled_ansible.txt",
306
            "urn:xccdf:fix:script:sh": "dconf_gnome_screensaver_lock_enabled_sh.txt"
307
        }
308
    ),
309
    (
310
        "xccdf_org.ssgproject.content_rule_auditd_data_retention_action_mail_acct",
311
        "auditd_data_retention_action_mail_acct",
312
        {
313
            "urn:xccdf:fix:script:sh": "auditd_data_retention_action_mail_acct_sh.txt",
314
            "urn:xccdf:fix:script:ansible": "auditd_data_retention_action_mail_acct_ansible.txt"
315
        }
316
    )
317
])
318
def test_remediations(rule, remediation_id, scripts):
319
    if DEFAULT_RULES[rule].remediations == scripts is None:
320
        return
321
322
    for remediation in DEFAULT_RULES[rule].remediations:
323
        assert remediation.remediation_id == remediation_id
324
        assert remediation.system in scripts
325
        path = PATH_TO_REMEDIATIONS_SCRIPTS / str(scripts[remediation.system])
326
        with open(path, "r", encoding="utf-8") as script:
327
            data = script.read()
328
            assert data == remediation.fix
329
330
331
@pytest.mark.unit_test
332
@pytest.mark.parametrize("rule, oval_def_id, title, description, version", [
333
    (
334
        "xccdf_org.ssgproject.content_rule_prefer_64bit_os",
335
        "oval:ssg-prefer_64bit_os:def:1",
336
        "Prefer to use a 64-bit Operating System when supported",
337
        "Check if the system supports a 64-bit Operating System",
338
        "1"
339
    ),
340
    (
341
        "xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_enabled",
342
        "oval:ssg-dconf_gnome_screensaver_lock_enabled:def:1",
343
        "Enable GNOME3 Screensaver Lock After Idle Period",
344
        "Idle activation of the screen lock should be enabled.",
345
        "2",
346
    ),
347
    (
348
        "xccdf_org.ssgproject.content_rule_installed_OS_is_FIPS_certified",
349
        "oval:ssg-installed_OS_is_FIPS_certified:def:1",
350
        "The Installed Operating System Is FIPS 140-2 Certified",
351
        (
352
            "\n          The operating system installed on the system is"
353
            " a certified operating system that meets FIPS 140-2 requirements.\n      "
354
        ),
355
        "1",
356
    ),
357
    (
358
        "xccdf_org.ssgproject.content_rule_dconf_db_up_to_date",
359
        "oval:ssg-dconf_db_up_to_date:def:1",
360
        "Make sure that the dconf databases are up-to-date with regards to respective keyfiles",
361
        "Make sure that the dconf databases are up-to-date with regards to respective keyfiles.",
362
        "2",
363
    ),
364
    (
365
        "xccdf_org.ssgproject.content_rule_dconf_gnome_screensaver_lock_delay",
366
        "oval:ssg-dconf_gnome_screensaver_lock_delay:def:1",
367
        "Set GNOME3 Screensaver Lock Delay After Activation Period",
368
        (
369
            "Idle activation of the screen lock should be enabled immediately or"
370
            "\n      after a delay."
371
        ),
372
        "2",
373
    )
374
])
375
def test_oval_definition_in_rules(rule, oval_def_id, title, description, version):
376
    oval_definition = BASIC_REPORT.rules[rule].oval_definition
377
    assert oval_definition.definition_id == oval_def_id
378
    assert oval_definition.title == title
379
    assert oval_definition.version == version
380
    assert oval_definition.description == description
381