Skip to content

Commit

Permalink
selinux: update kernel boot params when disabling/re-enabling SELinux
Browse files Browse the repository at this point in the history
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, add an 'update_kernel_param' module parameter that will cause
it to set/unset the kernel command-line parameter using grubby when
enabling/disabling SELinux. (An explicit parameter was chosen for
backwards compatibility.)

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 <[email protected]>
  • Loading branch information
WOnder93 committed Aug 18, 2021
1 parent 7f16f56 commit 80125f1
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 1 deletion.
2 changes: 2 additions & 0 deletions changelogs/fragments/disable_selinux_via_kernel_cmdline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- selinux - optionally update kernel boot params when disabling/re-enabling SELinux (https://github.com/ansible-collections/ansible.posix/pull/142).
69 changes: 69 additions & 0 deletions plugins/modules/selinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
required: true
choices: [ disabled, enforcing, permissive ]
type: str
update_kernel_param:
description:
- If set to true, will update also the kernel boot parameters when disabling/enabling SELinux.
- The 'grubby' tool must be present on the target system for this to work.
type: bool
configfile:
description:
- The path to the SELinux configuration file, if non-standard.
Expand Down Expand Up @@ -97,6 +102,7 @@
HAS_SELINUX = False

from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.process import get_bin_path
from ansible.module_utils.facts.utils import get_file_lines


Expand All @@ -119,6 +125,34 @@ def get_config_policy(configfile):
return line.split('=')[1].strip()


def get_kernel_enabled(module, grubby_bin):
if grubby_bin is None:
module.fail_json(msg="'grubby' command not found on host",
details="In order to update the kernel command line"
"enabled/disabled setting, the grubby package"
"needs to be present on the system.")

rc, stdout, stderr = module.run_command([grubby_bin, '--info=ALL'])
if rc != 0:
module.fail_json(msg="unable to run grubby")

all_enabled = True
all_disabled = True
for line in stdout.split('\n'):
match = re.match('^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:
# inconsistent config - return None to force update
return None
return all_enabled


# setter subroutines
def set_config_state(module, state, configfile):
# SELINUX=permissive
Expand Down Expand Up @@ -153,6 +187,17 @@ def set_state(module, state):
module.fail_json(msg=msg)


def set_kernel_enabled(module, grubby_bin, value):
rc, stdout, stderr = module.run_command([grubby_bin, '--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)
Expand Down Expand Up @@ -183,6 +228,7 @@ def main():
policy=dict(type='str'),
state=dict(type='str', required=True, choices=['enforcing', 'permissive', 'disabled']),
configfile=dict(type='str', default='/etc/selinux/config', aliases=['conf', 'file']),
update_kernel_param=dict(type='bool', default=False),
),
supports_check_mode=True,
)
Expand All @@ -196,9 +242,11 @@ def main():
configfile = module.params['configfile']
policy = module.params['policy']
state = module.params['state']
update_kernel_param = module.params['update_kernel_param']
runtime_enabled = selinux.is_selinux_enabled()
runtime_policy = selinux.selinux_getpolicytype()[1]
runtime_state = 'disabled'
kernel_enabled = None
reboot_required = False

if runtime_enabled:
Expand All @@ -215,6 +263,12 @@ def main():

config_policy = get_config_policy(configfile)
config_state = get_config_state(configfile)
if update_kernel_param:
try:
grubby_bin = get_bin_path('grubby')
except ValueError:
grubby_bin = None
kernel_enabled = get_kernel_enabled(module, grubby_bin)

# check to see if policy is set if state is not 'disabled'
if state != 'disabled':
Expand Down Expand Up @@ -269,6 +323,21 @@ 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 update_kernel_param and kernel_enabled != requested_kernel_enabled:
if not module.check_mode:
set_kernel_enabled(module, grubby_bin, requested_kernel_enabled)
if requested_kernel_enabled:
states = ('disabled', 'enabled')
else:
states = ('enabled', 'disabled')
if kernel_enabled == None:
states = ('<inconsistent>', states[1])
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)


Expand Down
48 changes: 47 additions & 1 deletion tests/integration/targets/selinux/tasks/selinux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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') }}"
Expand Down Expand Up @@ -104,11 +109,52 @@
- selinux_config_after[selinux_config_after.index('SELINUX=disabled')] is search("^SELINUX=\w+$")
- selinux_config_after[selinux_config_after.index('SELINUXTYPE=targeted')] is search("^SELINUXTYPE=\w+$")

- name: TEST 1 | Reset SELinux configuration for next test
- name: TEST 1 | Disable SELinux again, with kernel arguments update
selinux:
state: disabled
policy: targeted
update_kernel_param: true
register: _disable_test2

- 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 | Enable SELinux, without kernel arguments update
selinux:
state: disabled
policy: targeted
register: _disable_test2

- name: Check kernel command-line arguments
ansible.builtin.command: grubby --info=DEFAULT
register: _grubby_test1

- name: TEST 1 | Assert that kernel cmdline still contains selinux=0
assert:
that:
- "' selinux=0' in _grubby_test1.stdout"

- name: TEST 1 | Reset SELinux configuration for next test (also kernel args)
selinux:
state: enforcing
update_kernel_param: true
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
# ##############################################################################
Expand Down

0 comments on commit 80125f1

Please sign in to comment.