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). """