FirewallRules.eval_rules()   F
last analyzed

Complexity

Conditions 22

Size

Total Lines 127
Code Lines 90

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 90
dl 0
loc 127
rs 0
c 0
b 0
f 0
cc 22
nop 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like org_fedora_oscap.rule_handling.FirewallRules.eval_rules() 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
#
2
# Copyright (C) 2013  Red Hat, Inc.
3
#
4
# This copyrighted material is made available to anyone wishing to use,
5
# modify, copy, or redistribute it subject to the terms and conditions of
6
# the GNU General Public License v.2, or (at your option) any later version.
7
# This program is distributed in the hope that it will be useful, but WITHOUT
8
# ANY WARRANTY expressed or implied, including the implied warranties of
9
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
10
# Public License for more details.  You should have received a copy of the
11
# GNU General Public License along with this program; if not, write to the
12
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
13
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
14
# source code or documentation are not subject to the GNU General Public
15
# License and may only be used or replicated with the express permission of
16
# Red Hat, Inc.
17
#
18
# Red Hat Author(s): Vratislav Podzimek <[email protected]>
19
#
20
21
"""
22
Module with various classes for handling pre-installation rules.
23
24
"""
25
26
import optparse
27
import shlex
28
import logging
29
30
from pyanaconda.modules.common.util import is_module_available
31
from pyanaconda.core.constants import (
32
    FIREWALL_ENABLED, FIREWALL_DISABLED, FIREWALL_USE_SYSTEM_DEFAULTS,
33
    PASSWORD_POLICY_ROOT
34
    )
35
from pyanaconda.modules.common.constants.objects import (
36
    FIREWALL, BOOTLOADER, DEVICE_TREE,
37
    USER_INTERFACE
38
    )
39
from pyanaconda.modules.common.constants.services import NETWORK, STORAGE, USERS, BOSS
40
from pyanaconda.modules.common.structures.policy import PasswordPolicy
41
42
from org_fedora_oscap import common
43
from org_fedora_oscap.common import OSCAPaddonError, RuleMessage, KDUMP, get_packages_data, \
44
    set_packages_data
45
46
# everything else should be private
47
__all__ = ["RuleData"]
48
49
50
# Mapping of packages to package environments and/or groups that depends on them
51
# See also https://access.redhat.com/solutions/1201413 how to get group IDs.
52
# on RHEL8, use e.g. grep -R "<id>" /var/cache/dnf/*
53
ESSENTIAL_PACKAGES = {
54
    "xorg-x11-server-common": {
55
        "env": ["graphical-server-environment", "workstation-product-environment"],
56
        "groups": ["workstation-product-environment"],
57
    },
58
    "nfs-utils": {
59
        "env": ["graphical-server-environment", "workstation-product-environment"],
60
        "groups": ["workstation-product-environment"],
61
    },
62
    "tftp": {
63
        "groups": ["network-server"],
64
    },
65
    "abrt": {
66
        "groups": ["debugging"],
67
    },
68
    "gssproxy": {
69
        "groups": ["file-server"],
70
    },
71
}
72
73
log = logging.getLogger("anaconda")
74
75
_ = common._
76
77
78
def get_rule_data_from_content(profile_id, content_path, ds_id="", xccdf_id="", tailoring_path=""):
79
    rules = common.get_fix_rules_pre(
80
        profile_id, content_path, ds_id, xccdf_id, tailoring_path)
81
82
    # parse and store rules with a clean RuleData instance
83
    rule_data = RuleData()
84
    for rule in rules.splitlines():
85
        rule_data.new_rule(rule)
86
    return rule_data
87
88
89
# TODO: use set instead of list for mount options?
90
def parse_csv(option, opt_str, value, parser):
91
    for item in value.split(","):
92
        if item:
93
            parser.values.ensure_value(option.dest, []).append(item)
94
95
96
class ModifiedOptionParserException(Exception):
97
    """Exception to be raised by ModifiedOptionParser."""
98
    pass
99
100
101
class ModifiedOptionParser(optparse.OptionParser):
102
    """Overrides error behavior of OptionParser."""
103
    def error(self, msg):
104
        raise ModifiedOptionParserException(msg)
105
106
    def exit(self, status=0, msg=None):
107
        raise ModifiedOptionParserException(msg)
108
109
110
PART_RULE_PARSER = ModifiedOptionParser()
111
PART_RULE_PARSER.add_option("--mountoptions", dest="mount_options",
112
                            action="callback", callback=parse_csv, nargs=1,
113
                            type="string")
114
115
PASSWD_RULE_PARSER = ModifiedOptionParser()
116
PASSWD_RULE_PARSER.add_option("--minlen", dest="minlen", action="store",
117
                              default=0, type="int")
118
119
PACKAGE_RULE_PARSER = ModifiedOptionParser()
120
PACKAGE_RULE_PARSER.add_option("--add", dest="add_pkgs", action="append",
121
                               type="string")
122
PACKAGE_RULE_PARSER.add_option("--remove", dest="remove_pkgs", action="append",
123
                               type="string")
124
125
BOOTLOADER_RULE_PARSER = ModifiedOptionParser()
126
BOOTLOADER_RULE_PARSER.add_option("--passwd", dest="passwd", action="store_true",
127
                                  default=False)
128
129
KDUMP_RULE_PARSER = ModifiedOptionParser()
130
KDUMP_RULE_PARSER.add_option("--enable", action="store_true",
131
                             dest="kdenabled", default=None)
132
KDUMP_RULE_PARSER.add_option("--disable", action="store_false",
133
                             dest="kdenabled", default=None)
134
135
FIREWALL_RULE_PARSER = ModifiedOptionParser()
136
FIREWALL_RULE_PARSER.add_option("--enable", action="store_true",
137
                                dest="fwenabled", default=None)
138
FIREWALL_RULE_PARSER.add_option("--disable", action="store_false",
139
                                dest="fwenabled", default=None)
140
FIREWALL_RULE_PARSER.add_option("--service", dest="add_svcs", action="append",
141
                                type="string")
142
FIREWALL_RULE_PARSER.add_option("--port", dest="add_port", action="append",
143
                                type="string")
144
FIREWALL_RULE_PARSER.add_option("--trust", dest="add_trust", action="append",
145
                                type="string")
146
FIREWALL_RULE_PARSER.add_option("--remove-service", dest="remove_svcs",
147
                                action="append", type="string")
148
149
150
class RuleHandler(object):
151
    """Base class for the rule handlers."""
152
153
    def eval_rules(self, ksdata, storage, report_only=False):
154
        """
155
        Method that should check the current state (as defined by the ksdata
156
        and storage parameters) against the rules the instance of RuleHandler
157
        holds. Depending on the value of report_only it should fix the state
158
        with changes that can be done automatically or not and return the list
159
        of warnings and errors for fixes that need to be done manually together
160
        with info messages about the automatic changes. One should make sure
161
        this method is called with report_only set to False at least once so
162
        that the automatic fixes are done.
163
164
        :param ksdata: data representing the values set by user
165
        :type ksdata: pykickstart.base.BaseHandler
166
        :param storage: object storing storage-related information
167
                        (disks, partitioning, bootloader, etc.)
168
        :type storage: blivet.Blivet
169
        :param report_only: whether to do fixing or just report information
170
        :type report_only: bool
171
        :return: errors and warnings for fixes that need to be done manually
172
                 and info messages about the automatic changes
173
        :rtype: list of common.RuleMessage objects
174
175
        """
176
177
        return []
178
179
    def revert_changes(self, ksdata, storage):
180
        """
181
        Method that should revert all changes done by the previous calls of the
182
        eval_rules method with the report_only set to False.
183
184
        :see: eval_rules
185
186
        """
187
188
        # inheriting classes are supposed to override this
189
        pass
190
191
192
class UknownRuleError(OSCAPaddonError):
193
    """Exception class for cases when an uknown rule is to be processed."""
194
195
    pass
196
197
198
class RuleData(RuleHandler):
199
    """Class holding data parsed from the applied rules."""
200
201
    def __init__(self):
202
        """Constructor initializing attributes."""
203
204
        self._part_rules = PartRules()
205
        self._passwd_rules = PasswdRules()
206
        self._package_rules = PackageRules()
207
        self._bootloader_rules = BootloaderRules()
208
        self._kdump_rules = KdumpRules()
209
        self._firewall_rules = FirewallRules()
210
211
        self._rule_handlers = (self._part_rules, self._passwd_rules,
212
                               self._package_rules, self._bootloader_rules,
213
                               self._kdump_rules, self._firewall_rules,
214
                               )
215
216
    def __str__(self):
217
        """Standard method useful for debugging and testing."""
218
219
        ret = ""
220
221
        part_strs = str(self._part_rules)
222
        if part_strs:
223
            ret += part_strs
224
225
        passwd_str = str(self._passwd_rules)
226
        if passwd_str:
227
            ret += "\n" + passwd_str
228
229
        packages_str = str(self._package_rules)
230
        if packages_str:
231
            ret += "\n" + packages_str
232
233
        firewall_str = str(self._firewall_rules)
234
        if firewall_str:
235
            ret += "\n" + firewall_str
236
237
        return ret
238
239
    def new_rule(self, rule):
240
        """
241
        Method that handles a single rule line (e.g. "part /tmp").
242
243
        :param rule: a single rule line
244
        :type rule: str
245
246
        """
247
248
        actions = {"part": self._new_part_rule,
249
                   "passwd": self._new_passwd_rule,
250
                   "package": self._new_package_rule,
251
                   "bootloader": self._new_bootloader_rule,
252
                   "kdump": self._new_kdump_rule,
253
                   "firewall": self._new_firewall_rule,
254
                   }
255
256
        rule = rule.strip()
257
        if not rule:
258
            return
259
260
        first_word = rule.split(None, 1)[0]
261
        try:
262
            actions[first_word](rule)
263
        except (ModifiedOptionParserException, KeyError) as e:
264
            log.warning("OSCAP Addon: Unknown OSCAP Addon rule '{}': {}".format(rule, e))
265
266
    def eval_rules(self, ksdata, storage, report_only=False):
267
        """:see: RuleHandler.eval_rules"""
268
269
        messages = []
270
271
        # evaluate all subgroups of rules
272
        for rule_handler in self._rule_handlers:
273
            messages += rule_handler.eval_rules(ksdata, storage, report_only)
274
275
        return messages
276
277
    def revert_changes(self, ksdata, storage):
278
        """:see: RuleHandler.revert_changes"""
279
280
        # revert changes in all subgroups of rules
281
        for rule_handler in self._rule_handlers:
282
            rule_handler.revert_changes(ksdata, storage)
283
284
    def _new_part_rule(self, rule):
285
        args = shlex.split(rule)
286
        (opts, args) = PART_RULE_PARSER.parse_args(args)
287
288
        # args contain both "part" and mount point (e.g. "/tmp")
289
        mount_point = args[1]
290
291
        self._part_rules.ensure_mount_point(mount_point)
292
293
        if opts.mount_options:
294
            part_data = self._part_rules[mount_point]
295
            part_data.add_mount_options(opts.mount_options)
296
297
    def _new_passwd_rule(self, rule):
298
        args = shlex.split(rule)
299
        (opts, args) = PASSWD_RULE_PARSER.parse_args(args)
300
301
        self._passwd_rules.update_minlen(opts.minlen)
302
303
    def _new_package_rule(self, rule):
304
        args = shlex.split(rule)
305
        (opts, args) = PACKAGE_RULE_PARSER.parse_args(args)
306
307
        self._package_rules.add_packages(opts.add_pkgs)
308
        self._package_rules.remove_packages(opts.remove_pkgs)
309
310
    def _new_bootloader_rule(self, rule):
311
        args = shlex.split(rule)
312
        (opts, args) = BOOTLOADER_RULE_PARSER.parse_args(args)
313
314
        if opts.passwd:
315
            self._bootloader_rules.require_password()
316
317
    def _new_kdump_rule(self, rule):
318
        args = shlex.split(rule)
319
        (opts, args) = KDUMP_RULE_PARSER.parse_args(args)
320
321
        self._kdump_rules.kdump_enabled(opts.kdenabled)
322
323
    def _new_firewall_rule(self, rule):
324
        args = shlex.split(rule)
325
        (opts, args) = FIREWALL_RULE_PARSER.parse_args(args)
326
327
        self._firewall_rules.add_services(opts.add_svcs)
328
        self._firewall_rules.remove_services(opts.remove_svcs)
329
        self._firewall_rules.add_trusts(opts.add_trust)
330
        self._firewall_rules.add_ports(opts.add_port)
331
        self._firewall_rules.firewall_enabled(opts.fwenabled)
332
333
    @property
334
    def passwd_rules(self):
335
        # needed for fixups in GUI
336
        return self._passwd_rules
337
338
339
class PartRules(RuleHandler):
340
    """Simple class holding data from the rules affecting partitioning."""
341
342
    def __init__(self):
343
        """Constructor initializing attributes."""
344
345
        self._rules = dict()
346
347
    def __str__(self):
348
        """Standard method useful for debugging and testing."""
349
350
        return "\n".join(str(rule) for rule in self._rules.values())
351
352
    def __getitem__(self, key):
353
        """Method to support dictionary-like syntax."""
354
355
        return self._rules[key]
356
357
    def __setitem__(self, key, value):
358
        """Method to support dictionary-like syntax."""
359
360
        self._rules[key] = value
361
362
    def __delitem__(self, key):
363
        """One of the methods needed to implement a container."""
364
365
        self._rules.__delitem__(key)
366
367
    def __len__(self):
368
        """One of the methods needed to implement a container."""
369
370
        return self._rules.__len__()
371
372
    def __contains__(self, key):
373
        """Method needed for the 'in' operator to work."""
374
375
        return key in self._rules
376
377
    def ensure_mount_point(self, mount_point):
378
        if mount_point not in self._rules:
379
            self._rules[mount_point] = PartRule(mount_point)
380
381
    def eval_rules(self, ksdata, storage, report_only=False):
382
        """:see: RuleHandler.eval_rules"""
383
384
        messages = []
385
        for part_rule in self._rules.values():
386
            messages += part_rule.eval_rules(ksdata, storage, report_only)
387
388
        return messages
389
390
    def revert_changes(self, ksdata, storage):
391
        """:see: RuleHandler.revert_changes"""
392
393
        for part_rule in self._rules.values():
394
            part_rule.revert_changes(ksdata, storage)
395
396
397
class PartRule(RuleHandler):
398
    """Simple class holding rule data for a single partition/mount point."""
399
400
    def __init__(self, mount_point):
401
        """
402
        Constructor initializing attributes.
403
404
        :param mount_point: the mount point the object holds data for
405
        :type mount_point: str
406
407
        """
408
409
        self._mount_point = mount_point
410
        self._mount_options = []
411
        self._added_mount_options = []
412
413
    def __str__(self):
414
        """Standard method useful for debugging and testing."""
415
416
        ret = "part %s" % self._mount_point
417
        if self._mount_options:
418
            ret += " --mountoptions=%s" % ",".join(self._mount_options)
419
420
        return ret
421
422
    def add_mount_options(self, mount_options):
423
        """
424
        Add  new mount options (do not add duplicates).
425
426
        :param mount_options: list of mount options to be added
427
        :type mount_options: list of strings
428
429
        """
430
431
        self._mount_options.extend(opt for opt in mount_options
432
                                   if opt not in self._mount_options)
433
434
    def eval_rules(self, ksdata, storage, report_only=False):
435
        """:see: RuleHandler.eval_rules"""
436
        device_tree = STORAGE.get_proxy(DEVICE_TREE)
437
        mount_points = device_tree.GetMountPoints()
438
        messages = []
439
440
        if self._mount_point not in mount_points:
441
            msg = _("{0} must be on a separate partition or logical "
442
                    "volume and has to be created in the "
443
                    "partitioning layout before installation can occur "
444
                    "with a security profile").format(self._mount_point)
445
            messages.append(RuleMessage(self.__class__,
446
                                        common.MESSAGE_TYPE_FATAL, msg))
447
448
            # mount point doesn't exist, nothing more can be found here
449
            return messages
450
451
        # template for the message
452
        msg_tmpl = _("mount option '%(mount_option)s' added for "
453
                     "the mount point %(mount_point)s")
454
455
        # add message for every option already added
456
        for opt in self._added_mount_options:
457
            msg = msg_tmpl % {"mount_option": opt,
458
                              "mount_point": self._mount_point}
459
            messages.append(RuleMessage(self.__class__,
460
                                        common.MESSAGE_TYPE_INFO, msg))
461
462
        # mount point to be created during installation
463
        target_name = mount_points[self._mount_point]
464
        mount_options = device_tree.GetDeviceMountOptions(target_name)
465
466
        # generator for the new options that should be added
467
        new_opts = (opt for opt in self._mount_options
0 ignored issues
show
introduced by
The variable opt does not seem to be defined in case the for loop on line 456 is not entered. Are you sure this can never be the case?
Loading history...
468
                    if opt not in mount_options.split(","))
469
470
        # add message for every mount option added
471
        for opt in new_opts:
472
            msg = msg_tmpl % {"mount_option": opt,
473
                              "mount_point": self._mount_point}
474
475
            # add message for the mount option in any case
476
            messages.append(RuleMessage(self.__class__,
477
                                        common.MESSAGE_TYPE_INFO, msg))
478
479
            # add new options to the target mount point if not reporting only
480
            if not report_only:
481
                mount_options += ",%s" % opt
482
                self._added_mount_options.append(opt)
483
484
        if new_opts and not report_only:
485
            device_tree.SetDeviceMountOptions(target_name, mount_options)
486
487
        return messages
488
489
    def revert_changes(self, ksdata, storage):
490
        """
491
        Removes the mount options added to the mount point by this PartRule
492
        instance.
493
494
        :see: RuleHandler.revert_changes
495
496
        """
497
        device_tree = STORAGE.get_proxy(DEVICE_TREE)
498
        mount_points = device_tree.GetMountPoints()
499
500
        if self._mount_point not in mount_points:
501
            # mount point doesn't exist, nothing can be reverted
502
            return
503
504
        # mount point to be created during installation
505
        target_name = mount_points[self._mount_point]
506
507
        # mount options to be defined for the created mount point
508
        mount_options = device_tree.GetDeviceMountOptions(target_name)
509
510
        # generator of the options that should remain
511
        result_opts = (opt for opt in mount_options.split(",")
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable opt does not seem to be defined.
Loading history...
512
                       if opt not in self._added_mount_options)
513
514
        # set the new list of options
515
        mount_options = ",".join(result_opts)
516
        device_tree.SetDeviceMountOptions(target_name, mount_options)
517
518
        # reset the remembered added mount options
519
        self._added_mount_options = []
520
521
522
class PasswdRules(RuleHandler):
523
    """Simple class holding data from the rules affecting passwords."""
524
525
    def __init__(self):
526
        """Constructor initializing attributes."""
527
528
        self._minlen = 0
529
        self._orig_minlen = None
530
        self._orig_strict = None
531
532
    def __str__(self):
533
        """Standard method useful for debugging and testing."""
534
535
        if self._minlen > 0:
536
            return "passwd --minlen=%d" % self._minlen
537
        else:
538
            return ""
539
540
    def update_minlen(self, minlen):
541
        """Update password minimal length requirements."""
542
543
        if minlen > self._minlen:
544
            self._minlen = minlen
545
546
    def eval_rules(self, ksdata, storage, report_only=False):
547
        """:see: RuleHandler.eval_rules"""
548
549
        if self._minlen == 0:
550
            # no password restrictions, nothing to be done here
551
            return []
552
553
        ret = []
554
555
        users_proxy = USERS.get_proxy()
556
557
        if not users_proxy.IsRootPasswordSet:
558
            # root password was not set
559
560
            msg = _("make sure to create password with minimal length of %d "
561
                    "characters") % self._minlen
562
            ret = [RuleMessage(self.__class__,
563
                               common.MESSAGE_TYPE_WARNING, msg)]
564
        else:
565
            # root password set
566
            if users_proxy.IsRootPasswordCrypted:
567
                msg = _("cannot check root password length (password is crypted)")
568
                log.warning("OSCAP Addon: cannot check root password length (password is crypted)")
569
                return [RuleMessage(self.__class__,
570
                                    common.MESSAGE_TYPE_WARNING, msg)]
571
            elif len(users_proxy.RootPassword) < self._minlen:
572
                # too short
573
                msg = _("root password is too short, a longer one with at "
574
                        "least %d characters is required") % self._minlen
575
                ret = [RuleMessage(self.__class__,
576
                                   common.MESSAGE_TYPE_FATAL, msg)]
577
            else:
578
                ret = []
579
580
        if report_only:
581
            return ret
582
583
        # set the policy in any case (so that a weaker password is not entered)
584
        policies = self._get_password_policies()
585
        policy = policies[PASSWORD_POLICY_ROOT]
586
587
        self._orig_minlen = policy.min_length
588
        self._orig_strict = policy.is_strict
589
        policy.min_length = self._minlen
590
        policy.is_strict = True
591
592
        self._set_password_policies(policies)
593
        return ret
594
595
    def revert_changes(self, ksdata, storage):
596
        """:see: RuleHander.revert_changes"""
597
        policies = self._get_password_policies()
598
        policy = policies[PASSWORD_POLICY_ROOT]
599
600
        if self._orig_minlen is not None:
601
            policy.min_length = self._orig_minlen
602
            self._orig_minlen = None
603
        if self._orig_strict is not None:
604
            policy.is_strict = self._orig_strict
605
            self._orig_strict = None
606
607
        self._set_password_policies(policies)
608
609
    def _get_password_policies(self):
610
        """Get the password policies from the installer.
611
612
        :return: a dictionary of password policies
613
        """
614
        proxy = BOSS.get_proxy(USER_INTERFACE)
615
        policies = PasswordPolicy.from_structure_dict(proxy.PasswordPolicies)
616
617
        if PASSWORD_POLICY_ROOT not in policies:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable PASSWORD_POLICY_ROOT does not seem to be defined.
Loading history...
618
            policy = PasswordPolicy.from_defaults(PASSWORD_POLICY_ROOT)
619
            policies[PASSWORD_POLICY_ROOT] = policy
620
621
        return policies
622
623
    def _set_password_policies(self, policies):
624
        """Set the password policies for the installer.
625
626
        :param policies: a dictionary of password policies
627
        """
628
        proxy = BOSS.get_proxy(USER_INTERFACE)
629
630
        proxy.PasswordPolicies = \
631
            PasswordPolicy.to_structure_dict(policies)
632
633
634
class PackageRules(RuleHandler):
635
    """Simple class holding data from the rules affecting installed packages.
636
637
    """
638
639
    def __init__(self):
640
        """Constructor setting the initial value of attributes."""
641
642
        self._add_pkgs = set()
643
        self._remove_pkgs = set()
644
645
        self._added_pkgs = set()
646
        self._removed_pkgs = set()
647
648
    def add_packages(self, packages):
649
        """
650
        New packages that should be added.
651
652
        :param packages: packages to be added
653
        :type packages: iterable
654
655
        """
656
657
        if packages:
658
            self._add_pkgs.update(packages)
659
660
    def remove_packages(self, packages):
661
        """
662
        New packages that should be removed.
663
664
        :param packages: packages to be removed
665
        :type packages: iterable
666
667
        """
668
669
        if packages:
670
            self._remove_pkgs.update(packages)
671
672
    def __str__(self):
673
        """Standard method useful for debugging and testing."""
674
675
        ret = "packages"
676
        adds = " ".join("--add=%s" % package for package in self._add_pkgs)
677
        if adds:
678
            ret += " " + adds
679
680
        rems = " ".join("--remove=%s" % package
681
                        for package in self._remove_pkgs)
682
        if rems:
683
            ret += " " + rems
684
685
        return ret
686
687
    def _package_is_essential(self, package_name, packages_data):
688
        if package_name not in ESSENTIAL_PACKAGES:
689
            return False
690
691
        if package_name in packages_data.packages:
692
            return True
693
694
        selected_install_env = packages_data.environment
695
        if selected_install_env in ESSENTIAL_PACKAGES[package_name].get("env", []):
696
            return True
697
698
        for g in ESSENTIAL_PACKAGES[package_name].get("groups", []):
699
            if g in packages_data.groups:
700
                return True
701
702
        return False
703
704
    def eval_rules(self, ksdata, storage, report_only=False):
705
        """:see: RuleHandler.eval_rules"""
706
        messages = []
707
        packages_data = get_packages_data()
708
709
        msg_installed_template = _(
710
            "package '%s' has been added to the list of to be installed packages")
711
        # add messages for the already added packages
712
        for pkg in self._added_pkgs:
713
            msg = msg_installed_template % pkg
714
            messages.append(RuleMessage(self.__class__,
715
                                        common.MESSAGE_TYPE_INFO, msg))
716
717
        # packages, that should be added
718
        packages_to_add = (pkg for pkg in self._add_pkgs
0 ignored issues
show
introduced by
The variable pkg does not seem to be defined in case the for loop on line 712 is not entered. Are you sure this can never be the case?
Loading history...
719
                           if pkg not in packages_data.packages)
720
721
        for pkg in packages_to_add:
722
            # add the package unless already added
723
            if not report_only:
724
                self._added_pkgs.add(pkg)
725
                packages_data.packages.append(pkg)
726
727
            msg = msg_installed_template % pkg
728
            messages.append(RuleMessage(self.__class__,
729
                                        common.MESSAGE_TYPE_INFO, msg))
730
731
        msg_excluded_template = _(
732
            "package '%s' has been added to the list of excluded packages")
733
        # now do the same for the packages that should be excluded
734
        # add messages for the already excluded packages
735
        for pkg in self._removed_pkgs:
736
            if self._package_is_essential(pkg, packages_data):
737
                msg = _(
738
                    "package '{package}' has been added to the list "
739
                    "of excluded packages, but it can't be removed "
740
                    "from the current software selection without breaking the installation.")
741
                msg = msg.format(package=pkg)
742
                messages.append(RuleMessage(self.__class__,
743
                                            common.MESSAGE_TYPE_FATAL, msg))
744
            else:
745
                msg = msg_excluded_template % pkg
746
                messages.append(RuleMessage(self.__class__,
747
                                            common.MESSAGE_TYPE_INFO, msg))
748
749
        # packages, that should be added
750
        packages_to_remove = (pkg for pkg in self._remove_pkgs
751
                              if pkg not in packages_data.excluded_packages)
752
753
        for pkg in packages_to_remove:
754
            # exclude the package unless already excluded
755
            if not report_only:
756
                self._removed_pkgs.add(pkg)
757
                packages_data.excluded_packages.append(pkg)
758
759
            msg = msg_excluded_template % pkg
760
            messages.append(RuleMessage(self.__class__,
761
                                        common.MESSAGE_TYPE_INFO, msg))
762
763
        if not report_only:
764
            set_packages_data(packages_data)
765
766
        return messages
767
768
    def revert_changes(self, ksdata, storage):
769
        """:see: RuleHander.revert_changes"""
770
        packages_data = get_packages_data()
771
772
        # remove all packages this handler added
773
        for pkg in self._added_pkgs:
774
            if pkg in packages_data.packages:
775
                packages_data.packages.remove(pkg)
776
777
        # remove all packages this handler excluded
778
        for pkg in self._removed_pkgs:
779
            if pkg in packages_data.excluded_packages:
780
                packages_data.excluded_packages.remove(pkg)
781
782
        self._added_pkgs = set()
783
        self._removed_pkgs = set()
784
785
        set_packages_data(packages_data)
786
787
788
class BootloaderRules(RuleHandler):
789
    """Simple class holding data from the rules affecting bootloader."""
790
791
    def __init__(self):
792
        """Constructor setting the initial value of attributes."""
793
794
        self._require_password = False
795
796
    def require_password(self):
797
        """Requests the bootloader password should be required."""
798
799
        self._require_password = True
800
801
    def __str__(self):
802
        """Standard method useful for debugging and testing."""
803
804
        ret = "bootloader"
805
806
        if self._require_password:
807
            ret += " --passwd"
808
809
        return ret
810
811
    def eval_rules(self, ksdata, storage, report_only=False):
812
        """:see: RuleHandler.eval_rules"""
813
814
        bootloader_proxy = STORAGE.get_proxy(BOOTLOADER)
815
816
        if self._require_password and not bootloader_proxy.IsPasswordSet:
817
            # TODO: Anaconda provides a way to set bootloader password:
818
            # bootloader_proxy.SetEncryptedPassword(...)
819
            # We don't support setting the bootloader password yet,
820
            # but we shouldn't stop the installation, just because of that.
821
            return [RuleMessage(self.__class__, common.MESSAGE_TYPE_WARNING,
822
                                "boot loader password not set up")]
823
        else:
824
            return []
825
826
    # nothing to be reverted for now
827
828
829
class KdumpRules(RuleHandler):
830
    """Simple class holding data from the rules affecting the kdump addon."""
831
832
    def __init__(self):
833
        """Constructor setting the initial value of attributes."""
834
835
        self._kdump_enabled = None
836
        self._kdump_default_enabled = None
837
838
    def kdump_enabled(self, kdenabled):
839
        """Enable or Disable Kdump"""
840
841
        if kdenabled is not None:
842
            self._kdump_enabled = kdenabled
843
844
    def __str__(self):
845
        """Standard method useful for debugging and testing."""
846
847
        ret = "kdump"
848
849
        if self._kdump_enabled is True:
850
            ret += " --enable"
851
852
        if self._kdump_enabled is False:
853
            ret += " --disable"
854
855
        return ret
856
857
    def eval_rules(self, ksdata, storage, report_only=False):
858
        """:see: RuleHandler.eval_rules"""
859
860
        messages = []
861
862
        if self._kdump_enabled is None:
863
            return []
864
        elif self._kdump_enabled is False:
865
            msg = _("Kdump will be disabled on startup")
866
        elif self._kdump_enabled is True:
867
            msg = _("Kdump will be enabled on startup")
868
869
        messages.append(RuleMessage(self.__class__,
870
                                    common.MESSAGE_TYPE_INFO, msg))
0 ignored issues
show
introduced by
The variable msg does not seem to be defined for all execution paths.
Loading history...
871
872
        if not report_only:
873
            if is_module_available(KDUMP):
874
                kdump_proxy = KDUMP.get_proxy()
875
876
                if self._kdump_default_enabled is None:
877
                    # Kdump addon default startup setting
878
                    self._kdump_default_enabled = kdump_proxy.KdumpEnabled
879
880
                kdump_proxy.KdumpEnabled = self._kdump_enabled
881
            else:
882
                log.warning("OSCAP Addon: com_redhat_kdump is not installed. "
883
                            "Skipping kdump configuration")
884
885
        return messages
886
887
    def revert_changes(self, ksdata, storage):
888
        """:see: RuleHander.revert_changes"""
889
890
        if is_module_available(KDUMP):
891
            kdump_proxy = KDUMP.get_proxy()
892
893
            if self._kdump_enabled is not None:
894
                kdump_proxy.KdumpEnabled = self._kdump_default_enabled
895
        else:
896
            log.warning("OSCAP Addon: com_redhat_kdump is not installed. "
897
                        "Skipping reverting kdump configuration")
898
899
        self._kdump_enabled = None
900
        self._kdump_default_enabled = None
901
902
903
class FirewallRules(RuleHandler):
904
    """Simple class holding data from the rules affecting firewall configurations."""
905
906
    def __init__(self):
907
        """Constructor setting the initial value of attributes."""
908
909
        self._add_svcs = set()
910
        self._remove_svcs = set()
911
        self._add_trusts = set()
912
        self._add_ports = set()
913
914
        self._added_svcs = set()
915
        self._added_ports = set()
916
        self._added_trusts = set()
917
        self._removed_svcs = set()
918
919
        self._new_services_to_add = set()
920
        self._new_ports_to_add = set()
921
        self._new_trusts_to_add = set()
922
        self._new_services_to_remove = set()
923
924
        self._firewall_enabled = None
925
        self._firewall_default_state = None
926
927
    def add_services(self, services):
928
        """
929
        Services that should be allowed through firewall.
930
931
        :param services: services to be added
932
        :type services: iterable
933
934
        """
935
936
        if services:
937
            self._add_svcs.update(services)
938
939
    def add_ports(self, ports):
940
        """
941
        Ports that should be allowed through firewall.
942
943
        :param ports: ports to be added
944
        :type ports: iterable
945
946
        """
947
948
        if ports:
949
            self._add_ports.update(ports)
950
951
    def add_trusts(self, trusts):
952
        """
953
        trusts that should be allowed through firewall.
954
955
        :param trusts: trusts to be added
956
        :type trusts: iterable
957
958
        """
959
960
        if trusts:
961
            self._add_trusts.update(trusts)
962
963
    def remove_services(self, services):
964
        """
965
        New services that should not be allowed through firewall.
966
967
        :param services: services to be removed
968
        :type services: iterable
969
970
        """
971
972
        if services:
973
            self._remove_svcs.update(services)
974
975
    def firewall_enabled(self, fwenabled):
976
        """Enable or disable firewall"""
977
978
        if fwenabled is not None:
979
            self._firewall_enabled = fwenabled
980
981
    def __str__(self):
982
        """Standard method useful for debugging and testing."""
983
984
        ret = "firewall"
985
986
        if self._firewall_enabled is True:
987
            ret += " --enable"
988
989
        if self._firewall_enabled is False:
990
            ret += " --disable"
991
992
        adds = " ".join("--service=%s" % service
993
                        for service in self._add_svcs)
994
        if adds:
995
            ret += " " + adds
996
997
        rems = " ".join("--remove-service=%s" % service
998
                        for service in self._remove_svcs)
999
        if rems:
1000
            ret += " " + rems
1001
1002
        ports = " ".join("--port=%s" % port
1003
                         for port in self._add_ports)
1004
        if ports:
1005
            ret += " " + ports
1006
1007
        trusts = " ".join("--trust=%s" % trust
1008
                          for trust in self._add_trusts)
1009
        if trusts:
1010
            ret += " " + trusts
1011
1012
        return ret
1013
1014
    def eval_rules(self, ksdata, storage, report_only=False):
1015
        """:see: RuleHandler.eval_rules"""
1016
1017
        firewall_proxy = NETWORK.get_proxy(FIREWALL)
1018
        messages = []
1019
1020
        if self._firewall_default_state is None:
1021
            # firewall default startup setting
1022
            self._firewall_default_state = firewall_proxy.FirewallMode
1023
1024
        if self._firewall_enabled is False:
1025
            msg = _("Firewall will be disabled on startup")
1026
            messages.append(RuleMessage(self.__class__,
1027
                                        common.MESSAGE_TYPE_INFO, msg))
1028
            if not report_only:
1029
                firewall_proxy.FirewallMode = FIREWALL_DISABLED
1030
1031
        elif self._firewall_enabled is True:
1032
            msg = _("Firewall will be enabled on startup")
1033
            messages.append(RuleMessage(self.__class__,
1034
                                        common.MESSAGE_TYPE_INFO, msg))
1035
            if not report_only:
1036
                firewall_proxy.FirewallMode = FIREWALL_ENABLED
1037
1038
        # add messages for the already added services
1039
        for svc in self._added_svcs:
1040
            msg = _("service '%s' has been added to the list of services to be "
1041
                    "added to the firewall" % svc)
1042
            messages.append(RuleMessage(self.__class__,
1043
                                        common.MESSAGE_TYPE_INFO, msg))
1044
1045
        # add messages for the already added ports
1046
        for port in self._added_ports:
1047
            msg = _("port '%s' has been added to the list of ports to be "
1048
                    "added to the firewall" % port)
1049
            messages.append(RuleMessage(self.__class__,
1050
                                        common.MESSAGE_TYPE_INFO, msg))
1051
1052
        # add messages for the already added trusts
1053
        for trust in self._added_trusts:
1054
            msg = _("trust '%s' has been added to the list of trusts to be "
1055
                    "added to the firewall" % trust)
1056
            messages.append(RuleMessage(self.__class__,
1057
                                        common.MESSAGE_TYPE_INFO, msg))
1058
1059
        # services, that should be added
1060
        self._new_services_to_add = {
1061
            svc for svc in self._add_svcs
1062
            if svc not in firewall_proxy.EnabledServices}
1063
1064
        # ports, that should be added
1065
        self._new_ports_to_add = {
1066
            ports for ports in self._add_ports
1067
            if ports not in firewall_proxy.EnabledPorts}
1068
1069
        # trusts, that should be added
1070
        self._new_trusts_to_add = {
1071
            trust for trust in self._add_trusts
1072
            if trust not in firewall_proxy.Trusts}
1073
1074
        for svc in self._new_services_to_add:
1075
            # add the service unless already added
1076
            if not report_only:
1077
                self._added_svcs.add(svc)
1078
1079
            msg = _("service '%s' has been added to the list of services to be "
1080
                    "added to the firewall" % svc)
1081
            messages.append(RuleMessage(self.__class__,
1082
                                        common.MESSAGE_TYPE_INFO, msg))
1083
        if not report_only:
1084
            all_services = list(self._add_svcs.union(set(firewall_proxy.EnabledServices)))
1085
            firewall_proxy.EnabledServices = all_services
1086
1087
        for port in self._new_ports_to_add:
1088
            # add the port unless already added
1089
            if not report_only:
1090
                self._added_ports.add(port)
1091
1092
            msg = _("port '%s' has been added to the list of ports to be "
1093
                    "added to the firewall" % port)
1094
            messages.append(RuleMessage(self.__class__,
1095
                                        common.MESSAGE_TYPE_INFO, msg))
1096
        if not report_only:
1097
            all_ports = list(self._add_ports.union(set(firewall_proxy.EnabledPorts)))
1098
            firewall_proxy.EnabledPorts = all_ports
1099
1100
        for trust in self._new_trusts_to_add:
1101
            # add the trust unless already added
1102
            if not report_only:
1103
                self._added_trusts.add(trust)
1104
1105
            msg = _("trust '%s' has been added to the list of trusts to be "
1106
                    "added to the firewall" % trust)
1107
            messages.append(RuleMessage(self.__class__,
1108
                                        common.MESSAGE_TYPE_INFO, msg))
1109
        if not report_only:
1110
            all_trusts = list(self._add_trusts.union(set(firewall_proxy.Trusts)))
1111
            firewall_proxy.Trusts = all_trusts
1112
1113
        # now do the same for the services that should be excluded
1114
1115
        # add messages for the already excluded services
1116
        for svc in self._removed_svcs:
1117
            msg = _("service '%s' has been added to the list of services to be "
1118
                    "removed from the firewall" % svc)
1119
            messages.append(RuleMessage(self.__class__,
1120
                                        common.MESSAGE_TYPE_INFO, msg))
1121
1122
        # services, that should be excluded
1123
        self._new_services_to_remove = {
1124
            svc for svc in self._remove_svcs
1125
            if svc not in firewall_proxy.DisabledServices}
1126
1127
        for svc in self._new_services_to_remove:
1128
            # exclude the service unless already excluded
1129
            if not report_only:
1130
                self._removed_svcs.add(svc)
1131
1132
            msg = _("service '%s' has been added to the list of services to be "
1133
                    "removed from the firewall" % svc)
1134
            messages.append(RuleMessage(self.__class__,
1135
                                        common.MESSAGE_TYPE_INFO, msg))
1136
        if not report_only:
1137
            all_services = list(self._remove_svcs.union(set(firewall_proxy.DisabledServices)))
1138
            firewall_proxy.DisabledServices = all_services
1139
1140
        return messages
1141
1142
    def revert_changes(self, ksdata, storage):
1143
        """:see: RuleHander.revert_changes"""
1144
        firewall_proxy = NETWORK.get_proxy(FIREWALL)
1145
1146
        if self._firewall_enabled is not None:
1147
            firewall_proxy.FirewallMode = self._firewall_default_state
1148
1149
        # remove all services this handler added
1150
        all_services = firewall_proxy.EnabledServices
1151
        orig_services = set(all_services).difference(self._new_services_to_add)
1152
        firewall_proxy.EnabledServices = list(orig_services)
1153
1154
        # remove all ports this handler added
1155
        all_ports = firewall_proxy.EnabledPorts
1156
        orig_ports = set(all_ports).difference(self._new_ports_to_add)
1157
        firewall_proxy.EnabledPorts = list(orig_ports)
1158
1159
        # remove all trusts this handler added
1160
        all_trusts = firewall_proxy.Trusts
1161
        orig_trusts = set(all_trusts).difference(self._new_trusts_to_add)
1162
        firewall_proxy.Trusts = list(orig_trusts)
1163
1164
        # remove all services this handler excluded
1165
        all_services = firewall_proxy.DisabledServices
1166
        orig_services = set(all_services).difference(self._new_services_to_remove)
1167
        firewall_proxy.DisabledServices = list(orig_services)
1168
1169
        self._added_svcs = set()
1170
        self._added_ports = set()
1171
        self._added_trusts = set()
1172
        self._removed_svcs = set()
1173
        self._firewall_enabled = None
1174
        self._firewall_default_state = None
1175