The management of Security-Enhanced Linux (SELinux) network port assignments is a critical intersection of system security and automation. In a hardened Linux environment, SELinux does not merely monitor files and processes; it governs the network sockets that services are permitted to bind to. When a system administrator changes a service port—such as moving the Secure Shell (SSH) daemon from the standard port 22 to a non-standard port like 2222—the kernel's security policy will block the service from starting if the new port is not explicitly labeled with the correct type. The seport module in Ansible serves as the primary programmatic interface to manage these labels, effectively acting as a wrapper for the semanage port utility.
Failure to synchronize the application configuration (e.g., sshd_config) with the SELinux policy leads to immediate service failure. While the application may be configured to listen on a specific port, the SELinux policy acts as a secondary, mandatory access control layer. If the port is not labeled with the appropriate type (such as ssh_port_t or http_port_t), the kernel will deny the bind() system call, resulting in a "Permission Denied" error that is often mistaken for a firewall issue. By utilizing the seport module, engineers can ensure that the security policy is updated atomically alongside the service configuration, maintaining the integrity of the security posture without sacrificing deployment velocity.
Technical Architecture and Module Functionality
The seport module is designed to manage the SELinux port database. It allows for the addition, removal, and modification of port labels associated with specific security types. This is essential for any environment where services are moved from default ports to custom ones to mitigate brute-force attacks or to accommodate multiple services of the same type on a single IP address.
The module interacts with the underlying system by leveraging the semanage tool. Specifically, it targets the seport functionality to define which ports are associated with which security contexts. For example, the ssh_port_t type is specifically reserved for the SSH daemon. If a user attempts to run SSH on port 2222 without assigning that port to ssh_port_t, the operation will fail.
The fundamental parameters of the seport module include:
ports: This parameter specifies the port number or range of ports that need to be managed. Multiple values can be separated by commas.proto: This defines the network protocol, typicallytcporudp.setype: This is the SELinux type label that must be assigned to the port (e.g.,http_port_tfor web servers).state: This ensures the desired state of the port label, usuallypresentto add the port orabsentto remove it.
Deep Dive into SSH Port Migration and Idempotency
Changing the SSH port is a common security hardening task. However, implementing this via Ansible requires careful handling of the ansible_port variable to ensure the playbook remains idempotent. If a playbook changes the port from 22 to 2222 and restarts the service, a subsequent run of the same playbook will fail because Ansible will still attempt to connect via port 22 unless the connection variables are dynamically updated.
The Logic of Port Detection
To achieve a seamless transition, a sophisticated detection logic must be implemented. The process involves verifying the current reachability of the host through a series of wait_for checks:
- Verification of Default Port: The playbook first checks if port 22 is open using
wait_fordelegated tolocalhost. If the port is reachable, theansible_portis set to 22. - Verification of Custom Port: If port 22 is unreachable, the playbook checks the port currently specified in the inventory. If this port is reachable, it is registered as the active connection point.
- Failure Handling: If neither the default port nor the inventory port is reachable, the playbook must trigger a failure state, as the host is inaccessible.
Implementation of the Port Change
Once the active port is identified, the transition to a new port follows a strict sequence of tasks to prevent lockout:
- Update Configuration: The
lineinfilemodule is used to modify/etc/ssh/sshd_config, replacing thePortdirective with the newconfigured_port. - SELinux Labeling: The
seportmodule is invoked to assign thessh_port_tlabel to the new port. - Service Restart: A handler is triggered to restart
sshd. - State Synchronization: The
meta: flush_handlerstask is called to ensure the restart happens immediately. - Fact Update: The
set_factmodule updatesansible_portso that all subsequent tasks in the play use the new port.
Example Workflow for SSH Port Modification
The following sequence demonstrates the implementation of this logic:
```yaml
- name: Set configured port fact
setfact:
configuredport: "{{ ansible_port }}"
name: Check if we are using the default SSH port
waitfor:
port: "22"
state: "started"
host: "{{ inventoryhostname }}"
connecttimeout: "5"
timeout: "10"
delegateto: "localhost"
ignoreerrors: "yes"
register: defaultsshname: Set inventory ansibleport to default
setfact:
ansibleport: "22"
when: defaultssh is defined and defaultssh.state == "started"
register: sshport_setname: Check if we are using the inventory-provided SSH port
waitfor:
port: "{{ ansibleport }}"
state: "started"
host: "{{ inventoryhostname }}"
connecttimeout: "5"
timeout: "10"
delegateto: "localhost"
ignoreerrors: "yes"
register: configuredssh
when: defaultssh is defined and default_ssh.state is undefinedname: Setup alternate SSH port
lineinfile:
dest: "/etc/ssh/sshdconfig"
regexp: "^Port"
line: "Port {{ configuredport }}"
notify: "Restart sshd"name: Setup selinux for alternate SSH port
seport:
ports: "{{ configuredport }}"
proto: "tcp"
setype: "sshport_t"
state: "present"name: Ensure SSH is reloaded if need be
meta: flush_handlersname: Ensure we use the configured SSH port for the remainder of the role
setfact:
ansibleport: "{{ configured_port }}"
```
Analysis of Potential Failures and Bug Reports
Despite the utility of the seport module, certain environments encounter critical failures. A notable instance was reported in Ansible v2.5.8, specifically within AWS scenarios utilizing CentOS 6. In this case, the seport module failed to manage ports even when the required Python libraries were present.
Environment Analysis of the Reported Failure
The failure occurred in an environment with the following characteristics:
- Ansible Version: 2.5.8
- Python Version: 2.7.15rc1
- OS: CentOS 6 (Latest from AWS)
- Dependencies:
policycoreutils-python,libsemanage-python, andlibselinux-pythonwere preinstalled.
The failure manifested during an attempt to enable port 8001 for the http_port_t type. The task was wrapped in a conditional check to ensure SELinux was enabled and not in disabled mode:
yaml
- name: "Enable connections to 8001 port"
seport:
ports: "8001"
proto: "tcp"
setype: "http_port_t"
state: "present"
when: ansible_selinux.status == "enabled" and ansible_selinux.mode != "disabled"
The "Actual Result" was a fatal failure, despite the presence of the necessary libraries. This indicates a potential incompatibility between the seport module in version 2.5.8 and the specific Python/SELinux bindings on CentOS 6. This issue was eventually categorized as a bug and linked to the community.general collection migration, reflecting the evolution of the module from the core Ansible distribution to a community-supported collection.
Deployment Strategies via RHEL System Roles
For enterprise-grade deployments, Red Hat provides the rhel-system-roles.selinux role. This provides a more structured approach to deploying the same SELinux configuration across multiple systems. The use of system roles abstracts the individual seport calls into a broader policy management framework.
Administrative Requirements for SELinux Deployment
The deployment of SELinux configurations via Ansible requires specific prerequisites to ensure the semanage commands can be executed:
- Control Node: Must be properly configured with the RHEL system roles.
- Managed Nodes: Must have the
semanageutility installed. - Permissions: The connecting account must have
sudopermissions, as modifying the SELinux policy is a privileged operation.
Verification of Port Assignments
After applying a port change via seport or a system role, verification is mandatory to ensure the policy was actually committed to the kernel. This is typically done using the shell module to query the current state of the port labels.
For instance, to verify that port 8001 has been assigned to http_port_t, the following command can be executed:
bash
ansible managed-node-01.example.com -m shell -a 'semanage port --list | grep http_port_t'
The expected output would show the port list, including the newly added port:
http_port_t tcp 80, 81, 443, 8001, 488, 8008, 8009, 8443, 9000
Technical Specifications Table
The following table outlines the technical requirements and parameters for the seport module and its surrounding environment.
| Parameter / Requirement | Value / Description | Impact |
|---|---|---|
| Module Name | seport |
Manages SELinux network port labels |
| Required Python Libs | libsemanage-python, libselinux-python |
Essential for the module to communicate with SELinux |
| Common Type Labels | ssh_port_t, http_port_t |
Defines the allowed service for a specific port |
| Default State | present |
Ensures the port is added to the SELinux policy |
| Protocol Options | tcp, udp |
Defines the transport layer for the port label |
| Critical Path | /etc/ssh/sshd_config |
The file that must match the seport configuration |
| Ansible Version Issue | 2.5.8 (Bug #49050) | Reported failures in CentOS 6 AWS environments |
Summary of Operational Impact
The real-world consequence of mismanaging the seport module is service downtime. If a developer updates a configuration file to use a new port but forgets to update the SELinux policy, the application will fail to start. This creates a "silent" failure where the application process may be running, but the network socket is blocked by the kernel.
By implementing the "Deep Drilling" method of port migration—detecting the current port, updating the config, updating the SELinux label, and then updating the Ansible connection facts—engineers create a robust, idempotent pipeline. This removes the need for manual intervention in inventory files and ensures that the system remains secure and reachable throughout the automation process.
Conclusion
The seport module is an indispensable tool for any administrator managing hardened Linux environments. While it serves as a straightforward wrapper for semanage, its integration into a larger automation workflow requires a deep understanding of how SELinux interacts with network sockets and how Ansible handles connection variables. The transition from core modules to community.general collections highlights the ongoing effort to modularize Ansible, but the core functionality of managing setype and ports remains central to system security.
The interdependence between the lineinfile module (for configuration), the seport module (for security policy), and the service module (for applying changes) forms a critical triad in Linux administration. When these three are synchronized via meta: flush_handlers and dynamic set_fact updates, the resulting infrastructure is both flexible and secure. The reported bugs in older versions emphasize the importance of maintaining compatible Python bindings and using modern Ansible collections to ensure stability across diverse distributions like CentOS and RHEL.