From 0512a8a52a066d859f4a4e02d011542bea6487ef Mon Sep 17 00:00:00 2001 From: Jason Woods Date: Fri, 14 Feb 2020 14:11:26 +0000 Subject: [PATCH] fix: Enable port modification in state selinux.port_policy_present --- salt/modules/selinux.py | 46 ++- salt/states/selinux.py | 29 +- tests/pytests/unit/states/test_selinux.py | 335 ++++++++++++++++++++++ 3 files changed, 391 insertions(+), 19 deletions(-) diff --git a/salt/modules/selinux.py b/salt/modules/selinux.py index 55b18ed9d47..46ce1ec8b20 100644 --- a/salt/modules/selinux.py +++ b/salt/modules/selinux.py @@ -10,7 +10,6 @@ Execute calls on selinux proper packages are installed. """ - import os import re @@ -807,12 +806,47 @@ def port_add_policy(name, sel_type=None, protocol=None, port=None, sel_range=Non .. code-block:: bash - salt '*' selinux.port_add_policy add tcp/8080 http_port_t - salt '*' selinux.port_add_policy add foobar http_port_t protocol=tcp port=8091 + salt '*' selinux.port_add_policy tcp/8080 http_port_t + salt '*' selinux.port_add_policy foobar http_port_t protocol=tcp port=8091 """ return _port_add_or_delete_policy("add", name, sel_type, protocol, port, sel_range) +def port_modify_policy(name, sel_type=None, protocol=None, port=None, sel_range=None): + """ + .. versionadded:: 2019.2.0 + + Modifies the SELinux policy for a given protocol and port. + + Returns the result of the call to semanage. + + name + The protocol and port spec. Can be formatted as ``(tcp|udp)/(port|port-range)``. + + sel_type + The SELinux Type. Required. + + protocol + The protocol for the port, ``tcp`` or ``udp``. Required if name is not formatted. + + port + The port or port range. Required if name is not formatted. + + sel_range + The SELinux MLS/MCS Security Range. + + CLI Example: + + .. code-block:: bash + + salt '*' selinux.port_modify_policy tcp/8080 http_port_t + salt '*' selinux.port_modify_policy foobar http_port_t protocol=tcp port=8091 + """ + return _port_add_or_delete_policy( + "modify", name, sel_type, protocol, port, sel_range + ) + + def port_delete_policy(name, protocol=None, port=None): """ .. versionadded:: 2019.2.0 @@ -846,13 +880,13 @@ def _port_add_or_delete_policy( """ .. versionadded:: 2019.2.0 - Performs the action as called from ``port_add_policy`` or ``port_delete_policy``. + Performs the action as called from ``port_add_policy``, ``port_modify_policy`` or ``port_delete_policy``. Returns the result of the call to semanage. """ - if action not in ["add", "delete"]: + if action not in ["add", "modify", "delete"]: raise SaltInvocationError( - f'Actions supported are "add" and "delete", not "{action}".' + f'Actions supported are "add", "modify" and "delete", not "{action}".' ) if action == "add" and not sel_type: raise SaltInvocationError("SELinux Type is required to add a policy") diff --git a/salt/states/selinux.py b/salt/states/selinux.py index 24c48acd01b..1c2aa4cd61a 100644 --- a/salt/states/selinux.py +++ b/salt/states/selinux.py @@ -464,10 +464,8 @@ def fcontext_policy_applied(name, recursive=False): ret.update( { "result": True, - "comment": ( - 'SElinux policies are already applied for filespec "{}"'.format( - name - ) + "comment": 'SElinux policies are already applied for filespec "{}"'.format( + name ), } ) @@ -517,24 +515,31 @@ def port_policy_present(name, sel_type, protocol=None, port=None, sel_range=None { "result": True, "comment": f'SELinux policy for "{name}" already present ' - + 'with specified sel_type "{}", protocol "{}" and port "{}".'.format( - sel_type, protocol, port - ), + + f'with specified sel_type "{sel_type}", protocol "{protocol}" and port "{port}".', } ) return ret if __opts__["test"]: ret.update({"result": None}) else: - add_ret = __salt__["selinux.port_add_policy"]( + old_state = __salt__["selinux.port_get_policy"]( + name=name, + protocol=protocol, + port=port, + ) + if old_state: + module_method = "selinux.port_modify_policy" + else: + module_method = "selinux.port_add_policy" + add_modify_ret = __salt__[module_method]( name=name, sel_type=sel_type, protocol=protocol, port=port, sel_range=sel_range, ) - if add_ret["retcode"] != 0: - ret.update({"comment": f"Error adding new policy: {add_ret}"}) + if add_modify_ret["retcode"] != 0: + ret.update({"comment": f"Error adding new policy: {add_modify_ret}"}) else: ret.update({"result": True}) new_state = __salt__["selinux.port_get_policy"]( @@ -578,9 +583,7 @@ def port_policy_absent(name, sel_type=None, protocol=None, port=None): { "result": True, "comment": f'SELinux policy for "{name}" already absent ' - + 'with specified sel_type "{}", protocol "{}" and port "{}".'.format( - sel_type, protocol, port - ), + + f'with specified sel_type "{sel_type}", protocol "{protocol}" and port "{port}".', } ) return ret diff --git a/tests/pytests/unit/states/test_selinux.py b/tests/pytests/unit/states/test_selinux.py index 94a0a1ecac9..006bfdec3ef 100644 --- a/tests/pytests/unit/states/test_selinux.py +++ b/tests/pytests/unit/states/test_selinux.py @@ -1,5 +1,6 @@ """ :codeauthor: Jayesh Kariya + :codeauthor: Jason Woods """ import pytest @@ -118,3 +119,337 @@ def test_boolean(): ret.update({"comment": comt, "result": False}) ret.update({"changes": {}}) assert selinux.boolean(name, value) == ret + + +def test_port_policy_present(): + """ + Test to set up an SELinux port. + """ + name = "tcp/8080" + protocol = "tcp" + port = "8080" + ret = {"name": name, "changes": {}, "result": False, "comment": ""} + + # Test when already present with same sel_type + mock_add = MagicMock(return_value={"retcode": 0}) + mock_modify = MagicMock(return_value={"retcode": 0}) + mock_get = MagicMock( + return_value={ + "sel_type": "http_cache_port_t", + "protocol": "tcp", + "port": "8080", + } + ) + with patch.dict( + selinux.__salt__, + { + "selinux.port_get_policy": mock_get, + "selinux.port_add_policy": mock_add, + "selinux.port_modify_policy": mock_modify, + }, + ): + with patch.dict(selinux.__opts__, {"test": False}): + comt = ( + f'SELinux policy for "{name}" already present ' + + f'with specified sel_type "http_cache_port_t", protocol "None" ' + + f'and port "None".' + ) + ret.update({"comment": comt, "result": True}) + assert selinux.port_policy_present(name, "http_cache_port_t") == ret + + comt = ( + f'SELinux policy for "name" already present ' + + f'with specified sel_type "http_cache_port_t", protocol "{protocol}" ' + + f'and port "{port}".' + ) + ret.update({"comment": comt, "changes": {}, "result": True, "name": "name"}) + assert ( + selinux.port_policy_present("name", "http_cache_port_t", protocol, port) + == ret + ) + ret.update({"name": name}) + + # Test adding new port policy + mock_add = MagicMock(return_value={"retcode": 0}) + mock_modify = MagicMock(return_value={"retcode": 0}) + mock_get = MagicMock( + side_effect=[ + None, + None, + None, + {"sel_type": "http_cache_port_t", "protocol": "tcp", "port": "8080"}, + ] + ) + with patch.dict( + selinux.__salt__, + { + "selinux.port_get_policy": mock_get, + "selinux.port_add_policy": mock_add, + "selinux.port_modify_policy": mock_modify, + }, + ): + with patch.dict(selinux.__opts__, {"test": True}): + ret.update({"comment": "", "result": None}) + assert selinux.port_policy_present(name, "http_cache_port_t") == ret + + with patch.dict(selinux.__opts__, {"test": False}): + ret.update( + { + "comment": "", + "changes": { + "old": None, + "new": { + "sel_type": "http_cache_port_t", + "protocol": "tcp", + "port": "8080", + }, + }, + "result": True, + } + ) + assert selinux.port_policy_present(name, "http_cache_port_t") == ret + + # Test modifying policy to a new sel_type + mock_add = MagicMock(return_value={"retcode": 0}) + mock_modify = MagicMock(return_value={"retcode": 0}) + mock_get = MagicMock( + side_effect=[ + None, + None, + {"sel_type": "http_cache_port_t", "protocol": "tcp", "port": "8080"}, + {"sel_type": "http_port_t", "protocol": "tcp", "port": "8080"}, + ] + ) + with patch.dict( + selinux.__salt__, + { + "selinux.port_get_policy": mock_get, + "selinux.port_add_policy": mock_add, + "selinux.port_modify_policy": mock_modify, + }, + ): + with patch.dict(selinux.__opts__, {"test": True}): + ret.update({"comment": "", "changes": {}, "result": None}) + assert selinux.port_policy_present(name, "http_port_t") == ret + + with patch.dict(selinux.__opts__, {"test": False}): + ret.update( + { + "comment": "", + "changes": { + "old": { + "sel_type": "http_cache_port_t", + "protocol": "tcp", + "port": "8080", + }, + "new": { + "sel_type": "http_port_t", + "protocol": "tcp", + "port": "8080", + }, + }, + "result": True, + } + ) + assert selinux.port_policy_present(name, "http_port_t") == ret + + # Test adding new port policy with custom name and using protocol and port parameters + mock_add = MagicMock(return_value={"retcode": 0}) + mock_modify = MagicMock(return_value={"retcode": 0}) + mock_get = MagicMock( + side_effect=[ + None, + None, + {"sel_type": "http_cache_port_t", "protocol": "tcp", "port": "8081"}, + ] + ) + with patch.dict( + selinux.__salt__, + { + "selinux.port_get_policy": mock_get, + "selinux.port_add_policy": mock_add, + "selinux.port_modify_policy": mock_modify, + }, + ): + with patch.dict(selinux.__opts__, {"test": False}): + ret.update( + { + "name": "required_protocol_port", + "comment": "", + "changes": { + "old": None, + "new": { + "sel_type": "http_cache_port_t", + "protocol": "tcp", + "port": "8081", + }, + }, + "result": True, + } + ) + assert ( + selinux.port_policy_present( + "required_protocol_port", + "http_cache_port_t", + protocol="tcp", + port="8081", + ) + == ret + ) + + # Test failure of adding new policy + mock_add = MagicMock(return_value={"retcode": 1}) + mock_modify = MagicMock(return_value={"retcode": 1}) + mock_get = MagicMock(return_value=None) + with patch.dict( + selinux.__salt__, + { + "selinux.port_get_policy": mock_get, + "selinux.port_add_policy": mock_add, + "selinux.port_modify_policy": mock_modify, + }, + ): + with patch.dict(selinux.__opts__, {"test": False}): + comt = "Error adding new policy: {'retcode': 1}" + ret.update({"name": name, "comment": comt, "changes": {}, "result": False}) + assert selinux.port_policy_present(name, "http_cache_port_t") == ret + + +def test_port_policy_absent(): + """ + Test to delete an SELinux port. + """ + name = "tcp/8080" + protocol = "tcp" + port = "8080" + ret = {"name": name, "changes": {}, "result": False, "comment": ""} + + # Test policy already removed + mock_delete = MagicMock(return_value={"retcode": 0}) + mock_get = MagicMock(return_value=None) + with patch.dict( + selinux.__salt__, + { + "selinux.port_get_policy": mock_get, + "selinux.port_delete_policy": mock_delete, + }, + ): + with patch.dict(selinux.__opts__, {"test": False}): + comt = ( + f'SELinux policy for "{name}" already absent ' + + f'with specified sel_type "http_cache_port_t", protocol "None" ' + + f'and port "None".' + ) + ret.update({"comment": comt, "changes": {}, "result": True}) + assert selinux.port_policy_absent(name, "http_cache_port_t") == ret + + comt = ( + f'SELinux policy for "name" already absent ' + + f'with specified sel_type "http_cache_port_t", protocol "{protocol}" ' + + f'and port "{port}".' + ) + ret.update({"comment": comt, "changes": {}, "result": True, "name": "name"}) + assert ( + selinux.port_policy_absent("name", "http_cache_port_t", protocol, port) + == ret + ) + ret.update({"name": name}) + + # Test removing a policy + mock_delete = MagicMock(return_value={"retcode": 0}) + mock_get = MagicMock( + side_effect=[ + {"sel_type": "http_cache_port_t", "protocol": "tcp", "port": "8080"}, + {"sel_type": "http_cache_port_t", "protocol": "tcp", "port": "8080"}, + None, + ] + ) + with patch.dict( + selinux.__salt__, + { + "selinux.port_get_policy": mock_get, + "selinux.port_delete_policy": mock_delete, + }, + ): + with patch.dict(selinux.__opts__, {"test": True}): + ret.update({"comment": "", "result": None}) + assert selinux.port_policy_absent(name, "http_cache_port_t") == ret + + with patch.dict(selinux.__opts__, {"test": False}): + ret.update( + { + "comment": "", + "changes": { + "old": { + "sel_type": "http_cache_port_t", + "protocol": "tcp", + "port": "8080", + }, + "new": None, + }, + "result": True, + } + ) + assert selinux.port_policy_absent(name, "http_cache_port_t") == ret + + # Test removing a policy using custom name and with protocol and port parameters + mock_delete = MagicMock(return_value={"retcode": 0}) + mock_get = MagicMock( + side_effect=[ + {"sel_type": "http_cache_port_t", "protocol": "tcp", "port": "8081"}, + None, + ] + ) + with patch.dict( + selinux.__salt__, + { + "selinux.port_get_policy": mock_get, + "selinux.port_delete_policy": mock_delete, + }, + ): + with patch.dict(selinux.__opts__, {"test": False}): + ret.update( + { + "name": "required_protocol_port", + "comment": "", + "changes": { + "old": { + "sel_type": "http_cache_port_t", + "protocol": "tcp", + "port": "8081", + }, + "new": None, + }, + "result": True, + } + ) + assert ( + selinux.port_policy_absent( + "required_protocol_port", + "http_cache_port_t", + protocol="tcp", + port="8081", + ) + == ret + ) + + # Test failure to delete a policy + mock_delete = MagicMock(return_value={"retcode": 2}) + mock_get = MagicMock( + return_value={ + "sel_type": "http_cache_port_t", + "protocol": "tcp", + "port": "8080", + } + ) + with patch.dict( + selinux.__salt__, + { + "selinux.port_get_policy": mock_get, + "selinux.port_delete_policy": mock_delete, + }, + ): + with patch.dict(selinux.__opts__, {"test": False}): + comt = "Error deleting policy: {'retcode': 2}" + ret.update({"name": name, "comment": comt, "changes": {}, "result": False}) + assert selinux.port_policy_absent(name, "http_cache_port_t") == ret