Skip to content

Commit

Permalink
Selinux: Add tests
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
vmojzis committed Jul 4, 2019
1 parent 624650f commit ff8a9c7
Show file tree
Hide file tree
Showing 17 changed files with 498 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -103,5 +104,3 @@ def process(self):

# TODO - summarize all changes after LEAPP team rewrites reporting
# from leapp.reporting import Report


Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
)
)

Expand All @@ -52,4 +50,4 @@ def process(self):
commands=semanage_valid,
removed=semanage_removed
)
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -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):
'''
Expand All @@ -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 []
Expand Down Expand Up @@ -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
Expand All @@ -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":
Expand All @@ -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(
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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)

return (semanage_valid, semanage_removed)
Original file line number Diff line number Diff line change
@@ -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 "<module_name>-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
Original file line number Diff line number Diff line change
@@ -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)))
Original file line number Diff line number Diff line change
@@ -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)))
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(type mock_type3_t)
(typeattributeset domain (mock_type3_t))
(allow mock_type_t file_type (file (getattr open read)))
Loading

0 comments on commit ff8a9c7

Please sign in to comment.