Repository: ansible/ansible-modules-extras

Issue:
seport does not reassign port already used in system policy
##### Issue Type:
- Bug Report
##### Ansible Version:

```
ansible --version
ansible 2.0.0.2
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```
##### Ansible Configuration:

I hope irrelevant
##### Environment:

Fedora 23 on my machine, CentOs 7 it is managed.
##### Summary:

seport module does not reassign port if it already assigned elsewhere. It report OK, but do nothing. Even no error.
##### Steps To Reproduce:

```
- seport: ports=8020 proto=tcp setype=http_port_t state=present
```

```
ok: [unidata.perf.client] => {"changed": false, "ports": ["8020"], "proto": "tcp", "setype": "http_port_t", "state": "present"}
```
##### Expected Results:

SELinux port 8020 assigned to http_port_t type.
##### Actual Results:

It does not happened:

```
# semanage port -l | ag '^http_port_t|8020'
hadoop_namenode_port_t         tcp      8020
http_port_t                    tcp      80, 81, 443, 488, 8008, 8009, 8443, 9000
```

I think next is a reason:

```
# semanage port -a -t http_port_t -p tcp 8020
ValueError: Port tcp/8020 already defined
```

But modify work:

```
# semanage port -m -t http_port_t -p tcp 8020
# semanage port -l | ag '^http_port_t|8020'
hadoop_namenode_port_t         tcp      8020
http_port_t                    tcp      8020, 80, 81, 443, 488, 8008, 8009, 8443, 9000
```

P.S. Initially reported to https://github.com/ansible/ansible/issues/14466



Discussion:
Same problem with Ansible 2.0.2.0.
Can someone point to the `selinux` and `seobject` Python modules' documentation. I really cannot find it.
Is there an update function similar to `seport.update(port, proto, serange, setype)` ?
I'd like to try to fix this issue.
Cheers,

Same problem here on Fedora 23 and Fedora 24 both running Ansible 2.1.0.0.
I want to use 443 for ssh, but it can't because it's already in use by https. Ansible however doesn't give a warning and states in a green text that's it's okay, while it's not. As the bug reporter already pointed out, an add doesn't work if it's already assigned, a modify statement is needed in that case.

+1
A flag to fail in case it already exists could help it determining whether it succeeds or not. Also, there should be an option to add the port to a rule without removing it from another one (semanage -m option does this right).

@dankeder, ping. This issue is still waiting on your response.
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)

<!--- boilerplate: issue_renotify_maintainer --->


Resolving patch:
diff --git a/system/seport.py b/system/seport.py
--- a/system/seport.py
+++ b/system/seport.py
@@ -83,9 +83,30 @@
 from ansible.module_utils.basic import *
 from ansible.module_utils.pycompat24 import get_exception
 
-def semanage_port_exists(seport, port, proto):
-    """ Get the SELinux port type definition from policy. Return None if it does
-    not exist.
+
+def semanage_port_get_ports(seport, setype, proto):
+    """ Get the list of ports that have the specified type definition.
+
+    :param seport: Instance of seobject.portRecords
+
+    :type setype: str
+    :param setype: SELinux type.
+
+    :type proto: str
+    :param proto: Protocol ('tcp' or 'udp')
+
+    :rtype: list
+    :return: List of ports that have the specified SELinux type.
+    """
+    records = seport.get_all_by_type()
+    if (setype, proto) in records:
+        return records[(setype, proto)]
+    else:
+        return []
+
+
+def semanage_port_get_type(seport, port, proto):
+    """ Get the SELinux type of the specified port.
 
     :param seport: Instance of seobject.portRecords
 
@@ -95,15 +116,19 @@ def semanage_port_exists(seport, port, proto):
     :type proto: str
     :param proto: Protocol ('tcp' or 'udp')
 
-    :rtype: bool
-    :return: True if the SELinux port type definition exists, False otherwise
+    :rtype: tuple
+    :return: Tuple containing the SELinux type and MLS/MCS level, or None if not found.
     """
     ports = port.split('-', 1)
     if len(ports) == 1:
         ports.extend(ports)
-    ports = map(int, ports)
-    record = (ports[0], ports[1], proto)
-    return record in seport.get_all()
+    key = (int(ports[0]), int(ports[1]), proto)
+
+    records = seport.get_all()
+    if key in records:
+        return records[key]
+    else:
+        return None
 
 
 def semanage_port_add(module, ports, proto, setype, do_reload, serange='s0', sestore=''):
@@ -137,11 +162,15 @@ def semanage_port_add(module, ports, proto, setype, do_reload, serange='s0', ses
         seport = seobject.portRecords(sestore)
         seport.set_reload(do_reload)
         change = False
+        ports_by_type = semanage_port_get_ports(seport, setype, proto)
         for port in ports:
-            exists = semanage_port_exists(seport, port, proto)
-            if not exists and not module.check_mode:
-                seport.add(port, proto, serange, setype)
-            change = change or not exists
+            if port not in ports_by_type:
+                change = True
+                port_type = semanage_port_get_type(seport, port, proto)
+                if port_type is None and not module.check_mode:
+                    seport.add(port, proto, serange, setype)
+                elif port_type is not None and not module.check_mode:
+                    seport.modify(port, proto, serange, setype)
 
     except ValueError:
         e = get_exception()
@@ -162,7 +191,7 @@ def semanage_port_add(module, ports, proto, setype, do_reload, serange='s0', ses
     return change
 
 
-def semanage_port_del(module, ports, proto, do_reload, sestore=''):
+def semanage_port_del(module, ports, proto, setype, do_reload, sestore=''):
     """ Delete SELinux port type definition from the policy.
 
     :type module: AnsibleModule
@@ -174,6 +203,9 @@ def semanage_port_del(module, ports, proto, do_reload, sestore=''):
     :type proto: str
     :param proto: Protocol ('tcp' or 'udp')
 
+    :type setype: str
+    :param setype: SELinux type.
+
     :type do_reload: bool
     :param do_reload: Whether to reload SELinux policy after commit
 
@@ -187,11 +219,12 @@ def semanage_port_del(module, ports, proto, do_reload, sestore=''):
         seport = seobject.portRecords(sestore)
         seport.set_reload(do_reload)
         change = False
+        ports_by_type = semanage_port_get_ports(seport, setype, proto)
         for port in ports:
-            exists = semanage_port_exists(seport, port, proto)
-            if not exists and not module.check_mode:
-                seport.delete(port, proto)
-            change = change or not exists
+            if port in ports_by_type:
+                change = True
+                if not module.check_mode:
+                    seport.delete(port, proto)
 
     except ValueError:
         e = get_exception()
@@ -262,7 +295,7 @@ def main():
     if state == 'present':
         result['changed'] = semanage_port_add(module, ports, proto, setype, do_reload)
     elif state == 'absent':
-        result['changed'] = semanage_port_del(module, ports, proto, do_reload)
+        result['changed'] = semanage_port_del(module, ports, proto, setype, do_reload)
     else:
         module.fail_json(msg='Invalid value of argument "state": {0}'.format(state))
 


Test patch: