seport: fix idempotency when port is covered by an existing range (#11994)

* fix(seport): handle port overlap with existing ranges

Fixes idempotency when a requested port is already covered by an
existing range registered for the same setype/proto. Also improves
the error message when libsemanage raises FileNotFoundError on a
port overlap validation failure.

Fixes #10105

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(seport): add changelog fragment for #11994

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alexei Znamensky
2026-05-07 07:36:02 +12:00
committed by GitHub
parent 02b969ee4d
commit a43006c7cb
2 changed files with 31 additions and 1 deletions

View File

@@ -0,0 +1,2 @@
bugfixes:
- seport - fix idempotency when a requested port is already covered by an existing range registered for the same setype (https://github.com/ansible-collections/community.general/issues/10105, https://github.com/ansible-collections/community.general/pull/11994).

View File

@@ -158,6 +158,24 @@ def semanage_port_get_ports(seport, setype, proto, local):
return []
def _parse_port_range(port):
"""Return (low, high) integers for a port or port range string."""
parts = str(port).split("-", 1)
low = int(parts[0])
high = int(parts[1]) if len(parts) == 2 else low
return low, high
def _port_is_covered(port, existing_ports):
"""Return True if port (or range) is fully covered by an existing port entry."""
req_low, req_high = _parse_port_range(port)
for entry in existing_ports:
entry_low, entry_high = _parse_port_range(entry)
if entry_low <= req_low and req_high <= entry_high:
return True
return False
def semanage_port_get_type(seport, port, proto):
"""Get the SELinux type of the specified port.
@@ -218,7 +236,7 @@ def semanage_port_add(module, ports, proto, setype, do_reload, serange="s0", ses
seport.set_reload(do_reload)
ports_by_type = semanage_port_get_ports(seport, setype, proto, local)
for port in ports:
if port in ports_by_type:
if _port_is_covered(port, ports_by_type):
continue
change = True
@@ -230,6 +248,11 @@ def semanage_port_add(module, ports, proto, setype, do_reload, serange="s0", ses
else:
seport.modify(port, proto, serange, setype)
except FileNotFoundError as e:
module.fail_json(
msg=f"Failed to modify SELinux port policy, possibly due to a port overlap with an existing range: {e}\n",
exception=traceback.format_exc(),
)
except (ValueError, OSError, KeyError, RuntimeError) as e:
module.fail_json(msg=f"{e.__class__.__name__}: {e}\n", exception=traceback.format_exc())
@@ -271,6 +294,11 @@ def semanage_port_del(module, ports, proto, setype, do_reload, sestore="", local
if not module.check_mode:
seport.delete(port, proto)
except FileNotFoundError as e:
module.fail_json(
msg=f"Failed to modify SELinux port policy, possibly due to a port overlap with an existing range: {e}\n",
exception=traceback.format_exc(),
)
except (ValueError, OSError, KeyError, RuntimeError) as e:
module.fail_json(msg=f"{e.__class__.__name__}: {e}\n", exception=traceback.format_exc())