Passed
Pull Request — rhel8-branch (#178)
by Matěj
01:59
created

PackageRules.eval_rules()   C

Complexity

Conditions 9

Size

Total Lines 63
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 42
dl 0
loc 63
rs 6.5386
c 0
b 0
f 0
cc 9
nop 4

How to fix   Long Method   

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:

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.SetPasswordPolicies(
631
            PasswordPolicy.to_structure_dict(policies)
632
        )
633
634
635
class PackageRules(RuleHandler):
636
    """Simple class holding data from the rules affecting installed packages.
637
638
    """
639
640
    def __init__(self):
641
        """Constructor setting the initial value of attributes."""
642
643
        self._add_pkgs = set()
644
        self._remove_pkgs = set()
645
646
        self._added_pkgs = set()
647
        self._removed_pkgs = set()
648
649
    def add_packages(self, packages):
650
        """
651
        New packages that should be added.
652
653
        :param packages: packages to be added
654
        :type packages: iterable
655
656
        """
657
658
        if packages:
659
            self._add_pkgs.update(packages)
660
661
    def remove_packages(self, packages):
662
        """
663
        New packages that should be removed.
664
665
        :param packages: packages to be removed
666
        :type packages: iterable
667
668
        """
669
670
        if packages:
671
            self._remove_pkgs.update(packages)
672
673
    def __str__(self):
674
        """Standard method useful for debugging and testing."""
675
676
        ret = "packages"
677
        adds = " ".join("--add=%s" % package for package in self._add_pkgs)
678
        if adds:
679
            ret += " " + adds
680
681
        rems = " ".join("--remove=%s" % package
682
                        for package in self._remove_pkgs)
683
        if rems:
684
            ret += " " + rems
685
686
        return ret
687
688
    def _package_is_essential(self, package_name, packages_data):
689
        if package_name not in ESSENTIAL_PACKAGES:
690
            return False
691
692
        if package_name in packages_data.packages:
693
            return True
694
695
        selected_install_env = packages_data.environment
696
        if selected_install_env in ESSENTIAL_PACKAGES[package_name].get("env", []):
697
            return True
698
699
        for g in ESSENTIAL_PACKAGES[package_name].get("groups", []):
700
            if g in packages_data.groups:
701
                return True
702
703
        return False
704
705
    def eval_rules(self, ksdata, storage, report_only=False):
706
        """:see: RuleHandler.eval_rules"""
707
        messages = []
708
        packages_data = get_packages_data()
709
710
        # add messages for the already added packages
711
        for pkg in self._added_pkgs:
712
            msg = _("package '%s' has been added to the list of to be installed "
713
                    "packages" % 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 711 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 = _("package '%s' has been added to the list of to be installed "
728
                    "packages" % pkg)
729
            messages.append(RuleMessage(self.__class__,
730
                                        common.MESSAGE_TYPE_INFO, msg))
731
732
        # now do the same for the packages that should be excluded
733
        # add messages for the already excluded packages
734
        for pkg in self._removed_pkgs:
735
            if self._package_is_essential(pkg, packages_data):
736
                msg = _(
737
                    "package '{package}' has been added to the list "
738
                    "of excluded packages, but it can't be removed "
739
                    "from the current software selection without breaking the installation."
740
                    .format(package=pkg))
741
                messages.append(RuleMessage(self.__class__,
742
                                            common.MESSAGE_TYPE_FATAL, msg))
743
            else:
744
                msg = _("package '%s' has been added to the list of excluded "
745
                        "packages" % 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 = _("package '%s' has been added to the list of excluded "
760
                    "packages" % pkg)
761
            messages.append(RuleMessage(self.__class__,
762
                                        common.MESSAGE_TYPE_INFO, msg))
763
764
        if not report_only:
765
            set_packages_data(packages_data)
766
767
        return messages
768
769
    def revert_changes(self, ksdata, storage):
770
        """:see: RuleHander.revert_changes"""
771
        packages_data = get_packages_data()
772
773
        # remove all packages this handler added
774
        for pkg in self._added_pkgs:
775
            if pkg in packages_data.packages:
776
                packages_data.packages.remove(pkg)
777
778
        # remove all packages this handler excluded
779
        for pkg in self._removed_pkgs:
780
            if pkg in packages_data.excluded_packages:
781
                packages_data.excluded_packages.remove(pkg)
782
783
        self._added_pkgs = set()
784
        self._removed_pkgs = set()
785
786
        set_packages_data(packages_data)
787
788
789
class BootloaderRules(RuleHandler):
790
    """Simple class holding data from the rules affecting bootloader."""
791
792
    def __init__(self):
793
        """Constructor setting the initial value of attributes."""
794
795
        self._require_password = False
796
797
    def require_password(self):
798
        """Requests the bootloader password should be required."""
799
800
        self._require_password = True
801
802
    def __str__(self):
803
        """Standard method useful for debugging and testing."""
804
805
        ret = "bootloader"
806
807
        if self._require_password:
808
            ret += " --passwd"
809
810
        return ret
811
812
    def eval_rules(self, ksdata, storage, report_only=False):
813
        """:see: RuleHandler.eval_rules"""
814
815
        bootloader_proxy = STORAGE.get_proxy(BOOTLOADER)
816
817
        if self._require_password and not bootloader_proxy.IsPasswordSet:
818
            # TODO: Anaconda provides a way to set bootloader password:
819
            # bootloader_proxy.SetEncryptedPassword(...)
820
            # We don't support setting the bootloader password yet,
821
            # but we shouldn't stop the installation, just because of that.
822
            return [RuleMessage(self.__class__, common.MESSAGE_TYPE_WARNING,
823
                                "boot loader password not set up")]
824
        else:
825
            return []
826
827
    # nothing to be reverted for now
828
829
830
class KdumpRules(RuleHandler):
831
    """Simple class holding data from the rules affecting the kdump addon."""
832
833
    def __init__(self):
834
        """Constructor setting the initial value of attributes."""
835
836
        self._kdump_enabled = None
837
        self._kdump_default_enabled = None
838
839
    def kdump_enabled(self, kdenabled):
840
        """Enable or Disable Kdump"""
841
842
        if kdenabled is not None:
843
            self._kdump_enabled = kdenabled
844
845
    def __str__(self):
846
        """Standard method useful for debugging and testing."""
847
848
        ret = "kdump"
849
850
        if self._kdump_enabled is True:
851
            ret += " --enable"
852
853
        if self._kdump_enabled is False:
854
            ret += " --disable"
855
856
        return ret
857
858
    def eval_rules(self, ksdata, storage, report_only=False):
859
        """:see: RuleHandler.eval_rules"""
860
861
        messages = []
862
863
        if self._kdump_enabled is None:
864
            return []
865
        elif self._kdump_enabled is False:
866
            msg = _("Kdump will be disabled on startup")
867
        elif self._kdump_enabled is True:
868
            msg = _("Kdump will be enabled on startup")
869
870
        messages.append(RuleMessage(self.__class__,
871
                                    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...
872
873
        if not report_only:
874
            if is_module_available(KDUMP):
875
                kdump_proxy = KDUMP.get_proxy()
876
877
                if self._kdump_default_enabled is None:
878
                    # Kdump addon default startup setting
879
                    self._kdump_default_enabled = kdump_proxy.KdumpEnabled
880
881
                kdump_proxy.KdumpEnabled = self._kdump_enabled
882
            else:
883
                log.warning("OSCAP Addon: com_redhat_kdump is not installed. "
884
                            "Skipping kdump configuration")
885
886
        return messages
887
888
    def revert_changes(self, ksdata, storage):
889
        """:see: RuleHander.revert_changes"""
890
891
        if is_module_available(KDUMP):
892
            kdump_proxy = KDUMP.get_proxy()
893
894
            if self._kdump_enabled is not None:
895
                kdump_proxy.KdumpEnabled = self._kdump_default_enabled
896
        else:
897
            log.warning("OSCAP Addon: com_redhat_kdump is not installed. "
898
                        "Skipping reverting kdump configuration")
899
900
        self._kdump_enabled = None
901
        self._kdump_default_enabled = None
902
903
904
class FirewallRules(RuleHandler):
905
    """Simple class holding data from the rules affecting firewall configurations."""
906
907
    def __init__(self):
908
        """Constructor setting the initial value of attributes."""
909
910
        self._add_svcs = set()
911
        self._remove_svcs = set()
912
        self._add_trusts = set()
913
        self._add_ports = set()
914
915
        self._added_svcs = set()
916
        self._added_ports = set()
917
        self._added_trusts = set()
918
        self._removed_svcs = set()
919
920
        self._new_services_to_add = set()
921
        self._new_ports_to_add = set()
922
        self._new_trusts_to_add = set()
923
        self._new_services_to_remove = set()
924
925
        self._firewall_enabled = None
926
        self._firewall_default_state = None
927
928
    def add_services(self, services):
929
        """
930
        Services that should be allowed through firewall.
931
932
        :param services: services to be added
933
        :type services: iterable
934
935
        """
936
937
        if services:
938
            self._add_svcs.update(services)
939
940
    def add_ports(self, ports):
941
        """
942
        Ports that should be allowed through firewall.
943
944
        :param ports: ports to be added
945
        :type ports: iterable
946
947
        """
948
949
        if ports:
950
            self._add_ports.update(ports)
951
952
    def add_trusts(self, trusts):
953
        """
954
        trusts that should be allowed through firewall.
955
956
        :param trusts: trusts to be added
957
        :type trusts: iterable
958
959
        """
960
961
        if trusts:
962
            self._add_trusts.update(trusts)
963
964
    def remove_services(self, services):
965
        """
966
        New services that should not be allowed through firewall.
967
968
        :param services: services to be removed
969
        :type services: iterable
970
971
        """
972
973
        if services:
974
            self._remove_svcs.update(services)
975
976
    def firewall_enabled(self, fwenabled):
977
        """Enable or disable firewall"""
978
979
        if fwenabled is not None:
980
            self._firewall_enabled = fwenabled
981
982
    def __str__(self):
983
        """Standard method useful for debugging and testing."""
984
985
        ret = "firewall"
986
987
        if self._firewall_enabled is True:
988
            ret += " --enable"
989
990
        if self._firewall_enabled is False:
991
            ret += " --disable"
992
993
        adds = " ".join("--service=%s" % service
994
                        for service in self._add_svcs)
995
        if adds:
996
            ret += " " + adds
997
998
        rems = " ".join("--remove-service=%s" % service
999
                        for service in self._remove_svcs)
1000
        if rems:
1001
            ret += " " + rems
1002
1003
        ports = " ".join("--port=%s" % port
1004
                         for port in self._add_ports)
1005
        if ports:
1006
            ret += " " + ports
1007
1008
        trusts = " ".join("--trust=%s" % trust
1009
                          for trust in self._add_trusts)
1010
        if trusts:
1011
            ret += " " + trusts
1012
1013
        return ret
1014
1015
    def eval_rules(self, ksdata, storage, report_only=False):
1016
        """:see: RuleHandler.eval_rules"""
1017
1018
        firewall_proxy = NETWORK.get_proxy(FIREWALL)
1019
        messages = []
1020
1021
        if self._firewall_default_state is None:
1022
            # firewall default startup setting
1023
            self._firewall_default_state = firewall_proxy.FirewallMode
1024
1025
        if self._firewall_enabled is False:
1026
            msg = _("Firewall will be disabled on startup")
1027
            messages.append(RuleMessage(self.__class__,
1028
                                        common.MESSAGE_TYPE_INFO, msg))
1029
            if not report_only:
1030
                firewall_proxy.SetFirewallMode(FIREWALL_DISABLED)
1031
1032
        elif self._firewall_enabled is True:
1033
            msg = _("Firewall will be enabled on startup")
1034
            messages.append(RuleMessage(self.__class__,
1035
                                        common.MESSAGE_TYPE_INFO, msg))
1036
            if not report_only:
1037
                firewall_proxy.SetFirewallMode(FIREWALL_ENABLED)
1038
1039
        # add messages for the already added services
1040
        for svc in self._added_svcs:
1041
            msg = _("service '%s' has been added to the list of services to be "
1042
                    "added to the firewall" % svc)
1043
            messages.append(RuleMessage(self.__class__,
1044
                                        common.MESSAGE_TYPE_INFO, msg))
1045
1046
        # add messages for the already added ports
1047
        for port in self._added_ports:
1048
            msg = _("port '%s' has been added to the list of ports to be "
1049
                    "added to the firewall" % port)
1050
            messages.append(RuleMessage(self.__class__,
1051
                                        common.MESSAGE_TYPE_INFO, msg))
1052
1053
        # add messages for the already added trusts
1054
        for trust in self._added_trusts:
1055
            msg = _("trust '%s' has been added to the list of trusts to be "
1056
                    "added to the firewall" % trust)
1057
            messages.append(RuleMessage(self.__class__,
1058
                                        common.MESSAGE_TYPE_INFO, msg))
1059
1060
        # services, that should be added
1061
        self._new_services_to_add = {
1062
            svc for svc in self._add_svcs
1063
            if svc not in firewall_proxy.EnabledServices}
1064
1065
        # ports, that should be added
1066
        self._new_ports_to_add = {
1067
            ports for ports in self._add_ports
1068
            if ports not in firewall_proxy.EnabledPorts}
1069
1070
        # trusts, that should be added
1071
        self._new_trusts_to_add = {
1072
            trust for trust in self._add_trusts
1073
            if trust not in firewall_proxy.Trusts}
1074
1075
        for svc in self._new_services_to_add:
1076
            # add the service unless already added
1077
            if not report_only:
1078
                self._added_svcs.add(svc)
1079
1080
            msg = _("service '%s' has been added to the list of services to be "
1081
                    "added to the firewall" % svc)
1082
            messages.append(RuleMessage(self.__class__,
1083
                                        common.MESSAGE_TYPE_INFO, msg))
1084
        if not report_only:
1085
            all_services = list(self._add_svcs.union(set(firewall_proxy.EnabledServices)))
1086
            firewall_proxy.SetEnabledServices(all_services)
1087
1088
        for port in self._new_ports_to_add:
1089
            # add the port unless already added
1090
            if not report_only:
1091
                self._added_ports.add(port)
1092
1093
            msg = _("port '%s' has been added to the list of ports to be "
1094
                    "added to the firewall" % port)
1095
            messages.append(RuleMessage(self.__class__,
1096
                                        common.MESSAGE_TYPE_INFO, msg))
1097
        if not report_only:
1098
            all_ports = list(self._add_ports.union(set(firewall_proxy.EnabledPorts)))
1099
            firewall_proxy.SetEnabledPorts(all_ports)
1100
1101
        for trust in self._new_trusts_to_add:
1102
            # add the trust unless already added
1103
            if not report_only:
1104
                self._added_trusts.add(trust)
1105
1106
            msg = _("trust '%s' has been added to the list of trusts to be "
1107
                    "added to the firewall" % trust)
1108
            messages.append(RuleMessage(self.__class__,
1109
                                        common.MESSAGE_TYPE_INFO, msg))
1110
        if not report_only:
1111
            all_trusts = list(self._add_trusts.union(set(firewall_proxy.Trusts)))
1112
            firewall_proxy.SetTrusts(all_trusts)
1113
1114
        # now do the same for the services that should be excluded
1115
1116
        # add messages for the already excluded services
1117
        for svc in self._removed_svcs:
1118
            msg = _("service '%s' has been added to the list of services to be "
1119
                    "removed from the firewall" % svc)
1120
            messages.append(RuleMessage(self.__class__,
1121
                                        common.MESSAGE_TYPE_INFO, msg))
1122
1123
        # services, that should be excluded
1124
        self._new_services_to_remove = {
1125
            svc for svc in self._remove_svcs
1126
            if svc not in firewall_proxy.DisabledServices}
1127
1128
        for svc in self._new_services_to_remove:
1129
            # exclude the service unless already excluded
1130
            if not report_only:
1131
                self._removed_svcs.add(svc)
1132
1133
            msg = _("service '%s' has been added to the list of services to be "
1134
                    "removed from the firewall" % svc)
1135
            messages.append(RuleMessage(self.__class__,
1136
                                        common.MESSAGE_TYPE_INFO, msg))
1137
        if not report_only:
1138
            all_services = list(self._remove_svcs.union(set(firewall_proxy.DisabledServices)))
1139
            firewall_proxy.SetDisabledServices(all_services)
1140
1141
        return messages
1142
1143
    def revert_changes(self, ksdata, storage):
1144
        """:see: RuleHander.revert_changes"""
1145
        firewall_proxy = NETWORK.get_proxy(FIREWALL)
1146
1147
        if self._firewall_enabled is not None:
1148
            firewall_proxy.SetFirewallMode(self._firewall_default_state)
1149
1150
        # remove all services this handler added
1151
        all_services = firewall_proxy.EnabledServices
1152
        orig_services = set(all_services).difference(self._new_services_to_add)
1153
        firewall_proxy.SetEnabledServices(list(orig_services))
1154
1155
        # remove all ports this handler added
1156
        all_ports = firewall_proxy.EnabledPorts
1157
        orig_ports = set(all_ports).difference(self._new_ports_to_add)
1158
        firewall_proxy.SetEnabledPorts(list(orig_ports))
1159
1160
        # remove all trusts this handler added
1161
        all_trusts = firewall_proxy.Trusts
1162
        orig_trusts = set(all_trusts).difference(self._new_trusts_to_add)
1163
        firewall_proxy.SetTrusts(list(orig_trusts))
1164
1165
        # remove all services this handler excluded
1166
        all_services = firewall_proxy.DisabledServices
1167
        orig_services = set(all_services).difference(self._new_services_to_remove)
1168
        firewall_proxy.SetDisabledServices(list(orig_services))
1169
1170
        self._added_svcs = set()
1171
        self._added_ports = set()
1172
        self._added_trusts = set()
1173
        self._removed_svcs = set()
1174
        self._firewall_enabled = None
1175
        self._firewall_default_state = None
1176