From 7392eba8279e96b064a14b99ec5805e0fa88d71d Mon Sep 17 00:00:00 2001 From: Vit Mojzis Date: Fri, 31 May 2019 19:19:35 +0200 Subject: [PATCH] Add tests Each actor has it's own component test. The tests introduce SELinux customizations that should not affect the system. All changes are removed after testing. Actor SELinuxPrepare, which is executed as part of its component test, will remove any SELinux customizations introduced via semanage! Unit tests added for some library functions. --- .../selinux/selinuxapplycustom/actor.py | 9 +- .../tests/component_test.py | 91 +++++++++++++++ .../selinux/selinuxcontentscanner/actor.py | 14 +-- .../libraries/library.py | 40 +++++-- .../tests/component_test.py | 105 ++++++++++++++++++ .../tests/mock_modules/mock1.cil | 4 + .../tests/mock_modules/mock2.cil | 4 + .../tests/mock_modules/mock3.cil | 3 + .../selinuxcontentscanner/tests/unit_test.py | 67 +++++++++++ .../actors/selinux/selinuxprepare/actor.py | 32 +----- .../selinuxprepare/libraries/library.py | 34 ++++++ .../selinuxprepare/tests/component_test.py | 89 +++++++++++++++ .../tests/mock_modules/mock1.cil | 4 + .../tests/mock_modules/mock2.cil | 4 + .../tests/mock_modules/mock3.cil | 3 + .../selinux/selinuxprepare/tests/unit_test.py | 47 ++++++++ .../system_upgrade/el7toel8/models/selinux.py | 2 +- 17 files changed, 498 insertions(+), 54 deletions(-) create mode 100644 repos/system_upgrade/el7toel8/actors/selinux/selinuxapplycustom/tests/component_test.py create mode 100644 repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/component_test.py create mode 100644 repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/mock_modules/mock1.cil create mode 100644 repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/mock_modules/mock2.cil create mode 100644 repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/mock_modules/mock3.cil create mode 100644 repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/unit_test.py create mode 100644 repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/libraries/library.py create mode 100644 repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/component_test.py create mode 100644 repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/mock_modules/mock1.cil create mode 100644 repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/mock_modules/mock2.cil create mode 100644 repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/mock_modules/mock3.cil create mode 100644 repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/unit_test.py diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxapplycustom/actor.py b/repos/system_upgrade/el7toel8/actors/selinux/selinuxapplycustom/actor.py index 7f3e921b28..0d92d8ea1e 100644 --- a/repos/system_upgrade/el7toel8/actors/selinux/selinuxapplycustom/actor.py +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxapplycustom/actor.py @@ -41,7 +41,8 @@ def process(self): cil_filename = os.path.join(WORKING_DIRECTORY, "%s.cil" % module.name) self.log.info("Installing module %s on priority %d.", module.name, module.priority) if module.removed: - self.log.info("The following lines where removed because of incompatibility: \n%s", '\n'.join(module.removed)) + self.log.info("The following lines where removed because of incompatibility: \n%s", + '\n'.join(module.removed)) # write module content to disk try: with open(cil_filename, 'w') as cil_file: @@ -62,14 +63,14 @@ def process(self): self.log.info("Error installing module: %s", str(e)) # TODO - save the failed module to /etc/selinux ? # currently it is still left in the old policy store - pass try: os.remove(cil_filename) except OSError as e: self.log.info("Error removing module file: %s", str(e)) # import SELinux customizations collected by "semanage export" for custom in self.consume(SELinuxCustom): - self.log.info('Importing the following SELinux customizations collected by "semanage export": \n%s', '\n'.join(custom.commands)) + self.log.info('Importing the following SELinux customizations collected by "semanage export": \n%s', + '\n'.join(custom.commands)) semanage_filename = os.path.join(WORKING_DIRECTORY, "semanage") # save SELinux customizations to disk try: @@ -103,5 +104,3 @@ def process(self): # TODO - summarize all changes after LEAPP team rewrites reporting # from leapp.reporting import Report - - diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxapplycustom/tests/component_test.py b/repos/system_upgrade/el7toel8/actors/selinux/selinuxapplycustom/tests/component_test.py new file mode 100644 index 0000000000..635de798b5 --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxapplycustom/tests/component_test.py @@ -0,0 +1,91 @@ +import os + +from leapp.snactor.fixture import current_actor_context +from leapp.models import SELinuxModule, SELinuxModules, SELinuxCustom, SELinuxFacts, SELinuxRequestRPMs +from leapp.libraries.stdlib import api, run, CalledProcessError +from leapp.reporting import Report + +test_modules = [ + ["400", "mock1"], + ["99", "mock1"], + ["200", "mock1"], + ["400", "mock2"], + ["999", "mock3"], + ["400", "permissive_abrt_t"] +] + +semanage_commands = [ +['fcontext', '-t', 'ganesha_var_run_t', "'/ganesha(/.*)?'"], +['fcontext', '-t', 'httpd_sys_content_t', "'/web(/.*)?'"], +['port', '-t', 'http_port_t', '-p', 'udp', '81'] +] + +def findModuleSemodule(semodule_lfull, name, priority): + for line in semodule_lfull: + if name in line and priority in line: + return line + return None + +def findSemanageRule(rules, rule): + for r in rules: + for word in rule: + if word not in r: + break + else: + return r + return None + +def test_SELinuxApplyCustom(current_actor_context): + + semodule_list = [SELinuxModule(name=module, priority=int(prio), + content="(allow domain proc_type (file (getattr open read)))", removed=[]) + for (prio, module) in test_modules] + + commands = [" ".join([c[0], "-a"] + c[1:]) for c in semanage_commands[1:]] + semanage_removed = [" ".join([semanage_commands[0][0], "-a"] + semanage_commands[0][1:])] + + current_actor_context.feed(SELinuxModules(modules=semodule_list)) + current_actor_context.feed(SELinuxCustom(commands=commands, removed=semanage_removed)) + current_actor_context.run() + + # check if all given modules and local customizations where removed + semodule_lfull = [] + semanage_export = [] + try: + semodule = run(["semodule", "-lfull"], split=True) + semodule_lfull = semodule.get("stdout", "") + semanage = run(["semanage", "export"], split=True) + semanage_export = semanage.get("stdout", "") + except CalledProcessError as e: + api.current_logger().warning("Error listing selinux customizations: %s", str(e.stderr)) + assert False + + # check that all modules installed during test setup where reported + for priority, name in test_modules: + if priority != "100" and priority != "200": + assert findModuleSemodule(semodule_lfull, name, priority) + # check that all valid commands where reintroduced to the system + for command in semanage_commands[1:-1]: + assert findSemanageRule(semanage_export, command) + +def teardown(): + for priority, module in test_modules: + try: + run(["semodule", "-X", priority, "-r", module]) + except CalledProcessError as e: + # expected if the test fails + api.current_logger().warning("Error removing selinux modules after testing: %s", str(e.stderr)) + + for command in semanage_commands[1:]: + try: + run(["semanage", command[0], "-d"] + [x.strip('"\'') for x in command[1:]]) + except CalledProcessError as e: + # expected if the test fails + api.current_logger().warning("Error removing selinux customizations after testing: %s", str(e.stderr)) + continue + + try: + run(["semanage", semanage_commands[0][0], "-d"] + semanage_commands[0][1:]) + except CalledProcessError: + # expected + pass diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/actor.py b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/actor.py index 717ae917e4..0864c734fc 100644 --- a/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/actor.py +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/actor.py @@ -1,12 +1,10 @@ from leapp.actors import Actor -from leapp.models import SELinuxModules, SELinuxModule, SELinuxCustom, SELinuxFacts, SELinuxRequestRPMs, RpmTransactionTasks +from leapp.models import SELinuxModules, SELinuxCustom, SELinuxFacts, SELinuxRequestRPMs, RpmTransactionTasks from leapp.tags import FactsPhaseTag, IPUWorkflowTag from leapp.libraries.stdlib import run, CalledProcessError from leapp.libraries.actor import library -WORKING_DIRECTORY = '/tmp/selinux/' - class SELinuxContentScanner(Actor): ''' Scan the system for any SELinux customizations @@ -32,16 +30,16 @@ def process(self): self.produce(SELinuxModules(modules=semodule_list)) self.produce( RpmTransactionTasks( - to_install = rpms_to_install, + to_install=rpms_to_install, # possibly not necessary - dnf should not remove RPMs (that exist in both RHEL 7 and 8) durign update - to_keep = rpms_to_keep + to_keep=rpms_to_keep ) ) # this is produced so that we can later verify that the RPMs are present after upgrade self.produce( SELinuxRequestRPMs( - to_install = rpms_to_install, - to_keep = rpms_to_keep + to_install=rpms_to_install, + to_keep=rpms_to_keep ) ) @@ -52,4 +50,4 @@ def process(self): commands=semanage_valid, removed=semanage_removed ) - ) \ No newline at end of file + ) diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/libraries/library.py b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/libraries/library.py index a1e6ca8543..766e8fe32e 100644 --- a/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/libraries/library.py +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/libraries/library.py @@ -2,13 +2,31 @@ import re from shutil import rmtree from leapp.libraries.stdlib import api, run, CalledProcessError +from leapp.models import SELinuxModule # types and attributes that where removed between RHEL 7 and 8 -REMOVED_TYPES_=["base_typeattr_15","direct_run_init","gpgdomain","httpd_exec_scripts","httpd_user_script_exec_type","ibendport_type","ibpkey_type","pcmcia_typeattr_2","pcmcia_typeattr_3","pcmcia_typeattr_4","pcmcia_typeattr_5","pcmcia_typeattr_6","pcmcia_typeattr_7","sandbox_caps_domain","sandbox_typeattr_2","sandbox_typeattr_3","sandbox_typeattr_4","server_ptynode","systemctl_domain","user_home_content_type","userhelper_type","cgdcbxd_exec_t","cgdcbxd_t","cgdcbxd_unit_file_t","cgdcbxd_var_run_t","ganesha_use_fusefs","ganesha_exec_t","ganesha_t","ganesha_tmp_t","ganesha_unit_file_t","ganesha_var_log_t","ganesha_var_run_t","ganesha_use_fusefs"] +REMOVED_TYPES_ = ["base_typeattr_15", "direct_run_init", "gpgdomain", "httpd_exec_scripts", + "httpd_user_script_exec_type", "ibendport_type", "ibpkey_type", "pcmcia_typeattr_2", + "pcmcia_typeattr_3", "pcmcia_typeattr_4", "pcmcia_typeattr_5", "pcmcia_typeattr_6", + "pcmcia_typeattr_7", "sandbox_caps_domain", "sandbox_typeattr_2", "sandbox_typeattr_3", + "sandbox_typeattr_4", "server_ptynode", "systemctl_domain", "user_home_content_type", + "userhelper_type", "cgdcbxd_exec_t", "cgdcbxd_t", "cgdcbxd_unit_file_t", "cgdcbxd_var_run_t", + "ganesha_use_fusefs", "ganesha_exec_t", "ganesha_t", "ganesha_tmp_t", "ganesha_unit_file_t", + "ganesha_var_log_t", "ganesha_var_run_t", "ganesha_use_fusefs"] # types, attributes and boolean contained in container-selinux -CONTAINER_TYPES=["container_connect_any","container_runtime_t","container_runtime_exec_t","spc_t","container_auth_t","container_auth_exec_t","spc_var_run_t","container_var_lib_t","container_home_t","container_config_t","container_lock_t","container_log_t","container_runtime_tmp_t","container_runtime_tmpfs_t","container_var_run_t","container_plugin_var_run_t","container_unit_file_t","container_devpts_t","container_share_t","container_port_t","container_build_t","container_logreader_t","docker_log_t","docker_tmpfs_t","docker_share_t","docker_t","docker_lock_t","docker_home_t","docker_exec_t","docker_unit_file_t","docker_devpts_t","docker_config_t","docker_tmp_t","docker_auth_exec_t","docker_plugin_var_run_t","docker_port_t","docker_auth_t","docker_var_run_t","docker_var_lib_t","container_domain","container_net_domain"] - +CONTAINER_TYPES = ["container_connect_any", "container_runtime_t", "container_runtime_exec_t", "spc_t", + "container_auth_t", "container_auth_exec_t", "spc_var_run_t", "container_var_lib_t", + "container_home_t", "container_config_t", "container_lock_t", "container_log_t", + "container_runtime_tmp_t", "container_runtime_tmpfs_t", "container_var_run_t", + "container_plugin_var_run_t", "container_unit_file_t", "container_devpts_t", "container_share_t", + "container_port_t", "container_build_t", "container_logreader_t", "docker_log_t", "docker_tmpfs_t", + "docker_share_t", "docker_t", "docker_lock_t", "docker_home_t", "docker_exec_t", + "docker_unit_file_t", "docker_devpts_t", "docker_config_t", "docker_tmp_t", "docker_auth_exec_t", + "docker_plugin_var_run_t", "docker_port_t", "docker_auth_t", "docker_var_run_t", + "docker_var_lib_t", "container_domain", "container_net_domain"] + +WORKING_DIRECTORY = '/tmp/selinux/' def checkModule(name): ''' @@ -20,7 +38,7 @@ def checkModule(name): ''' try: removed = run(['grep', '-w', '-E', "|".join(REMOVED_TYPES_), name], split=True) - run(['sed', '-i', '/%s/s/^/;/g' % '\|'.join(REMOVED_TYPES_), name]) + run(['sed', '-i', '/%s/s/^/;/g' % r'\|'.join(REMOVED_TYPES_), name]) return removed.get("stdout", []) except CalledProcessError: return [] @@ -57,7 +75,7 @@ def getSELinuxModules(): Returns 3-tuple (modules, retain_rpms, install_rpms) where "modules" is a list of "SELinuxModule" objects, - "retain_rpms" is a list of RPMs that should be retained + "retain_rpms" is a list of RPMs that should be retained during the upgrade and "install_rpms" is a list of RPMs that should be installed during the upgrade @@ -81,7 +99,7 @@ def getSELinuxModules(): os.chdir(WORKING_DIRECTORY) except OSError: api.current_logger().info("Failed to access working directory! Aborting.") - return ([],[],[]) + return ([], [], []) for (name, priority) in modules: if priority == "200": @@ -103,7 +121,7 @@ def getSELinuxModules(): with open(name + ".cil", 'r') as cil_file: module_content = cil_file.read() except OSError as e: - api.current_logger().info("Error reading %s.cil : %s", name, str(e)) + api.current_logger().info("Error reading %s.cil : %s", name, e.get("stderr", "")) continue semodule_list.append(SELinuxModule( @@ -119,7 +137,7 @@ def getSELinuxModules(): # rename the cil module file so that it does not clash # with the same module on different priority try: - os.rename(name + ".cil", "%s_%s" % (name, priority)) + os.rename(name + ".cil", "%s_%s" % (name, priority)) except OSError: # TODO leapp.libraries.stdlib.api.current_logger() # and move the method to a library @@ -132,7 +150,7 @@ def getSELinuxModules(): # Check if modules contain any type, attribute, or boolean contained in container-selinux and install it if so # This is necessary since container policy module is part of selinux-policy-targeted in RHEL 7 (but not in RHEL 8) try: - semodule = run(['grep', '-w', '-r', '-E', "|".join(CONTAINER_TYPES)], split=False) + run(['grep', '-w', '-r', '-E', "|".join(CONTAINER_TYPES)], split=False) # Request "container-selinux" to be installed since container types where used in local customizations # and container-selinux policy was removed from selinux-policy-* packages install_rpms.append("container-selinux") @@ -175,5 +193,5 @@ def getSELinuxCustomizations(): except CalledProcessError as e: api.current_logger().info("Failed to export SELinux customizations: %s", str(e)) - - return (semanage_valid, semanage_removed) \ No newline at end of file + + return (semanage_valid, semanage_removed) diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/component_test.py b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/component_test.py new file mode 100644 index 0000000000..4b521832f0 --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/component_test.py @@ -0,0 +1,105 @@ +import os + +from leapp.snactor.fixture import current_actor_context +from leapp.models import SELinuxModule, SELinuxModules, SELinuxCustom, SELinuxFacts, SELinuxRequestRPMs +from leapp.libraries.stdlib import api, run, CalledProcessError +from leapp.reporting import Report + +test_modules = [ + ["400", "mock1"], + ["99", "mock1"], + ["200", "mock1"], + ["400", "mock2"], + ["999", "mock3"] +] + +semanage_commands = [ +['fcontext', '-t', 'httpd_sys_content_t', '"/web(/.*)?"'], +['fcontext', '-t', 'ganesha_var_run_t', '"/ganesha(/.*)?"'], +['port', '-t', 'http_port_t', '-p', 'udp', '81'], +['permissive', 'abrt_t'] +] + +testmoduledir = os.path.join(os.getcwd(), "tests/mock_modules/") + +def setup(): + for priority, module in test_modules: + try: + semodule = run(["semodule", "-X", priority, "-i", os.path.join(testmoduledir, module + ".cil")]) + except CalledProcessError as e: + api.current_logger().warning("Error installing mock module: %s", e.stderr) + api.current_logger().warning("Error installing mock module: %s, %s", str(e.stderr), + semodule.get("stderr", "fuck")) + continue + + for command in semanage_commands: + try: + run(["semanage", command[0], "-a"] + command[1:]) + except CalledProcessError as e: + api.current_logger().warning("Error applying selinux customizations %s", str(e.stderr)) + continue + +def findModule(selinuxmodules, name, priority): + for module in selinuxmodules.modules: + if module.name == name and module.priority == int(priority): + return module + return None + +def findSemanageRule(rules, rule): + for r in rules: + for word in rule: + if word not in r: + break + else: + return r + return None + +def test_SELinuxContentScanner(current_actor_context): + + expected_data = {'policy': 'targeted', + 'mls_enabled': True, + 'enabled': True, + 'runtime_mode': 'enforcing', + 'static_mode': 'enforcing'} + + current_actor_context.feed(SELinuxFacts(**expected_data)) + current_actor_context.run() + + modules = current_actor_context.consume(SELinuxModules)[0] + api.current_logger().warning("Modules: %s", str(modules)) + assert modules + # check that all modules installed during test setup where reported + for priority, name in test_modules: + if priority != "100" and priority != "200": + assert findModule(modules, name, priority) + + rpms = current_actor_context.consume(SELinuxRequestRPMs)[0] + assert rpms + # modules with priority 200 should only originate in "-selinux" rpms + assert "mock1-selinux" in rpms.to_keep + # mock1 contains container related type + assert "container-selinux" in rpms.to_install + + custom = current_actor_context.consume(SELinuxCustom)[0] + assert custom + # the second command contains removed type and should be discarded + assert findSemanageRule(custom.removed, semanage_commands[1]) + # the rest of the commands should be reported (except for the last which will show up in modules) + assert findSemanageRule(custom.commands, semanage_commands[0]) + assert findSemanageRule(custom.commands, semanage_commands[2]) + + +def teardown(): + for command in semanage_commands[:-1]: + try: + run(["semanage", command[0], "-d"] + command[1:]) + except CalledProcessError as e: + api.current_logger().warning("Error removing selinux customizations after testing: %s", str(e.stderr)) + continue + + for priority, module in reversed(test_modules + [["400", "permissive_abrt_t"]]): + try: + run(["semodule", "-X", priority, "-r", module]) + except CalledProcessError as e: + api.current_logger().warning("Error removing selinux modules after testing: %s", str(e.stderr)) + continue diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/mock_modules/mock1.cil b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/mock_modules/mock1.cil new file mode 100644 index 0000000000..4a910ba5fd --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/mock_modules/mock1.cil @@ -0,0 +1,4 @@ +(type mock_type_t) +(typeattributeset domain (mock_type_t)) +(allow mock_type_t proc_type (file (getattr open read))) +(allow mock_type_t container_var_run_t (file (getattr open read))) diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/mock_modules/mock2.cil b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/mock_modules/mock2.cil new file mode 100644 index 0000000000..69211c7463 --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/mock_modules/mock2.cil @@ -0,0 +1,4 @@ +(type mock_type2_t) +(typeattributeset direct_run_init (mock_type2_t)) +(allow mock_type_t file_type (file (getattr open read))) +(allow mock_type_t ganesha_exec_t (file (getattr open read))) diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/mock_modules/mock3.cil b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/mock_modules/mock3.cil new file mode 100644 index 0000000000..ce2560e4a6 --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/mock_modules/mock3.cil @@ -0,0 +1,3 @@ +(type mock_type3_t) +(typeattributeset domain (mock_type3_t)) +(allow mock_type_t file_type (file (getattr open read))) diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/unit_test.py b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/unit_test.py new file mode 100644 index 0000000000..d97a970fba --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxcontentscanner/tests/unit_test.py @@ -0,0 +1,67 @@ +from leapp.libraries.stdlib import run, CalledProcessError +from leapp.libraries.actor import library + +class run_mocked(object): + def __init__(self): + self.args = [] + self.called = 0 + + def __call__(self, args, split=True): + self.called += 1 + self.args = args + + if self.args == ['semodule', '-lfull']: + stdout = ["400 permissive_abrt_t cil", + "400 zebra cil", + "300 zebra cil", + "100 vpn pp ", + "099 zebra cil ", + "100 minissdpd pp"] + + elif self.args == ['semanage', 'export']: + stdout = ["boolean -D", + "login -D", + "interface -D", + "user -D", + "port -D", + "node -D", + "fcontext -D", + "module -D", + "boolean -m -1 cron_can_relabel", + "port -a -t http_port_t -p udp 81", + "fcontext -a -f a -t httpd_sys_content_t '/web(/.*)?'", + "fcontext -a -f a -t ganesha_var_run_t '/ganesha(/.*)?'"] + + return {'stdout': stdout} + +class run_mocked_fail(object): + def __init__(self): + self.called = 0 + + def __call__(self, args, split=True): + raise CalledProcessError(self, 1, "Mock error ;)") + + +def test_listSELinuxModules(monkeypatch): + monkeypatch.setattr(library, "run", run_mocked()) + + assert library.listSELinuxModules() == [("permissive_abrt_t", "400"), + ("zebra", "400"), + ("zebra", "300"), + ("vpn", "100"), + ("zebra", "099"), + ("minissdpd", "100")] + + monkeypatch.setattr(library, "run", run_mocked_fail()) + + assert library.listSELinuxModules() == [] + +def test_getSELinuxCustomizations(monkeypatch): + monkeypatch.setattr(library, "run", run_mocked()) + + (semanage_valid, semanage_removed) = library.getSELinuxCustomizations() + + assert len(semanage_valid) == 11 + assert semanage_valid[0] == "boolean -D" + assert semanage_valid[10] == "fcontext -a -f a -t httpd_sys_content_t '/web(/.*)?'" + assert semanage_removed == ["fcontext -a -f a -t ganesha_var_run_t '/ganesha(/.*)?'"] diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/actor.py b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/actor.py index 7621963c67..1eba8ea76e 100644 --- a/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/actor.py +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/actor.py @@ -2,6 +2,7 @@ from leapp.models import SELinuxModules, SELinuxCustom from leapp.tags import PreparationPhaseTag, IPUWorkflowTag from leapp.libraries.stdlib import run, CalledProcessError +from leapp.libraries.actor import library class SELinuxPrepare(Actor): ''' @@ -19,32 +20,5 @@ class SELinuxPrepare(Actor): tags = (PreparationPhaseTag, IPUWorkflowTag) def process(self): - # remove SELinux customizations done by semanage -- to be reintroduced after the upgrade - self.log.info('Removing SELinux customizations introduced by semanage.') - - semanage_options = ["login","user","port","interface","module","node","fcontext","boolean","ibpkey","ibendport"] - # permissive domains are handled by porting modules (permissive -a adds new cil module with priority 400) - for option in semanage_options: - try: - run(['semanage', option, '-D']) - except CalledProcessError: - continue - - # remove custom SElinux modules - to be reinstalled after the upgrade - for semodules in self.consume(SELinuxModules): - self.log.info("Removing custom SELinux policy modules. Count: %d", len(semodules.modules)) - for module in semodules.modules: - self.log.info("Removing %s on priority %d.", module.name, module.priority) - try: - run([ - 'semodule', - '-X', - str(module.priority), - '-r', - module.name] - ) - except CalledProcessError as e: - self.log.info("Failed to remove module %s on priority %d: %s", module.name, module.priority, str(e)) - continue - - self.log.info("SElinux customizations removed successfully.") + library.removeSemanageCustomizations() + library.removeCustomModules() diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/libraries/library.py b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/libraries/library.py new file mode 100644 index 0000000000..bc3b10b07a --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/libraries/library.py @@ -0,0 +1,34 @@ +from leapp.libraries.stdlib import api, run, CalledProcessError +from leapp.models import SELinuxModules, SELinuxCustom + +def removeSemanageCustomizations(): + # remove SELinux customizations done by semanage -- to be reintroduced after the upgrade + api.current_logger().info('Removing SELinux customizations introduced by semanage.') + + semanage_options = ["login", "user", "port", "interface", "module", "node", + "fcontext", "boolean", "ibpkey", "ibendport"] + # permissive domains are handled by porting modules (permissive -a adds new cil module with priority 400) + for option in semanage_options: + try: + run(['semanage', option, '-D']) + except CalledProcessError: + continue + +def removeCustomModules(): + # remove custom SElinux modules - to be reinstalled after the upgrade + for semodules in api.consume(SELinuxModules): + api.current_logger().info("Removing custom SELinux policy modules. Count: %d", len(semodules.modules)) + for module in semodules.modules: + api.current_logger().info("Removing %s on priority %d.", module.name, module.priority) + try: + run([ + 'semodule', + '-X', + str(module.priority), + '-r', + module.name] + ) + except CalledProcessError as e: + api.current_logger().info("Failed to remove module %s on priority %d: %s", + module.name, module.priority, str(e)) + continue diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/component_test.py b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/component_test.py new file mode 100644 index 0000000000..e0de16755d --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/component_test.py @@ -0,0 +1,89 @@ +import os + +from leapp.snactor.fixture import current_actor_context +from leapp.models import SELinuxModule, SELinuxModules, SELinuxCustom +from leapp.libraries.stdlib import api, run, CalledProcessError +from leapp.reporting import Report + +test_modules = [ + ["400", "mock1"], + ["99", "mock1"], + ["300", "mock1"], + ["400", "mock2"], + ["999", "mock3"] +] + +semanage_commands = [ +['fcontext', '-t', 'httpd_sys_content_t', '"/web(/.*)?"'], +['fcontext', '-t', 'ganesha_var_run_t', '"/ganesha(/.*)?"'], +['port', '-t', 'http_port_t', '-p', 'udp', '81'], +['permissive', 'abrt_t'] +] + +# save value of semodule -lfull for comparison +semodule_lfull = "" +semanage_export = "" +try: + semodule = run(["semodule", "-lfull"], split=False) + semodule_lfull = semodule.get("stdout", "") + semanage = run(["semanage", "export"], split=False) + semanage_export = semanage.get("stdout", "") +except CalledProcessError as e: + api.current_logger().warning("Error listing SELinux customizations: %s", str(e.stderr)) + +testmoduledir = os.path.join(os.getcwd(), "tests/mock_modules/") + +def setup(): + for priority, module in test_modules: + try: + run(["semodule", "-X", priority, "-i", os.path.join(testmoduledir, module + ".cil")]) + except CalledProcessError as e: + api.current_logger().warning("Error installing mock module: %s", str(e.stderr)) + continue + + for command in semanage_commands: + try: + run(["semanage", command[0], "-a"] + command[1:]) + except CalledProcessError as e: + api.current_logger().warning("Error applying selinux customizations %s", str(e.stderr)) + continue + +def test_SELinuxPrepare(current_actor_context): + try: + semodule = run(["semodule", "-lfull"], split=False) + api.current_logger().info("Before test:" + semodule.get("stdout", "")) + semanage = run(["semanage", "export"], split=False) + api.current_logger().info("Before test:" + semanage.get("stdout", "")) + except CalledProcessError as e: + api.current_logger().warning("Error listing SELinux customizations: %s", str(e.stderr)) + + semodule_list = [SELinuxModule(name=module, priority=int(prio), content="", removed=[]) + for (prio, module) in test_modules + [["400", "permissive_abrt_t"]]] + + current_actor_context.feed(SELinuxModules(modules=semodule_list)) + current_actor_context.run() + + # check if all given modules and local customizations where removed + try: + semodule = run(["semodule", "-lfull"], split=False) + assert semodule_lfull == semodule.get("stdout", "") + semanage = run(["semanage", "export"], split=False) + assert semanage_export == semanage.get("stdout", "") + except CalledProcessError as e: + api.current_logger().warning("Error listing SELinux customizations: %s", str(e.stderr)) + assert False + +def teardown(): + for priority, module in test_modules + [["400", "permissive_abrt_t"]]: + try: + run(["semodule", "-X", priority, "-r", module]) + except CalledProcessError: + #expected -- should be removed by the actor + pass + + for command in semanage_commands: + try: + run(["semanage", command[0], "-d"] + command[1:]) + except CalledProcessError: + #expected -- should be removed by the actor + continue diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/mock_modules/mock1.cil b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/mock_modules/mock1.cil new file mode 100644 index 0000000000..4a910ba5fd --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/mock_modules/mock1.cil @@ -0,0 +1,4 @@ +(type mock_type_t) +(typeattributeset domain (mock_type_t)) +(allow mock_type_t proc_type (file (getattr open read))) +(allow mock_type_t container_var_run_t (file (getattr open read))) diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/mock_modules/mock2.cil b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/mock_modules/mock2.cil new file mode 100644 index 0000000000..69211c7463 --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/mock_modules/mock2.cil @@ -0,0 +1,4 @@ +(type mock_type2_t) +(typeattributeset direct_run_init (mock_type2_t)) +(allow mock_type_t file_type (file (getattr open read))) +(allow mock_type_t ganesha_exec_t (file (getattr open read))) diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/mock_modules/mock3.cil b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/mock_modules/mock3.cil new file mode 100644 index 0000000000..ce2560e4a6 --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/mock_modules/mock3.cil @@ -0,0 +1,3 @@ +(type mock_type3_t) +(typeattributeset domain (mock_type3_t)) +(allow mock_type_t file_type (file (getattr open read))) diff --git a/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/unit_test.py b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/unit_test.py new file mode 100644 index 0000000000..6247cdabe5 --- /dev/null +++ b/repos/system_upgrade/el7toel8/actors/selinux/selinuxprepare/tests/unit_test.py @@ -0,0 +1,47 @@ +from leapp.libraries.stdlib import run, CalledProcessError +from leapp.libraries.actor import library +from leapp.libraries.stdlib import api +from leapp.models import SELinuxModules, SELinuxModule + +class run_mocked(object): + def __init__(self): + self.args = [] + self.called = 0 + self.removed_modules = set() + self.non_semodule_calls = 0 + + def __call__(self, args, split=True): + self.called += 1 + self.args = args + + if self.args[0] == 'semodule': + stdout = ["libsemanage.semanage_direct_remove_key: Removing last dummy module " + + "(no other dummy module exists at another priority)."] + self.removed_modules.add(self.args[-1]) + else: + self.non_semodule_calls += 1 + + return {'stdout': stdout} + +def test_removeCustomModules(monkeypatch): + mock_modules = {"a": 99, + "b": 300, + "c": 400, + "abrt":190} + + def consume_SELinuxModules_mocked(*models): + + semodule_list = [SELinuxModule(name=k, priority=mock_modules[k], content="", removed=[]) + for k in mock_modules] + + yield SELinuxModules(modules=semodule_list) + + + monkeypatch.setattr(api, "consume", consume_SELinuxModules_mocked) + monkeypatch.setattr(library, "run", run_mocked()) + + library.removeCustomModules() + assert library.run.called == len(mock_modules) + assert library.run.non_semodule_calls == 0 + # verify that removeCustomModules tried to remove all given modules + assert (set(mock_modules) - library.run.removed_modules) == set() diff --git a/repos/system_upgrade/el7toel8/models/selinux.py b/repos/system_upgrade/el7toel8/models/selinux.py index da74cc247c..f4964d9b39 100644 --- a/repos/system_upgrade/el7toel8/models/selinux.py +++ b/repos/system_upgrade/el7toel8/models/selinux.py @@ -26,7 +26,7 @@ class SELinuxRequestRPMs(Model): SELinux related RPM packages that need to be present after upgrade Listed packages provide types that where used in policy - customizations (to_install), or the corresponding policy + customizations (to_install), or the corresponding policy was installed on RHEL-7 installation with priority 200 (to_keep). """