From dab5587b1e96bb01aa9295da8a9e9eff82d7e183 Mon Sep 17 00:00:00 2001 From: Ondrej Mosnacek Date: Tue, 15 Dec 2020 17:24:37 +0100 Subject: [PATCH] selinux: update kernel boot params when disabling/re-enabling SELinux The ability to disable SELinux from userspace based on the configuration file is being deprecated in favor of the selinux=0 kernel boot parameter. (Note that this affects only the "full" disable; switching to/from permissive mode will work the same as before.) Therefore, enhance the selinux module to try to set/unset the kernel command-line parameter using grubby when enabling/disabling SELinux. If the grubby package is not present on the system, the module will only update the config file and report a warning. Note that even with the runtime disable functionality removed, setting SELINUX=disabled in the config file will lead to a system with no SELinux policy loaded, which will behave in a very similar way as if SELinux was fully disabled, only there could still be some minor performance impact, since the kernel hooks will still be active. More information: https://lore.kernel.org/selinux/157836784986.560897.13893922675143903084.stgit@chester/ https://fedoraproject.org/wiki/Changes/Remove_Support_For_SELinux_Runtime_Disable Signed-off-by: Ondrej Mosnacek --- plugins/modules/selinux.py | 50 +++++++++++++++++++ .../targets/selinux/tasks/selinux.yml | 23 +++++++++ 2 files changed, 73 insertions(+) diff --git a/plugins/modules/selinux.py b/plugins/modules/selinux.py index a22c282ac37..0a9c50760d6 100644 --- a/plugins/modules/selinux.py +++ b/plugins/modules/selinux.py @@ -119,6 +119,30 @@ def get_config_policy(configfile): return line.split('=')[1].strip() +def get_kernel_enabled(module): + rc, stdout, stderr = module.run_command(['grubby', '--info=ALL']) + if rc != 0: + module.warn("'grubby' command failed - kernel boot configuration won't be updated") + return None + + all_enabled = True + all_disabled = True + for line in stdout.split('\n'): + match = re.fullmatch('args="(.*)"', line) + if match is None: + continue + args = match.group(1).split(' ') + if 'selinux=0' in args: + all_enabled = False + else: + all_disabled = False + if all_disabled == all_enabled: + module.warn("Kernel SELinux configuration inconsistent - " + "kernel boot configuration won't be updated") + return None + return all_enabled + + # setter subroutines def set_config_state(module, state, configfile): # SELINUX=permissive @@ -153,6 +177,17 @@ def set_state(module, state): module.fail_json(msg=msg) +def set_kernel_enabled(module, value): + rc, stdout, stderr = module.run_command(['grubby', '--update-kernel=ALL', + '--remove-args' if value else '--args', + 'selinux=0']) + if rc != 0: + if value: + module.fail_json(msg='unable to remove selinux=0 from kernel config') + else: + module.fail_json(msg='unable to add selinux=0 to kernel config') + + def set_config_policy(module, policy, configfile): if not os.path.exists('/etc/selinux/%s/policy' % policy): module.fail_json(msg='Policy %s does not exist in /etc/selinux/' % policy) @@ -199,6 +234,7 @@ def main(): runtime_enabled = selinux.is_selinux_enabled() runtime_policy = selinux.selinux_getpolicytype()[1] runtime_state = 'disabled' + kernel_enabled = None reboot_required = False if runtime_enabled: @@ -215,6 +251,7 @@ def main(): config_policy = get_config_policy(configfile) config_state = get_config_state(configfile) + kernel_enabled = get_kernel_enabled(module) # check to see if policy is set if state is not 'disabled' if state != 'disabled': @@ -269,6 +306,19 @@ def main(): msgs.append("Config SELinux state changed from '%s' to '%s'" % (config_state, state)) changed = True + requested_kernel_enabled = state in ('enforcing', 'permissive') + # Update kernel enabled/disabled config only when setting is consistent + # across all kernels AND the requested state differs from the current state + if kernel_enabled is not None and kernel_enabled != requested_kernel_enabled: + if not module.check_mode: + set_kernel_enabled(module, requested_kernel_enabled) + if requested_kernel_enabled: + states = ('disabled', 'enabled') + else: + states = ('enabled', 'disabled') + msgs.append("Kernel SELinux state changed from '%s' to '%s'" % states) + changed = True + module.exit_json(changed=changed, msg=', '.join(msgs), configfile=configfile, policy=policy, state=state, reboot_required=reboot_required) diff --git a/tests/integration/targets/selinux/tasks/selinux.yml b/tests/integration/targets/selinux/tasks/selinux.yml index a262f8bcb1a..ef946f22f64 100644 --- a/tests/integration/targets/selinux/tasks/selinux.yml +++ b/tests/integration/targets/selinux/tasks/selinux.yml @@ -20,6 +20,11 @@ # ############################################################################## # Test changing the state, which requires a reboot +- name: TEST 1 | Make sure grubby is present + package: + name: grubby + state: present + - name: TEST 1 | Get current SELinux config file contents set_fact: selinux_config_original: "{{ lookup('file', '/etc/sysconfig/selinux').split('\n') }}" @@ -72,6 +77,15 @@ var: ansible_selinux verbosity: 1 +- name: Check kernel command-line arguments + ansible.builtin.command: grubby --info=DEFAULT + register: _grubby_test1 + +- name: TEST 1 | Assert that kernel cmdline contains selinux=0 + assert: + that: + - "' selinux=0' in _grubby_test1.stdout" + - name: TEST 1 | Disable SELinux again selinux: state: disabled @@ -109,6 +123,15 @@ state: enforcing policy: targeted +- name: Check kernel command-line arguments + ansible.builtin.command: grubby --info=DEFAULT + register: _grubby_test2 + +- name: TEST 1 | Assert that kernel cmdline doesn't contain selinux=0 + assert: + that: + - "' selinux=0' not in _grubby_test2.stdout" + # Second Test # ##############################################################################