Advanced Orchestration of SELinux Port Management via Ansible seport

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, typically tcp or udp.
  • setype: This is the SELinux type label that must be assigned to the port (e.g., http_port_t for web servers).
  • state: This ensures the desired state of the port label, usually present to add the port or absent to 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:

  1. Verification of Default Port: The playbook first checks if port 22 is open using wait_for delegated to localhost. If the port is reachable, the ansible_port is set to 22.
  2. 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.
  3. 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 lineinfile module is used to modify /etc/ssh/sshd_config, replacing the Port directive with the new configured_port.
  • SELinux Labeling: The seport module is invoked to assign the ssh_port_t label to the new port.
  • Service Restart: A handler is triggered to restart sshd.
  • State Synchronization: The meta: flush_handlers task is called to ensure the restart happens immediately.
  • Fact Update: The set_fact module updates ansible_port so 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:
configured
port: "{{ ansible_port }}"

  • name: Check if we are using the default SSH port
    waitfor:
    port: "22"
    state: "started"
    host: "{{ inventory
    hostname }}"
    connecttimeout: "5"
    timeout: "10"
    delegate
    to: "localhost"
    ignoreerrors: "yes"
    register: default
    ssh

  • name: Set inventory ansibleport to default
    set
    fact:
    ansibleport: "22"
    when: default
    ssh is defined and defaultssh.state == "started"
    register: ssh
    port_set

  • name: Check if we are using the inventory-provided SSH port
    waitfor:
    port: "{{ ansible
    port }}"
    state: "started"
    host: "{{ inventoryhostname }}"
    connect
    timeout: "5"
    timeout: "10"
    delegateto: "localhost"
    ignore
    errors: "yes"
    register: configuredssh
    when: default
    ssh is defined and default_ssh.state is undefined

  • name: Setup alternate SSH port
    lineinfile:
    dest: "/etc/ssh/sshdconfig"
    regexp: "^Port"
    line: "Port {{ configured
    port }}"
    notify: "Restart sshd"

  • name: Setup selinux for alternate SSH port
    seport:
    ports: "{{ configuredport }}"
    proto: "tcp"
    setype: "ssh
    port_t"
    state: "present"

  • name: Ensure SSH is reloaded if need be
    meta: flush_handlers

  • name: Ensure we use the configured SSH port for the remainder of the role
    setfact:
    ansible
    port: "{{ 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, and libselinux-python were 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 semanage utility installed.
  • Permissions: The connecting account must have sudo permissions, 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.

Sources

  1. Ansible GitHub Issue #49050
  2. Changing the SSH Port with Ansible - David Moreau Simard
  3. Red Hat Enterprise Linux 9: Deploying SELinux Configuration

Related Posts