community.general/lib/ansible/modules/system/seboolean.py
Matthew Krupcale 277bb3066c seboolean: Fix issue with changing persistent SELinux boolean values (#22779)
Previously, when the active SELinux value was the same as the desired value, regardless of the value of `persistent`, the module would simply exit successfully, assuming no need for update. This made the assumption that the active and persistent values should be the same, but that is not always the case. This modification treats both the active and persistent values separately when checking for the need to update. Note that the persistent update mode, however, will still update the active as well as the persistent value. If this is not desired, it is possible to separately toggle the active value alone.

Other changes:
* Make the check mode actually perform checks for changes
* Organizes semanage commands into set of logical steps
2018-09-28 19:04:33 -04:00

319 lines
9.9 KiB
Python

#!/usr/bin/python
# Copyright: (c) 2012, Stephen Fromm <sfromm@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'core'}
DOCUMENTATION = '''
---
module: seboolean
short_description: Toggles SELinux booleans
description:
- Toggles SELinux booleans.
version_added: "0.7"
options:
name:
description:
- Name of the boolean to configure.
required: true
persistent:
description:
- Set to C(yes) if the boolean setting should survive a reboot.
type: bool
default: 'no'
state:
description:
- Desired boolean value
type: bool
required: true
notes:
- Not tested on any Debian based system.
requirements:
- libselinux-python
- libsemanage-python
author:
- Stephen Fromm (@sfromm)
'''
EXAMPLES = '''
- name: Set httpd_can_network_connect flag on and keep it persistent across reboots
seboolean:
name: httpd_can_network_connect
state: yes
persistent: yes
'''
import os
try:
import selinux
HAVE_SELINUX = True
except ImportError:
HAVE_SELINUX = False
try:
import semanage
HAVE_SEMANAGE = True
except ImportError:
HAVE_SEMANAGE = False
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import binary_type
from ansible.module_utils._text import to_bytes
def has_boolean_value(module, name):
bools = []
try:
rc, bools = selinux.security_get_boolean_names()
except OSError:
module.fail_json(msg="Failed to get list of boolean names")
# work around for selinux who changed its API, see
# https://github.com/ansible/ansible/issues/25651
if len(bools) > 0:
if isinstance(bools[0], binary_type):
name = to_bytes(name)
if name in bools:
return True
else:
return False
def get_boolean_value(module, name):
state = 0
try:
state = selinux.security_get_boolean_active(name)
except OSError:
module.fail_json(msg="Failed to determine current state for boolean %s" % name)
if state == 1:
return True
else:
return False
def semanage_get_handle(module):
handle = semanage.semanage_handle_create()
if not handle:
module.fail_json(msg="Failed to create semanage library handle")
managed = semanage.semanage_is_managed(handle)
if managed <= 0:
semanage.semanage_handle_destroy(handle)
if managed < 0:
module.fail_json(msg="Failed to determine whether policy is manage")
if managed == 0:
if os.getuid() == 0:
module.fail_json(msg="Cannot set persistent booleans without managed policy")
else:
module.fail_json(msg="Cannot set persistent booleans; please try as root")
if semanage.semanage_connect(handle) < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to connect to semanage")
return handle
def semanage_begin_transaction(module, handle):
if semanage.semanage_begin_transaction(handle) < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to begin semanage transaction")
def semanage_set_boolean_value(module, handle, name, value):
rc, t_b = semanage.semanage_bool_create(handle)
if rc < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to create seboolean with semanage")
if semanage.semanage_bool_set_name(handle, t_b, name) < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to set seboolean name with semanage")
rc, boolkey = semanage.semanage_bool_key_extract(handle, t_b)
if rc < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to extract boolean key with semanage")
rc, exists = semanage.semanage_bool_exists(handle, boolkey)
if rc < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to check if boolean is defined")
if not exists:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="SELinux boolean %s is not defined in persistent policy" % name)
rc, sebool = semanage.semanage_bool_query(handle, boolkey)
if rc < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to query boolean in persistent policy")
semanage.semanage_bool_set_value(sebool, value)
if semanage.semanage_bool_modify_local(handle, boolkey, sebool) < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to modify boolean key with semanage")
if semanage.semanage_bool_set_active(handle, boolkey, sebool) < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to set boolean key active with semanage")
semanage.semanage_bool_key_free(boolkey)
semanage.semanage_bool_free(t_b)
semanage.semanage_bool_free(sebool)
def semanage_get_boolean_value(module, handle, name):
rc, t_b = semanage.semanage_bool_create(handle)
if rc < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to create seboolean with semanage")
if semanage.semanage_bool_set_name(handle, t_b, name) < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to set seboolean name with semanage")
rc, boolkey = semanage.semanage_bool_key_extract(handle, t_b)
if rc < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to extract boolean key with semanage")
rc, exists = semanage.semanage_bool_exists(handle, boolkey)
if rc < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to check if boolean is defined")
if not exists:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="SELinux boolean %s is not defined in persistent policy" % name)
rc, sebool = semanage.semanage_bool_query(handle, boolkey)
if rc < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to query boolean in persistent policy")
value = semanage.semanage_bool_get_value(sebool)
semanage.semanage_bool_key_free(boolkey)
semanage.semanage_bool_free(t_b)
semanage.semanage_bool_free(sebool)
return value
def semanage_commit(module, handle, load=0):
semanage.semanage_set_reload(handle, load)
if semanage.semanage_commit(handle) < 0:
semanage.semanage_handle_destroy(handle)
module.fail_json(msg="Failed to commit changes to semanage")
def semanage_destroy_handle(module, handle):
rc = semanage.semanage_disconnect(handle)
semanage.semanage_handle_destroy(handle)
if rc < 0:
module.fail_json(msg="Failed to disconnect from semanage")
# The following method implements what setsebool.c does to change
# a boolean and make it persist after reboot..
def semanage_boolean_value(module, name, state):
value = 0
changed = False
if state:
value = 1
try:
handle = semanage_get_handle(module)
semanage_begin_transaction(module, handle)
cur_value = semanage_get_boolean_value(module, handle, name)
if cur_value != value:
changed = True
if not module.check_mode:
semanage_set_boolean_value(module, handle, name, value)
semanage_commit(module, handle)
semanage_destroy_handle(module, handle)
except Exception as e:
module.fail_json(msg="Failed to manage policy for boolean %s: %s" % (name, str(e)))
return changed
def set_boolean_value(module, name, state):
rc = 0
value = 0
if state:
value = 1
try:
rc = selinux.security_set_boolean(name, value)
except OSError:
module.fail_json(msg="Failed to set boolean %s to %s" % (name, value))
if rc == 0:
return True
else:
return False
def main():
module = AnsibleModule(
argument_spec=dict(
name=dict(type='str', required=True),
persistent=dict(type='bool', default=False),
state=dict(type='bool', required=True),
),
supports_check_mode=True,
)
if not HAVE_SELINUX:
module.fail_json(msg="This module requires libselinux-python support")
if not HAVE_SEMANAGE:
module.fail_json(msg="This module requires libsemanage-python support")
if not selinux.is_selinux_enabled():
module.fail_json(msg="SELinux is disabled on this host.")
name = module.params['name']
persistent = module.params['persistent']
state = module.params['state']
result = dict(
name=name,
persistent=persistent,
state=state
)
changed = False
if hasattr(selinux, 'selinux_boolean_sub'):
# selinux_boolean_sub allows sites to rename a boolean and alias the old name
# Feature only available in selinux library since 2012.
name = selinux.selinux_boolean_sub(name)
if not has_boolean_value(module, name):
module.fail_json(msg="SELinux boolean %s does not exist." % name)
if persistent:
changed = semanage_boolean_value(module, name, state)
else:
cur_value = get_boolean_value(module, name)
if cur_value != state:
changed = True
if not module.check_mode:
changed = set_boolean_value(module, name, state)
if not changed:
module.fail_json(msg="Failed to set boolean %s to %s" % (name, state))
try:
selinux.security_commit_booleans()
except:
module.fail_json(msg="Failed to commit pending boolean %s value" % name)
result['changed'] = changed
module.exit_json(**result)
if __name__ == '__main__':
main()