Test Failed
Push — master ( 6149c6...e6e408 )
by Matthew
02:55 queued 14s
created

test_controls   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 512
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 364
dl 0
loc 512
rs 8.96
c 0
b 0
f 0
wmc 43

31 Functions

Rating   Name   Duplication   Size   Complexity  
A compiled_controls_manager() 0 6 1
B assert_control_confirms_to_standard() 0 42 1
A compiled_controls_dir_py2() 0 3 1
A env_yaml() 0 6 1
A compiled_controls_dir_py3() 0 3 1
A _load_test() 0 2 1
A test_controls_load() 0 2 1
A controls_manager() 0 5 1
A test_manager_removes_rules() 0 22 1
C test_controls_levels() 0 113 7
A one_simple_subcontrol() 0 3 1
A profile_resolution_all() 0 29 1
A test_load_control_from_specific_folder_and_file() 0 2 1
A test_controls_invalid_rules() 0 8 2
A profile_resolution_extends() 0 28 1
A test_profile_resolution_all_inline() 0 3 1
A test_policy_parse_from_ours_and_foreign() 0 22 1
A test_profile_resolution_extends_inline() 0 5 1
A profile_resolution() 0 20 1
A order_by_attribute() 0 3 1
A test_profile_resolution_inline() 0 3 1
A test_policy_parse_from_dict() 0 7 1
A test_load_control_from_folder() 0 2 1
A test_controls_load_product() 0 13 1
A minimal_empty_controls() 0 3 1
A test_policy_parse_from_nested() 0 20 1
A test_control_with_bad_key() 0 8 2
A test_policy_fail_parse_from_incomplete_dict() 0 8 2
A test_policy_parse_from_referenced() 0 14 1
A test_load_control_from_folder_and_file() 0 2 1
A test_load_compiled_control_from_folder_and_file() 0 2 1

2 Methods

Rating   Name   Duplication   Size   Complexity  
A DictContainingAnyRule.__contains__() 0 2 1
A DictContainingAnyRule.__getitem__() 0 4 1

How to fix   Complexity   

Complexity

Complex classes like test_controls often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import pytest
2
import logging
3
import os
4
5
import ssg.controls
6
import ssg.build_yaml
7
from ssg.environment import open_environment
8
from ssg.products import load_product_yaml
9
10
ssg_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
11
data_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "data"))
12
controls_dir = os.path.join(data_dir, "controls_dir")
13
profiles_dir = os.path.join(data_dir, "profiles_dir")
14
15
16
def assert_control_confirms_to_standard(controls_manager, profile):
17
    c_r1 = controls_manager.get_control(profile, "R1")
18
    assert c_r1.title == "User session timeout"
19
    assert c_r1.description == "Remote user sessions must be closed after " \
20
                               "a certain period of inactivity."
21
    assert c_r1.automated == "yes"
22
    c_r1_rules = c_r1.selected
23
    assert "sshd_set_idle_timeout" in c_r1_rules
24
    assert "accounts_tmout" in c_r1_rules
25
    assert "var_accounts_tmout=10_min" not in c_r1_rules
26
    assert "var_accounts_tmout" in c_r1.variables
27
    assert c_r1.variables["var_accounts_tmout"] == "10_min"
28
    # abcd is a level-less policy
29
    assert c_r1.levels == ["default"]
30
    assert "vague" in c_r1.notes
31
    c_r2 = controls_manager.get_control(profile, "R2")
32
    assert c_r2.automated == "no"
33
    assert c_r2.notes == "This is individual depending on the system " \
34
                        "workload therefore needs to be audited manually."
35
    assert c_r2.rationale == "Minimization of configuration helps to reduce attack surface."
36
    c_r4 = controls_manager.get_control(profile, "R4")
37
    assert len(c_r4.selected) == 3
38
    c_r4_rules = c_r4.selected
39
    assert "accounts_passwords_pam_faillock_deny_root" in c_r4_rules
40
    assert "accounts_password_pam_minlen" in c_r4_rules
41
    assert "accounts_password_pam_ocredit" in c_r4_rules
42
    assert "var_password_pam_ocredit" in c_r4.variables
43
    assert c_r4.variables["var_password_pam_ocredit"] == "1"
44
    assert "R4.a" in c_r4.controls
45
    assert "R4.b" in c_r4.controls
46
    c_r5 = controls_manager.get_control(profile, "R5")
47
    assert c_r5.id == "R5"
48
    assert c_r5.status == "does not meet"
49
    assert c_r5.fixtext == "There is no fixtext."
50
    assert c_r5.check == "There is no check."
51
    assert "Although the listed mitigation is supporting the security function" in c_r5.mitigation
52
    assert c_r5.description == \
53
           "The operating system must provide automated mechanisms for supporting account " \
54
           "management functions."
55
    assert "Enterprise environments make account management challenging and complex." in \
56
           c_r5.rationale
57
    assert c_r5.status_justification == "Mitigate with third-party software."
58
59
60
@pytest.fixture
61
def env_yaml():
62
    product_yaml = os.path.join(ssg_root, "products", "rhel8", "product.yml")
63
    build_config_yaml = os.path.join(ssg_root, "build", "build_config.yml")
64
    env_yaml = open_environment(build_config_yaml, product_yaml)
65
    return env_yaml
66
67
68
@pytest.fixture
69
def controls_manager(env_yaml):
70
    controls_manager = ssg.controls.ControlsManager(controls_dir, env_yaml)
71
    controls_manager.load()
72
    return controls_manager
73
74
75
@pytest.fixture
76
def compiled_controls_dir_py2(tmpdir):
77
    return str(tmpdir)
78
79
80
@pytest.fixture
81
def compiled_controls_dir_py3(tmp_path):
82
    return tmp_path
83
84
85
@pytest.fixture
86
def compiled_controls_manager(env_yaml, controls_manager,compiled_controls_dir_py2):
87
    controls_manager.save_everything(compiled_controls_dir_py2)
88
    controls_manager = ssg.controls.ControlsManager(compiled_controls_dir_py2, env_yaml)
89
    controls_manager.load()
90
    return controls_manager
91
92
93
def _load_test(controls_manager, profile):
94
    assert_control_confirms_to_standard(controls_manager, profile)
95
96
97
def test_controls_load(controls_manager):
98
    _load_test(controls_manager, "abcd")
99
100
101
def test_controls_invalid_rules(env_yaml):
102
    existing_rules = {"accounts_tmout", "configure_crypto_policy"}
103
    controls_manager = ssg.controls.ControlsManager(
104
        controls_dir, env_yaml, existing_rules)
105
    with pytest.raises(ValueError) as exc:
106
        controls_manager.load()
107
    assert str(exc.value) == \
108
        "Control abcd:R1 contains nonexisting rule(s) sshd_set_idle_timeout"
109
110
def test_controls_levels(controls_manager):
111
    # Default level is the lowest level
112
    c_1 = controls_manager.get_control("abcd-levels", "S1")
113
    assert c_1.levels == ["low"]
114
    c_4 = controls_manager.get_control("abcd-levels", "S4")
115
    assert c_4.levels == ["low"]
116
117
    # Explicit levels
118
    c_2 = controls_manager.get_control("abcd-levels", "S2")
119
    assert c_2.levels == ["low"]
120
121
    c_3 = controls_manager.get_control("abcd-levels", "S3")
122
    assert c_3.levels == ["medium"]
123
124
    c_4a = controls_manager.get_control("abcd-levels", "S4.a")
125
    assert c_4a.levels == ["low"]
126
127
    c_4b = controls_manager.get_control("abcd-levels", "S4.b")
128
    assert c_4b.levels == ["high"]
129
130
    c_5 = controls_manager.get_control("abcd-levels", "S5")
131
    assert c_5.levels == ["low"]
132
133
    c_6 = controls_manager.get_control("abcd-levels", "S6")
134
    assert c_6.levels == ["medium"]
135
136
    c_7 = controls_manager.get_control("abcd-levels", "S7")
137
    assert c_7.levels == ["high"]
138
139
    # test if all crypto-policy controls have the rule selected
140
    assert "configure_crypto_policy" in c_5.selections
141
    assert "configure_crypto_policy" in c_6.selections
142
    assert "configure_crypto_policy" in c_7.selections
143
144
    # just the essential controls
145
    low_controls = controls_manager.get_all_controls_of_level(
146
        "abcd-levels", "low")
147
    # essential and more advanced together
148
    medium_controls = controls_manager.get_all_controls_of_level(
149
        "abcd-levels", "medium")
150
    high_controls = controls_manager.get_all_controls_of_level(
151
        "abcd-levels", "high")
152
    all_controls = controls_manager.get_all_controls("abcd-levels")
153
154
    assert len(high_controls) == len(all_controls)
155
    assert len(low_controls) <= len(high_controls)
156
    assert len(low_controls) == 5
157
    assert len(medium_controls) == 7
158
159
    # test overriding of variables in levels
160
    assert c_2.variables["var_password_pam_minlen"] == "1"
161
    assert "var_password_pam_minlen" not in c_3.variables.keys()
162
    assert c_4b.variables["var_password_pam_minlen"] == "2"
163
164
    variable_found = False
165
    for c in low_controls:
166
        if "var_password_pam_minlen" in c.variables.keys():
167
            variable_found = True
168
            assert c.variables["var_password_pam_minlen"] == "1"
169
    assert variable_found
170
171
    variable_found = False
172
    for c in medium_controls:
173
        if "var_password_pam_minlen" in c.variables.keys():
174
            variable_found = True
175
            assert c.variables["var_password_pam_minlen"] == "1"
176
    assert variable_found
177
178
    variable_found = False
179
    for c in high_controls:
180
        if "var_password_pam_minlen" in c.variables.keys():
181
            variable_found = True
182
            assert c.variables["var_password_pam_minlen"] == "2"
183
    assert variable_found
184
185
    # now test if controls of lower level has the variable definition correctly removed
186
    # because it is overriden by higher level controls
187
    s2_high = [c for c in high_controls if c.id == "S2"]
188
    assert len(s2_high) == 1
189
    assert "var_some_variable" not in s2_high[0].variables.keys()
190
    assert "var_password_pam_minlen" not in s2_high[0].variables.keys()
191
    s4b_high = [c for c in high_controls if c.id == "S4.b"]
192
    assert len(s4b_high) == 1
193
    assert s4b_high[0].variables["var_some_variable"] == "3"
194
    assert s4b_high[0].variables["var_password_pam_minlen"] == "2"
195
196
    # check that in low level the variable is correctly placed there in S2
197
    s2_low = [c for c in low_controls if c.id == "S2"]
198
    assert len(s2_low) == 1
199
    assert s2_low[0].variables["var_some_variable"] == "1"
200
    assert s2_low[0].variables["var_password_pam_minlen"] == "1"
201
202
    # check that low, medium and high levels have crypto policy selected
203
    s5_low = [c for c in low_controls if c.id == "S5"]
204
    assert len(s5_low) == 1
205
    assert "configure_crypto_policy" in s5_low[0].selections
206
207
    s5_medium = [c for c in medium_controls if c.id == "S5"]
208
    assert len(s5_medium) == 1
209
    assert "configure_crypto_policy" in s5_medium[0].selections
210
    s6_medium = [c for c in medium_controls if c.id == "S6"]
211
    assert len(s6_medium) == 1
212
    assert "configure_crypto_policy" in s6_medium[0].selections
213
214
    s5_high = [c for c in high_controls if c.id == "S5"]
215
    assert len(s5_high) == 1
216
    assert "configure_crypto_policy" in s5_high[0].selections
217
    s6_high = [c for c in high_controls if c.id == "S6"]
218
    assert len(s6_high) == 1
219
    assert "configure_crypto_policy" in s6_high[0].selections
220
    s7_high = [c for c in high_controls if c.id == "S7"]
221
    assert len(s7_high) == 1
222
    assert "configure_crypto_policy" in s7_high[0].selections
223
224
225
def test_controls_load_product(controls_manager):
226
    c_r1 = controls_manager.get_control("abcd", "R1")
227
    assert c_r1.title == "User session timeout"
228
    assert c_r1.description == "Remote user sessions must be closed after " \
229
        "a certain period of inactivity."
230
    assert c_r1.automated == "yes"
231
232
    c_r1_rules = c_r1.selected
233
    assert "sshd_set_idle_timeout" in c_r1_rules
234
    assert "accounts_tmout" in c_r1_rules
235
    assert "var_accounts_tmout=10_min" not in c_r1_rules
236
    assert "var_accounts_tmout" in c_r1.variables
237
    assert c_r1.variables["var_accounts_tmout"] == "10_min"
238
239
240
def test_profile_resolution_inline(env_yaml, controls_manager):
241
    profile_resolution(
242
        env_yaml, controls_manager, ssg.build_yaml.ProfileWithInlinePolicies, "abcd-low-inline")
243
244
245
def test_profile_resolution_extends_inline(env_yaml, controls_manager):
246
    profile_resolution_extends(
247
        env_yaml, controls_manager,
248
        ssg.build_yaml.ProfileWithInlinePolicies,
249
        "abcd-low-inline", "abcd-high-inline")
250
251
252
def test_profile_resolution_all_inline(env_yaml, controls_manager):
253
    profile_resolution_all(
254
        env_yaml, controls_manager, ssg.build_yaml.ProfileWithInlinePolicies, "abcd-all-inline")
255
256
257
class DictContainingAnyRule(dict):
258
    def __getitem__(self, key):
259
        rule = ssg.build_yaml.Rule(key)
260
        rule.product = "all"
261
        return rule
262
263
    def __contains__(self, rid):
264
        return True
265
266
267
def profile_resolution(env_yaml, controls_manager, cls, profile_low):
268
    low_profile_path = os.path.join(profiles_dir, profile_low + ".profile")
269
    profile = cls.from_yaml(low_profile_path, env_yaml)
270
    all_profiles = {"abcd-low": profile}
271
    rules_by_id = DictContainingAnyRule()
272
273
    profile.resolve(all_profiles, rules_by_id, controls_manager=controls_manager)
274
275
    # Profile 'abcd-low' selects controls R1, R2, R3 from 'abcd' policy,
276
    # which should add the following rules to the profile:
277
    selected = profile.get_rule_selectors()
278
    assert "sshd_set_idle_timeout" in selected
279
    assert "accounts_tmout" in selected
280
    assert "configure_crypto_policy" in selected
281
    assert "var_accounts_tmout" in profile.variables
282
283
    # The rule "security_patches_up_to_date" has been selected directly
284
    # by profile selections, not by using controls, so it should be in
285
    # the resolved profile as well.
286
    assert "security_patches_up_to_date" in selected
287
288
289
def profile_resolution_extends(env_yaml, controls_manager, cls, profile_low, profile_high):
290
    low_profile_path = os.path.join(profiles_dir, profile_low + ".profile")
291
    low_profile = cls.from_yaml(low_profile_path, env_yaml)
292
    high_profile_path = os.path.join(profiles_dir, profile_high + ".profile")
293
    high_profile = cls.from_yaml(high_profile_path, env_yaml)
294
    all_profiles = {profile_low: low_profile, profile_high: high_profile}
295
    rules_by_id = DictContainingAnyRule()
296
297
    high_profile.resolve(all_profiles, rules_by_id, controls_manager=controls_manager)
298
299
    # Profile 'abcd-high' selects controls R1, R2, R3 from 'abcd' policy,
300
    # which should add the following rules to the profile:
301
    selected = high_profile.get_rule_selectors()
302
    assert "sshd_set_idle_timeout" in selected
303
    assert "accounts_tmout" in selected
304
    assert "configure_crypto_policy" in selected
305
    assert "var_accounts_tmout" in high_profile.variables
306
307
    # The rule "security_patches_up_to_date" has been selected directly by the
308
    # abcd-low profile selections, not by using controls, so it should be
309
    # in the resolved profile as well.
310
    assert "security_patches_up_to_date" in selected
311
312
    assert "accounts_passwords_pam_faillock_deny_root" in selected
313
    assert "accounts_password_pam_minlen" in selected
314
    assert "accounts_password_pam_ocredit" in selected
315
    assert "var_password_pam_ocredit" in high_profile.variables
316
    assert high_profile.variables["var_password_pam_ocredit"] == "2"
317
318
319
def profile_resolution_all(env_yaml, controls_manager, cls, profile_all):
320
    profile_path = os.path.join(profiles_dir, profile_all + ".profile")
321
    profile = cls.from_yaml(profile_path, env_yaml)
322
    all_profiles = {profile_all: profile}
323
    rules_by_id = DictContainingAnyRule()
324
325
    profile.resolve(all_profiles, rules_by_id, controls_manager=controls_manager)
326
327
    # Profile 'abcd-all' selects all controls from 'abcd' policy,
328
    # which should add the following rules and variables to the profile:
329
    selected = profile.get_rule_selectors()
330
    assert "sshd_set_idle_timeout" in selected
331
    assert "accounts_tmout" in selected
332
    assert "var_accounts_tmout" in profile.variables
333
    assert profile.variables["var_accounts_tmout"] == "10_min"
334
    assert "configure_crypto_policy" in selected
335
    # Rule "systemd_target_multi_user" is only "related_rules"
336
    # therefore it should not appear in the resolved profile.
337
    assert "systemd_target_multi_user" not in selected
338
    assert "accounts_passwords_pam_faillock_deny_root" in selected
339
    assert "accounts_password_pam_minlen" in selected
340
    assert "accounts_password_pam_ocredit" in selected
341
    assert "var_password_pam_ocredit" in profile.variables
342
    assert profile.variables["var_password_pam_ocredit"] == "1"
343
344
    # The rule "security_patches_up_to_date" has been selected directly
345
    # by profile selections, not by using controls, so it should be in
346
    # the resolved profile as well.
347
    assert "security_patches_up_to_date" in selected
348
349
350
def test_load_control_from_folder(controls_manager):
351
    _load_test(controls_manager, "qrst")
352
353
354
def test_load_control_from_folder_and_file(controls_manager):
355
    _load_test(controls_manager, "jklm")
356
357
358
def test_load_compiled_control_from_folder_and_file(compiled_controls_dir_py2, compiled_controls_manager):
359
    _load_test(compiled_controls_manager, "jklm")
360
361
362
def test_load_control_from_specific_folder_and_file(controls_manager):
363
    _load_test(controls_manager, "nopq")
364
365
366
@pytest.fixture
367
def minimal_empty_controls():
368
    return [dict(title="control", id="c", rules=["a"])]
369
370
371
@pytest.fixture
372
def one_simple_subcontrol():
373
    return dict(title="subcontrol", id="s", rules=["b"])
374
375
376
def test_policy_parse_from_dict(minimal_empty_controls):
377
    policy = ssg.controls.Policy("")
378
    controls = policy.save_controls_tree(minimal_empty_controls)
379
    controls = policy.controls
380
    assert len(controls) == 1
381
    control = controls[0]
382
    assert control.title == "control"
383
384
385
def test_policy_fail_parse_from_incomplete_dict(minimal_empty_controls):
386
    incomplete_controls = minimal_empty_controls
387
    incomplete_controls[0]["controls"] = [dict(title="nested")]
388
389
    policy = ssg.controls.Policy("")
390
391
    with pytest.raises(RuntimeError, match="id"):
392
        policy.save_controls_tree(incomplete_controls)
393
394
395
def order_by_attribute(items, attribute, ordering):
396
    items_by_attribute = {getattr(i, attribute): i for i in items}
397
    return [items_by_attribute[val] for val in ordering]
398
399
400
def test_policy_parse_from_nested(minimal_empty_controls, one_simple_subcontrol):
401
    nested_controls = minimal_empty_controls
402
    nested_controls[0]["controls"] = [one_simple_subcontrol]
403
404
    policy = ssg.controls.Policy("P")
405
    controls = policy.save_controls_tree(minimal_empty_controls)
406
    controls = policy.controls
407
    assert len(controls) == 2
408
    control, subcontrol = order_by_attribute(controls, "id", ("c", "s"))
409
    assert control.title == "control"
410
    assert control.selections == ["a"]
411
    assert subcontrol.title == "subcontrol"
412
    assert subcontrol.selections == ["b"]
413
414
    controls_manager = ssg.controls.ControlsManager("", dict())
415
    controls_manager.policies[policy.id] = policy
416
417
    controls_manager.resolve_controls()
418
    control = policy.get_control("c")
419
    assert control.selections == ["a", "b"]
420
421
422
def test_manager_removes_rules():
423
    control_dict = dict(id="top", rules=["one", "two", "three", "!four", "!five"])
424
425
    policy = ssg.controls.Policy("")
426
    policy.save_controls_tree([control_dict])
427
    policy.id = "P"
428
429
    controls_manager = ssg.controls.ControlsManager("", dict())
430
    controls_manager.policies[policy.id] = policy
431
432
    control = controls_manager.get_control("P", "top")
433
    assert len(control.selections) == 5
434
435
    controls_manager.remove_selections_not_known(["one", "four"])
436
    control = controls_manager.get_control("P", "top")
437
    assert len(control.selections) == 2
438
    assert "one" in control.selections
439
    assert "!four" in control.selections
440
441
    controls_manager.remove_selections_not_known([])
442
    control = controls_manager.get_control("P", "top")
443
    assert len(control.selections) == 0
444
445
446
def test_policy_parse_from_nested():
447
    top_control_dict = dict(id="top", controls=["nested-1"])
448
    first_nested_dict = dict(id="nested-1", controls=["nested-2"], rules="Y")
449
    second_nested_dict = dict(id="nested-2", rules=["X"])
450
451
    policy = ssg.controls.Policy("")
452
    policy.id = "P"
453
454
    controls_manager = ssg.controls.ControlsManager("", dict())
455
    controls_manager.policies[policy.id] = policy
456
457
    controls = policy.save_controls_tree([top_control_dict, second_nested_dict, first_nested_dict])
458
    controls_manager.resolve_controls()
459
    control = policy.get_control("top")
460
    assert "Y" in control.selections
461
    assert "X" in control.selections
462
463
464
def test_policy_parse_from_ours_and_foreign():
465
    main_control_dict = dict(id="top", controls=["foreign:top", "ours", "P:ours_qualified"])
466
    main_subcontrol_dicts = [dict(id="ours", rules=["ours"]), dict(id="ours_qualified", rules=["really_ours"])]
467
    foreign_control_dict = dict(id="top", rules=["foreign"])
468
469
    main_policy = ssg.controls.Policy("")
470
    main_policy.id = "P"
471
    main_policy.save_controls_tree([main_control_dict] + main_subcontrol_dicts)
472
473
    foreign_policy = ssg.controls.Policy("")
474
    foreign_policy.id = "foreign"
475
    foreign_policy.save_controls_tree([foreign_control_dict])
476
477
    controls_manager = ssg.controls.ControlsManager("", dict())
478
    controls_manager.policies[main_policy.id] = main_policy
479
    controls_manager.policies[foreign_policy.id] = foreign_policy
480
481
    controls_manager.resolve_controls()
482
    control = controls_manager.get_control("P", "top")
483
    assert "ours" in control.selections
484
    assert "really_ours" in control.selections
485
    assert "foreign" in control.selections
486
487
488
def test_policy_parse_from_referenced(minimal_empty_controls, one_simple_subcontrol):
489
    nested_controls = minimal_empty_controls
490
    nested_controls[0]["controls"] = ["s"]
491
    nested_controls.append(one_simple_subcontrol)
492
493
    policy = ssg.controls.Policy("")
494
    policy.id = "P"
495
    controls = policy.save_controls_tree(minimal_empty_controls)
496
    controls = policy.controls
497
    assert len(controls) == 2
498
    control, subcontrol = order_by_attribute(controls, "id", ("c", "s"))
499
    assert control.title == "control"
500
    assert control.controls == ["s"]
501
    assert subcontrol.title == "subcontrol"
502
503
504
def test_control_with_bad_key():
505
    control = {'id': 'abcd', 'badval': 'should not be here', }
506
    control_obj = None
507
    try:
508
        control_obj = ssg.controls.Control.from_control_dict(control)
509
    except ValueError as e:
510
        assert type(e) is ValueError
511
    assert control_obj is None
512