Architecting Secure Remote Access: An Exhaustive Guide to Ansible SSHD Management

The orchestration of Secure Shell (SSH) configurations across a distributed infrastructure is a critical pillar of system hardening and identity management. When managing a fleet of servers, manual configuration of the sshd_config file is not only inefficient but introduces significant risks of configuration drift and human error. By leveraging Ansible, administrators can transform the static process of SSH configuration into a dynamic, version-controlled, and repeatable workflow. This process involves the use of specialized RHEL System Roles, community-driven roles, and custom Jinja2 templating to ensure that every managed node adheres to a strict security baseline. The goal of automating sshd is to move beyond simple connectivity and toward a state of "Infrastructure as Code" (IaC), where the desired state of the SSH daemon is declared in YAML and enforced across diverse operating systems, from RHEL and Ubuntu to FreeBSD and AIX.

The RHEL System Role for SSHD Configuration

Red Hat provides a specialized sshd System Role designed to configure SSH servers consistently across multiple RHEL systems. This role allows administrators to define parameters for the SSH configuration file based on specific organizational preferences and security limitations. When the role is invoked, it ensures that the resulting sshd_config file is consistent across the environment, reducing the attack surface by eliminating non-standard or insecure configurations.

The role operates on a set of predefined variables that control the behavior of the SSH daemon. If these variables are not explicitly configured, the System Role reverts to RHEL defaults, ensuring that the system remains functional while maintaining a baseline level of security. A key technical detail of this role is its handling of Boolean values; any Boolean variable defined in the Ansible playbook is correctly rendered as yes or no within the sshd_config file, adhering to the strict syntax required by the OpenSSH daemon.

Furthermore, the role supports multi-line configuration items through the use of lists. For instance, if an administrator needs the server to listen on both IPv4 and IPv6 addresses, the variable sshd_ListenAddress can be defined as a list:

yaml sshd_ListenAddress: - 0.0.0.0 - '::'

This technical implementation renders as two separate directives in the configuration file:

text ListenAddress 0.0.0.0 ListenAddress ::

This capability is vital for complex networking environments where a server may have multiple network interfaces and must be reachable across different protocols.

Critical Variables and Operational Logic in RHEL System Roles

The efficiency of the sshd System Role is governed by several high-level control variables that dictate how the role interacts with the managed node.

The sshd_enable variable serves as the master switch for the role. When set to False, the role is completely disabled, and no changes are applied to the system. It defaults to True, ensuring that the role is active by default upon inclusion in a playbook.

The sshd_skip_defaults variable provides a mechanism for advanced administrators to take total control of the configuration. By default, this is set to False, meaning the System Role will apply standard RHEL default values. However, when set to True, the role ceases to apply these defaults. In this mode, the administrator must specify the complete set of configuration defaults using either the sshd dictionary or sshd_Key variables. This is particularly useful for organizations with highly specific security mandates that conflict with standard vendor defaults.

The sshd_manage_service variable controls the lifecycle of the sshd process. When set to True (the default), Ansible ensures the service is enabled on boot and is started or reloaded as needed. However, there are technical exceptions: when running inside a container or on AIX systems, the Ansible service module does not support the enabled parameter, and thus the default behavior is modified to reflect these platform limitations.

For RHEL-based systems, the sshd_sysconfig_use_strong_rng variable allows the administrator to force sshd to reseed the OpenSSL random number generator. This is achieved by providing a specific number of bytes as an argument. The default value is 0, which disables the functionality. From a technical standpoint, this should only be enabled if the system possesses a hardware random number generator; failing to have a hardware RNG while enabling this feature can lead to entropy exhaustion and potential system instability.

Advanced Implementation Strategies for SSH Hardening

Hardening the SSH daemon is a necessity for any server exposed to the public internet. The internet is extensively mapped by tools like Shodan, and both IPv4 and IPv6 address spaces are constantly scanned for open port 22. To mitigate these risks, several strategies can be implemented via Ansible.

One of the most effective methods to deter "script kiddies" and automated scanners is changing the default TCP port from 22 to a non-standard port. While this is not a substitute for strong authentication, it significantly reduces the volume of noise in the logs. This can be achieved using the lineinfile module in Ansible:

yaml - name: Setup alternate SSHd port lineinfile: dest: /etc/ssh/sshd_config regexp: '^#Port' line: 'Port 4242'

When changing the port, the administrator must also update the Ansible inventory to reflect this change by setting the ansible_port variable for the respective hosts. To further enhance security, the installation of fail2ban is recommended to protect against brute force and dictionary attacks by automatically banning IP addresses that exhibit malicious behavior.

Customized Templating and Management of Authorized Keys

For environments requiring granular control, using Jinja2 templates is the preferred method for managing sshd_config. This approach allows for the dynamic insertion of variables and ensures that the configuration file is treated as a single, atomic unit of truth.

A professional implementation of a sshd_config.j2 template often includes management statements and revision tags to track changes:

```text

$Id$

{{ ansible_managed }}

ListenAddress {{ ansiblesshhost }}
PermitRootLogin without-password
AuthorizedKeysFile /etc/ssh/authorized_keys/%u
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
Subsystem sftp /usr/libexec/sftp-server
```

In this template, {{ ansible_ssh_host }} is a variable pulled from the Ansible inventory, allowing each host to have a unique ListenAddress. The use of PermitRootLogin without-password ensures that root login is only possible via key-based authentication, effectively disabling password-based root access.

The management of authorized_keys is another critical security component. Rather than allowing users to upload their own keys—which could allow an intruder to persist in a compromised account—administrators can centralize key management. This is done by creating a directory structure such as /etc/ssh/authorized_keys/%u and pushing keys from the control node.

A sample playbook for this process is as follows:

yaml - hosts: freebsd user: ansible sudo: yes tasks: - name: create key directory file: path=/etc/ssh/authorized_keys state=directory owner=0 group=0 mode=0755 - name: upload user key copy: src=/home/ansible/crossplatform/etc/ssh/authorized_keys/{{ item }} dest=/etc/ssh/authorized_keys/ owner=0 group=0 mode=644 with_items: sshusers - name: sshd configuration file update template: src=/etc/ansible/configs/etc/ssh/sshd_config.j2 dest=/etc/ssh/sshd_config backup=yes owner=0 group=0 mode=0644 validate='/usr/sbin/sshd -T -f %s' notify: - restart sshd handlers: - name: restart sshd service: name=sshd state=restarted

This workflow ensures that only users listed in the sshusers variable (defined in group_vars files like /etc/ansible/group_vars/dns or /etc/ansible/group_vars/ldap) are granted access. This creates a strict mapping between user identity and server function.

Cross-Platform Compatibility and Deployment Requirements

The ansible-sshd role and RHEL System Roles are designed for broad compatibility across various Unix-like environments. The supported platforms include:

  • Ubuntu: precise, trusty, xenial, bionic, focal, jammy, noble
  • Debian: 11 (Bullseye), 12 (Bookbook), 13 (Trixie)
  • Enterprise Linux (EL): 6, 7, 8, 9, 10 derived distributions
  • Fedora: All versions
  • Alpine Linux: Latest versions
  • FreeBSD: 10.1
  • OpenBSD: 6.0
  • AIX: 7.1, 7.2
  • OpenWrt: 21.03
  • NetBSD: 10.1

For those utilizing the role on systems with advanced requirements, such as rpm-ostree or those needing automatic firewall and SELinux configuration (essential when using custom ports), additional collections must be installed. These are specified in meta/collection-requirements.yml and can be installed via the following command:

bash ansible-galaxy install -vv -r meta/collection-requirements.yml

Integration with Other System Roles and Handler Logic

The sshd System Role is often used in conjunction with other roles, such as the Identity Management (IdM) RHEL System Roles. To prevent configuration conflicts where different roles attempt to modify the same file, administrators must use specific strategies based on the RHEL version. For RHEL 8 and earlier, namespaces should be employed. For RHEL 9, the use of a drop-in directory is required to ensure that configuration fragments do not overwrite one another.

A critical operational detail regarding the sshd service is the triggering of handlers. If the role is invoked using the roles keyword, the sshd service is automatically reloaded or restarted. However, if include_role is used, handlers are not triggered due to the Ansible "taskify includes" proposal. To resolve this and ensure that configuration changes take effect immediately, administrators must explicitly call the meta: flush_handlers task.

Prerequisites for Control and Managed Nodes

To successfully deploy the sshd configuration via Ansible, the following infrastructure requirements must be met:

On the Control Node:
- Installation of the ansible-core package.
- Installation of the rhel-system-roles package.
- For versions RHEL 8.0-8.5, access to the separate Ansible repository containing Ansible Engine 2.9, which provides the ansible and ansible-playbook command-line utilities, as well as connectors for docker and podman.

On the Managed Nodes:
- Appropriate access and permissions allowing the control node to execute configuration changes.
- A compatible operating system as listed in the platform support section.

Technical Specifications Summary

The following table summarizes the key operational parameters and their default behaviors within the RHEL System Role ecosystem.

Variable Default Value Impact of Change
sshd_enable True If False, the role is completely inactive.
sshd_skip_defaults False If True, the user must provide all configuration defaults.
sshd_manage_service True If False, the service is not enabled or started via Ansible.
sshd_sysconfig_use_strong_rng 0 If set to a value, forces OpenSSL RNG reseeding.
sshd_ListenAddress RHEL Default Configures the IP address(es) the daemon binds to.

Conclusion

The automation of SSH configuration through Ansible represents a shift from reactive system administration to proactive security engineering. By utilizing the RHEL System Roles, administrators can ensure that every node in their infrastructure adheres to a known, tested, and secure state. The ability to implement non-standard ports, centralized key management, and platform-specific optimizations—such as the sshd_sysconfig_use_strong_rng for RHEL—allows for a defense-in-depth strategy. The integration of Jinja2 templates and the use of group_vars enables a scalable identity management system where access is granted based on functional groups rather than individual manual additions. Ultimately, the use of meta: flush_handlers and the strict adherence to sshd_config syntax through Ansible's Boolean rendering ensures that security policies are not just defined, but actively enforced across diverse environments ranging from legacy AIX systems to modern Ubuntu Noble releases.

Sources

  1. Configuring secure communication by using the ssh and sshd RHEL System Roles
  2. mwl.io - SSH Configuration with Ansible
  3. Hardening Your SSHd with Ansible
  4. ansible-sshd GitHub Repository

Related Posts