From d62db95075dff19556ba4ddf9af9bfb5025391f0 Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Mon, 8 Jun 2026 11:52:20 +0400 Subject: [PATCH 01/12] Make `PermitRootLogin` regexp more generic --- roles/sshd/tasks/restrictions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/sshd/tasks/restrictions.yml b/roles/sshd/tasks/restrictions.yml index 6d14920..dc03159 100644 --- a/roles/sshd/tasks/restrictions.yml +++ b/roles/sshd/tasks/restrictions.yml @@ -2,7 +2,7 @@ - name: "Restrictions | Configure SSH security restrictions" loop: - { regexp: '^#?Protocol\s+', line: 'Protocol 2' } - - { regexp: '^PermitRootLogin yes', line: 'PermitRootLogin no' } + - { regexp: '^#?\s*PermitRootLogin\s+', line: 'PermitRootLogin no' } - { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' } - { regexp: '^#?IgnoreRhosts', line: 'IgnoreRhosts yes' } - { regexp: '^#?DebianBanner\s+', line: 'DebianBanner no' } From 310bda74afdf889c8bda28d38bc657b74914349b Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Mon, 8 Jun 2026 12:19:08 +0400 Subject: [PATCH 02/12] Add more configuration restrictions --- roles/sshd/tasks/restrictions.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/roles/sshd/tasks/restrictions.yml b/roles/sshd/tasks/restrictions.yml index dc03159..c3e978f 100644 --- a/roles/sshd/tasks/restrictions.yml +++ b/roles/sshd/tasks/restrictions.yml @@ -7,6 +7,11 @@ - { regexp: '^#?IgnoreRhosts', line: 'IgnoreRhosts yes' } - { regexp: '^#?DebianBanner\s+', line: 'DebianBanner no' } notify: Restart sshd + - { regexp: '^#?\s*KbdInteractiveAuthentication\s+', line: 'KbdInteractiveAuthentication no' } + - { regexp: '^#?\s*HostbasedAuthentication\s+', line: 'HostbasedAuthentication no' } + - { regexp: '^#?\s*PermitUserEnvironment\s+', line: 'PermitUserEnvironment no' } + - { regexp: '^#?\s*StrictModes\s+', line: 'StrictModes yes' } + - { regexp: '^#?\s*IgnoreUserKnownHosts\s+', line: 'IgnoreUserKnownHosts yes' } ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: "{{ item.regexp }}" From aa24942348beac9163ae4f212917e3bbdaac6628 Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Mon, 8 Jun 2026 14:08:28 +0400 Subject: [PATCH 03/12] Add controls for tunneling and forwarding --- roles/sshd/README.md | 9 ++++++++- roles/sshd/defaults/main.yml | 4 ++++ roles/sshd/tasks/restrictions.yml | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/roles/sshd/README.md b/roles/sshd/README.md index 6702986..545e928 100644 --- a/roles/sshd/README.md +++ b/roles/sshd/README.md @@ -11,7 +11,14 @@ None Role Variables -------------- -None +- `sshd_disable_pam`: whether to disable PAM support. Default: `false` +- `sshd_password_auth`: whether to allow password authentication. Default: `false` +- `sshd_challenge_response_auth`: whether to allow challenge-response authentication. Default: `false` +- `sshd_gss_api_auth`: whether to allow GSSAPI authentication. Default: `false` +- `sshd_allow_agent_forwarding`: whether to allow SSH agent forwarding. Default: `false` +- `sshd_allow_tcp_forwarding`: whether to allow TCP forwarding. Default: `false` +- `sshd_gateway_ports`: whether to allow gateway ports. Default: `false` +- `sshd_permit_tunnel`: whether to allow SSH tunneling. Default: `false` Dependencies ------------ diff --git a/roles/sshd/defaults/main.yml b/roles/sshd/defaults/main.yml index 5354282..4169bdb 100644 --- a/roles/sshd/defaults/main.yml +++ b/roles/sshd/defaults/main.yml @@ -3,3 +3,7 @@ sshd_disable_pam: false sshd_password_auth: false sshd_challenge_response_auth: false sshd_gss_api_auth: false +sshd_allow_agent_forwarding: false +sshd_allow_tcp_forwarding: false +sshd_gateway_ports: false +sshd_permit_tunnel: false diff --git a/roles/sshd/tasks/restrictions.yml b/roles/sshd/tasks/restrictions.yml index c3e978f..7a96479 100644 --- a/roles/sshd/tasks/restrictions.yml +++ b/roles/sshd/tasks/restrictions.yml @@ -10,6 +10,10 @@ - { regexp: '^#?\s*KbdInteractiveAuthentication\s+', line: 'KbdInteractiveAuthentication no' } - { regexp: '^#?\s*HostbasedAuthentication\s+', line: 'HostbasedAuthentication no' } - { regexp: '^#?\s*PermitUserEnvironment\s+', line: 'PermitUserEnvironment no' } + - { regexp: '^#?\s*AllowAgentForwarding\s+', line: 'AllowAgentForwarding {{ sshd_allow_agent_forwarding | ternary("yes", "no") }}' } + - { regexp: '^#?\s*AllowTcpForwarding\s+', line: 'AllowTcpForwarding {{ sshd_allow_tcp_forwarding | ternary("yes", "no") }}' } + - { regexp: '^#?\s*GatewayPorts\s+', line: 'GatewayPorts {{ sshd_gateway_ports | ternary("yes", "no") }}' } + - { regexp: '^#?\s*PermitTunnel\s+', line: 'PermitTunnel {{ sshd_permit_tunnel | ternary("yes", "no") }}' } - { regexp: '^#?\s*StrictModes\s+', line: 'StrictModes yes' } - { regexp: '^#?\s*IgnoreUserKnownHosts\s+', line: 'IgnoreUserKnownHosts yes' } ansible.builtin.lineinfile: From 0c1e8cb7d35b8361913eadf5e253eef611f6774d Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Mon, 8 Jun 2026 14:09:24 +0400 Subject: [PATCH 04/12] Fix incorrect package being installed for RHEL 9+ derivatives --- roles/sshd/tasks/install.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/roles/sshd/tasks/install.yml b/roles/sshd/tasks/install.yml index ff03595..5e86395 100644 --- a/roles/sshd/tasks/install.yml +++ b/roles/sshd/tasks/install.yml @@ -1,11 +1,11 @@ --- -- name: "Install | install OpenSSH (RHEL flavours)" +- name: "Install | install OpenSSH server (RHEL flavours)" when: ansible_os_family == "RHEL" ansible.builtin.dnf: - name: openssh + name: openssh-server state: installed -- name: "Install | Install OpenSSH (Debian flavours)" +- name: "Install | Install OpenSSH server (Debian flavours)" when: ansible_os_family == "Debian" ansible.builtin.apt: name: openssh-server From b87da747a7213960050ab3b78503e86cc5509fdb Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Mon, 8 Jun 2026 14:10:03 +0400 Subject: [PATCH 05/12] Ensure `DebianBanner` is only handled on Debian derivatives --- roles/sshd/tasks/restrictions.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/roles/sshd/tasks/restrictions.yml b/roles/sshd/tasks/restrictions.yml index 7a96479..ce29396 100644 --- a/roles/sshd/tasks/restrictions.yml +++ b/roles/sshd/tasks/restrictions.yml @@ -5,8 +5,6 @@ - { regexp: '^#?\s*PermitRootLogin\s+', line: 'PermitRootLogin no' } - { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' } - { regexp: '^#?IgnoreRhosts', line: 'IgnoreRhosts yes' } - - { regexp: '^#?DebianBanner\s+', line: 'DebianBanner no' } - notify: Restart sshd - { regexp: '^#?\s*KbdInteractiveAuthentication\s+', line: 'KbdInteractiveAuthentication no' } - { regexp: '^#?\s*HostbasedAuthentication\s+', line: 'HostbasedAuthentication no' } - { regexp: '^#?\s*PermitUserEnvironment\s+', line: 'PermitUserEnvironment no' } @@ -16,12 +14,22 @@ - { regexp: '^#?\s*PermitTunnel\s+', line: 'PermitTunnel {{ sshd_permit_tunnel | ternary("yes", "no") }}' } - { regexp: '^#?\s*StrictModes\s+', line: 'StrictModes yes' } - { regexp: '^#?\s*IgnoreUserKnownHosts\s+', line: 'IgnoreUserKnownHosts yes' } + notify: "Restart ssh" ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: "{{ item.regexp }}" line: "{{ item.line }}" validate: /usr/sbin/sshd -t -f %s +- name: "Restrictions | disable Debian banner" + when: "ansible_os_family == 'Debian'" + notify: "Restart ssh" + ansible.builtin.lineinfile: + path: /etc/ssh/sshd_config + regexp: '^#?DebianBanner\s+' + line: "DebianBanner no" + validate: /usr/sbin/sshd -t -f %s + - name: "Restrictions | toggle PAM" notify: Restart sshd ansible.builtin.lineinfile: From 37222f515e2301e8b6bba41af86da49b2ad0f329 Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Mon, 8 Jun 2026 14:14:09 +0400 Subject: [PATCH 06/12] Fix handlers --- roles/sshd/handlers/main.yml | 13 ++++++++++++- roles/sshd/tasks/algorithms.yml | 8 ++++---- roles/sshd/tasks/authentication.yml | 4 ++-- roles/sshd/tasks/encryption.yml | 2 +- roles/sshd/tasks/main.yml | 2 +- roles/sshd/tasks/restrictions.yml | 4 ++-- roles/sshd/tasks/whitelists.yml | 4 ++-- 7 files changed, 24 insertions(+), 13 deletions(-) diff --git a/roles/sshd/handlers/main.yml b/roles/sshd/handlers/main.yml index 9f08055..56ced7d 100644 --- a/roles/sshd/handlers/main.yml +++ b/roles/sshd/handlers/main.yml @@ -1,7 +1,18 @@ --- -- name: "Restart sshd" +- name: "Restart the ssh service" + listen: "Restart ssh" + when: ansible_os_family == 'Debian' ansible.builtin.service: name: ssh state: restarted enabled: true daemon_reload: true + +- name: "Restart the sshd service" + listen: "Restart ssh" + when: ansible_os_family == 'RedHat' + ansible.builtin.service: + name: sshd + state: restarted + enabled: true + daemon_reload: true diff --git a/roles/sshd/tasks/algorithms.yml b/roles/sshd/tasks/algorithms.yml index a43581d..65008c9 100644 --- a/roles/sshd/tasks/algorithms.yml +++ b/roles/sshd/tasks/algorithms.yml @@ -10,7 +10,7 @@ # NOTE: order of preference for openssh-server ed25519 -> rsa - name: "Algorithms | enable ed25519 authentication algorithm" - notify: Restart sshd + notify: "Restart ssh" ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: '^HostKey /etc/ssh/ssh_host_ed25519_key' @@ -18,7 +18,7 @@ validate: sshd -f %s -t - name: "Algorithms | enable the RSA authentication algorithm" - notify: Restart sshd + notify: "Restart ssh" ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: '^HostKey /etc/ssh/ssh_host_rsa_key' @@ -26,7 +26,7 @@ validate: sshd -f %s -t - name: "Algorithms | disable the ECDSA algorithm (deemed to be less safe)" - notify: Restart sshd + notify: "Restart ssh" ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: '^HostKey /etc/ssh/ssh_host_ecdsa_key' @@ -34,7 +34,7 @@ validate: sshd -f %s -t - name: "Algorithms | disable the DSA algorithm (considered to be defunct)" - notify: Restart sshd + notify: "Restart ssh" ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: '^HostKey /etc/ssh/ssh_host_dsa_key' diff --git a/roles/sshd/tasks/authentication.yml b/roles/sshd/tasks/authentication.yml index 8b4e63b..bda73c4 100644 --- a/roles/sshd/tasks/authentication.yml +++ b/roles/sshd/tasks/authentication.yml @@ -1,6 +1,6 @@ --- - name: "Authentication | Configure SSH authentication settings" - notify: Restart sshd + notify: "Restart ssh" loop: - { regexp: '^#?\s*PubkeyAuthentication\s+', line: 'PubkeyAuthentication yes' } - { regexp: '^#?\s*PasswordAuthentication\s+', line: 'PasswordAuthentication {{ sshd_password_auth | ternary("yes", "no") }}' } @@ -24,7 +24,7 @@ - name: "Authentication | override password authentication by cloud-init to '{{ sshd_password_auth | ternary('yes', 'no') }}'" when: sshd_cloud_init.stat.exists - notify: Restart sshd + notify: "Restart ssh" ansible.builtin.lineinfile: path: "/etc/ssh/sshd_config.d/50-cloud-init.conf" regexp: '^#?PasswordAuthentication' diff --git a/roles/sshd/tasks/encryption.yml b/roles/sshd/tasks/encryption.yml index 660a369..d000a35 100644 --- a/roles/sshd/tasks/encryption.yml +++ b/roles/sshd/tasks/encryption.yml @@ -5,7 +5,7 @@ - /etc/ssh/ssh_host_ecdsa_key.pub - /etc/ssh/ssh_host_dsa_key - /etc/ssh/ssh_host_dsa_key.pub - notify: Restart sshd + notify: "Restart ssh" ansible.builtin.file: path: "{{ item }}" state: absent diff --git a/roles/sshd/tasks/main.yml b/roles/sshd/tasks/main.yml index f567900..2af59f3 100644 --- a/roles/sshd/tasks/main.yml +++ b/roles/sshd/tasks/main.yml @@ -18,7 +18,7 @@ ansible.builtin.include_tasks: "whitelists.yml" - name: "Log at VERBOSE level" - notify: Restart sshd + notify: "Restart ssh" ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: '^#?LogLevel' diff --git a/roles/sshd/tasks/restrictions.yml b/roles/sshd/tasks/restrictions.yml index ce29396..86e3703 100644 --- a/roles/sshd/tasks/restrictions.yml +++ b/roles/sshd/tasks/restrictions.yml @@ -31,7 +31,7 @@ validate: /usr/sbin/sshd -t -f %s - name: "Restrictions | toggle PAM" - notify: Restart sshd + notify: "Restart ssh" ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: '^#?UsePAM' @@ -39,7 +39,7 @@ validate: sshd -f %s -t - name: "Restrictions | ensure the SSHD config is restricted to the root user" - notify: Restart sshd + notify: "Restart ssh" ansible.builtin.file: path: /etc/ssh/sshd_config owner: root diff --git a/roles/sshd/tasks/whitelists.yml b/roles/sshd/tasks/whitelists.yml index 3952302..9a3d410 100644 --- a/roles/sshd/tasks/whitelists.yml +++ b/roles/sshd/tasks/whitelists.yml @@ -1,7 +1,7 @@ --- - name: "Configure AllowUsers" when: sshd_allow_users is defined - notify: Restart sshd + notify: "Restart ssh" ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: '^#?\s*AllowUsers\s+' @@ -10,7 +10,7 @@ - name: "Configure AllowGroups" when: sshd_allow_groups is defined - notify: Restart sshd + notify: "Restart ssh" ansible.builtin.lineinfile: path: /etc/ssh/sshd_config regexp: '^#?\s*AllowGroups\s+' From b246eae0171d61c74164d55148c7bd7f8f6d954c Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Mon, 8 Jun 2026 14:15:09 +0400 Subject: [PATCH 07/12] Use full path in config validation consistently --- roles/sshd/tasks/algorithms.yml | 8 ++++---- roles/sshd/tasks/main.yml | 2 +- roles/sshd/tasks/restrictions.yml | 2 +- roles/sshd/tasks/whitelists.yml | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/roles/sshd/tasks/algorithms.yml b/roles/sshd/tasks/algorithms.yml index 65008c9..4de445f 100644 --- a/roles/sshd/tasks/algorithms.yml +++ b/roles/sshd/tasks/algorithms.yml @@ -15,7 +15,7 @@ path: /etc/ssh/sshd_config regexp: '^HostKey /etc/ssh/ssh_host_ed25519_key' line: 'HostKey /etc/ssh/ssh_host_ed25519_key' - validate: sshd -f %s -t + validate: /usr/sbin/sshd -t -f %s - name: "Algorithms | enable the RSA authentication algorithm" notify: "Restart ssh" @@ -23,7 +23,7 @@ path: /etc/ssh/sshd_config regexp: '^HostKey /etc/ssh/ssh_host_rsa_key' line: 'HostKey /etc/ssh/ssh_host_rsa_key' - validate: sshd -f %s -t + validate: /usr/sbin/sshd -t -f %s - name: "Algorithms | disable the ECDSA algorithm (deemed to be less safe)" notify: "Restart ssh" @@ -31,7 +31,7 @@ path: /etc/ssh/sshd_config regexp: '^HostKey /etc/ssh/ssh_host_ecdsa_key' state: absent - validate: sshd -f %s -t + validate: /usr/sbin/sshd -t -f %s - name: "Algorithms | disable the DSA algorithm (considered to be defunct)" notify: "Restart ssh" @@ -39,4 +39,4 @@ path: /etc/ssh/sshd_config regexp: '^HostKey /etc/ssh/ssh_host_dsa_key' state: absent - validate: sshd -f %s -t + validate: /usr/sbin/sshd -t -f %s diff --git a/roles/sshd/tasks/main.yml b/roles/sshd/tasks/main.yml index 2af59f3..a24c5cb 100644 --- a/roles/sshd/tasks/main.yml +++ b/roles/sshd/tasks/main.yml @@ -23,4 +23,4 @@ path: /etc/ssh/sshd_config regexp: '^#?LogLevel' line: 'LogLevel VERBOSE' - validate: sshd -f %s -t + validate: /usr/sbin/sshd -t -f %s diff --git a/roles/sshd/tasks/restrictions.yml b/roles/sshd/tasks/restrictions.yml index 86e3703..1d6f0c2 100644 --- a/roles/sshd/tasks/restrictions.yml +++ b/roles/sshd/tasks/restrictions.yml @@ -36,7 +36,7 @@ path: /etc/ssh/sshd_config regexp: '^#?UsePAM' line: "UsePAM {{ sshd_disable_pam | ternary('no', 'yes') }}" - validate: sshd -f %s -t + validate: /usr/sbin/sshd -t -f %s - name: "Restrictions | ensure the SSHD config is restricted to the root user" notify: "Restart ssh" diff --git a/roles/sshd/tasks/whitelists.yml b/roles/sshd/tasks/whitelists.yml index 9a3d410..03f0136 100644 --- a/roles/sshd/tasks/whitelists.yml +++ b/roles/sshd/tasks/whitelists.yml @@ -6,7 +6,7 @@ path: /etc/ssh/sshd_config regexp: '^#?\s*AllowUsers\s+' line: "AllowUsers {{ sshd_allow_users }}" - validate: sshd -f %s -t + validate: /usr/sbin/sshd -t -f %s - name: "Configure AllowGroups" when: sshd_allow_groups is defined @@ -15,4 +15,4 @@ path: /etc/ssh/sshd_config regexp: '^#?\s*AllowGroups\s+' line: "AllowGroups {{ sshd_allow_groups }}" - validate: sshd -f %s -t + validate: /usr/sbin/sshd -t -f %s From 94f363209c9ef0976375a4157e52240a981e7fd5 Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Mon, 8 Jun 2026 14:17:10 +0400 Subject: [PATCH 08/12] Restore task naming consistency --- roles/sshd/tasks/algorithms.yml | 2 +- roles/sshd/tasks/authentication.yml | 4 ++-- roles/sshd/tasks/install.yml | 2 +- roles/sshd/tasks/restrictions.yml | 2 +- roles/sshd/tasks/whitelists.yml | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/roles/sshd/tasks/algorithms.yml b/roles/sshd/tasks/algorithms.yml index 4de445f..46c52da 100644 --- a/roles/sshd/tasks/algorithms.yml +++ b/roles/sshd/tasks/algorithms.yml @@ -1,6 +1,6 @@ --- # next task requires this directory to exist for sshd -t flag -- name: Ensure /run/sshd exists +- name: "Algorithms | Ensure /run/sshd exists" ansible.builtin.file: path: /run/sshd state: directory diff --git a/roles/sshd/tasks/authentication.yml b/roles/sshd/tasks/authentication.yml index bda73c4..192a53f 100644 --- a/roles/sshd/tasks/authentication.yml +++ b/roles/sshd/tasks/authentication.yml @@ -1,5 +1,5 @@ --- -- name: "Authentication | Configure SSH authentication settings" +- name: "Authentication | configure SSH authentication settings" notify: "Restart ssh" loop: - { regexp: '^#?\s*PubkeyAuthentication\s+', line: 'PubkeyAuthentication yes' } @@ -17,7 +17,7 @@ line: "{{ item.line }}" validate: /usr/sbin/sshd -t -f %s -- name: "Check if there is an SSH config forced by cloud-init" +- name: "Authentication | check if there is an SSH config forced by cloud-init" register: sshd_cloud_init ansible.builtin.stat: path: "/etc/ssh/sshd_config.d/50-cloud-init.conf" diff --git a/roles/sshd/tasks/install.yml b/roles/sshd/tasks/install.yml index 5e86395..5d32274 100644 --- a/roles/sshd/tasks/install.yml +++ b/roles/sshd/tasks/install.yml @@ -5,7 +5,7 @@ name: openssh-server state: installed -- name: "Install | Install OpenSSH server (Debian flavours)" +- name: "Install | install OpenSSH server (Debian flavours)" when: ansible_os_family == "Debian" ansible.builtin.apt: name: openssh-server diff --git a/roles/sshd/tasks/restrictions.yml b/roles/sshd/tasks/restrictions.yml index 1d6f0c2..5186ba8 100644 --- a/roles/sshd/tasks/restrictions.yml +++ b/roles/sshd/tasks/restrictions.yml @@ -1,5 +1,5 @@ --- -- name: "Restrictions | Configure SSH security restrictions" +- name: "Restrictions | configure SSH security restrictions" loop: - { regexp: '^#?Protocol\s+', line: 'Protocol 2' } - { regexp: '^#?\s*PermitRootLogin\s+', line: 'PermitRootLogin no' } diff --git a/roles/sshd/tasks/whitelists.yml b/roles/sshd/tasks/whitelists.yml index 03f0136..47e83d4 100644 --- a/roles/sshd/tasks/whitelists.yml +++ b/roles/sshd/tasks/whitelists.yml @@ -1,5 +1,5 @@ --- -- name: "Configure AllowUsers" +- name: "Whitelists | configure AllowUsers" when: sshd_allow_users is defined notify: "Restart ssh" ansible.builtin.lineinfile: @@ -8,7 +8,7 @@ line: "AllowUsers {{ sshd_allow_users }}" validate: /usr/sbin/sshd -t -f %s -- name: "Configure AllowGroups" +- name: "Whitelists | configure AllowGroups" when: sshd_allow_groups is defined notify: "Restart ssh" ansible.builtin.lineinfile: From e260c8ac4c5f70017d93fac9bc60ee6268e11e3b Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Mon, 8 Jun 2026 16:02:30 +0400 Subject: [PATCH 09/12] Shuffle configuration more logically between tasks --- roles/sshd/tasks/authentication.yml | 3 +++ roles/sshd/tasks/restrictions.yml | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/roles/sshd/tasks/authentication.yml b/roles/sshd/tasks/authentication.yml index 192a53f..7bda62e 100644 --- a/roles/sshd/tasks/authentication.yml +++ b/roles/sshd/tasks/authentication.yml @@ -7,6 +7,9 @@ - { regexp: '^#?\s*PermitEmptyPasswords\s+', line: 'PermitEmptyPasswords no' } - { regexp: '^#?\s*ChallengeResponseAuthentication\s+', line: 'ChallengeResponseAuthentication {{ sshd_challenge_response_auth | ternary("yes", "no") }}' } - { regexp: '^#?\s*GSSAPIAuthentication\s+', line: 'GSSAPIAuthentication {{ sshd_gss_api_auth | ternary("yes", "no") }}' } + - { regexp: '^#?IgnoreRhosts', line: 'IgnoreRhosts yes' } + - { regexp: '^#?\s*KbdInteractiveAuthentication\s+', line: 'KbdInteractiveAuthentication no' } + - { regexp: '^#?\s*HostbasedAuthentication\s+', line: 'HostbasedAuthentication no' } - { regexp: '^#?\s*AuthenticationMethods\s+', line: "{{ 'AuthenticationMethods publickey password' if sshd_password_auth else 'AuthenticationMethods publickey' }}" diff --git a/roles/sshd/tasks/restrictions.yml b/roles/sshd/tasks/restrictions.yml index 5186ba8..655b77b 100644 --- a/roles/sshd/tasks/restrictions.yml +++ b/roles/sshd/tasks/restrictions.yml @@ -4,9 +4,6 @@ - { regexp: '^#?Protocol\s+', line: 'Protocol 2' } - { regexp: '^#?\s*PermitRootLogin\s+', line: 'PermitRootLogin no' } - { regexp: '^#?X11Forwarding', line: 'X11Forwarding no' } - - { regexp: '^#?IgnoreRhosts', line: 'IgnoreRhosts yes' } - - { regexp: '^#?\s*KbdInteractiveAuthentication\s+', line: 'KbdInteractiveAuthentication no' } - - { regexp: '^#?\s*HostbasedAuthentication\s+', line: 'HostbasedAuthentication no' } - { regexp: '^#?\s*PermitUserEnvironment\s+', line: 'PermitUserEnvironment no' } - { regexp: '^#?\s*AllowAgentForwarding\s+', line: 'AllowAgentForwarding {{ sshd_allow_agent_forwarding | ternary("yes", "no") }}' } - { regexp: '^#?\s*AllowTcpForwarding\s+', line: 'AllowTcpForwarding {{ sshd_allow_tcp_forwarding | ternary("yes", "no") }}' } From 54c19551f36bbf0428f2421320507e857949578b Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Mon, 8 Jun 2026 16:02:47 +0400 Subject: [PATCH 10/12] Fix a tiny stylistic discrepancy --- roles/sshd/tasks/algorithms.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/sshd/tasks/algorithms.yml b/roles/sshd/tasks/algorithms.yml index 46c52da..1e5faae 100644 --- a/roles/sshd/tasks/algorithms.yml +++ b/roles/sshd/tasks/algorithms.yml @@ -1,6 +1,6 @@ --- # next task requires this directory to exist for sshd -t flag -- name: "Algorithms | Ensure /run/sshd exists" +- name: "Algorithms | ensure /run/sshd exists" ansible.builtin.file: path: /run/sshd state: directory From 06e1f73b079ef5a9db9d6cf423b038d9f2ec77b5 Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Mon, 8 Jun 2026 16:23:15 +0400 Subject: [PATCH 11/12] Update `converge.yml` --- roles/sshd/molecule/default/converge.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/roles/sshd/molecule/default/converge.yml b/roles/sshd/molecule/default/converge.yml index bb22a32..be83814 100644 --- a/roles/sshd/molecule/default/converge.yml +++ b/roles/sshd/molecule/default/converge.yml @@ -3,4 +3,9 @@ hosts: all roles: - role: "genlab.common.sshd" + sshd_password_auth: false + sshd_allow_agent_forwarding: false + sshd_allow_tcp_forwarding: false + sshd_gateway_ports: false + sshd_permit_tunnel: false sshd_allow_users: "testusr" From e4b85fa2c9e87c6ef7ab545eecb292fdee0e5514 Mon Sep 17 00:00:00 2001 From: Alexander Gorelyshev Date: Mon, 8 Jun 2026 16:23:29 +0400 Subject: [PATCH 12/12] Rework `README.md` --- roles/sshd/README.md | 67 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/roles/sshd/README.md b/roles/sshd/README.md index 545e928..26886ff 100644 --- a/roles/sshd/README.md +++ b/roles/sshd/README.md @@ -1,35 +1,76 @@ sshd ==== -Deploy a hardened `sshd` server +A hardened OpenSSH server role for Debian/Ubuntu and RHEL/Rocky systems. + +This role installs and configures the `sshd` server with secure defaults and a small set of tunable options for authentication and forwarding. Requirements ------------ -None +- Target hosts must be Debian-family or RedHat-family Linux systems + +Supported Platforms +------------------- + +- Debian +- Ubuntu +- RHEL 9+ +- Rocky Linux 9+ Role Variables -------------- -- `sshd_disable_pam`: whether to disable PAM support. Default: `false` -- `sshd_password_auth`: whether to allow password authentication. Default: `false` -- `sshd_challenge_response_auth`: whether to allow challenge-response authentication. Default: `false` -- `sshd_gss_api_auth`: whether to allow GSSAPI authentication. Default: `false` -- `sshd_allow_agent_forwarding`: whether to allow SSH agent forwarding. Default: `false` -- `sshd_allow_tcp_forwarding`: whether to allow TCP forwarding. Default: `false` -- `sshd_gateway_ports`: whether to allow gateway ports. Default: `false` -- `sshd_permit_tunnel`: whether to allow SSH tunneling. Default: `false` +Authentication -Dependencies ------------- +- `sshd_disable_pam`: disable PAM support in `sshd_config`. Default: `false` +- `sshd_password_auth`: allow password authentication. Default: `false` +- `sshd_challenge_response_auth`: allow challenge-response authentication. Default: `false` +- `sshd_gss_api_auth`: allow GSSAPI authentication. Default: `false` -None +Forwarding & tunneling + +- `sshd_allow_agent_forwarding`: allow SSH agent forwarding. Default: `false` +- `sshd_allow_tcp_forwarding`: allow TCP forwarding. Default: `false` +- `sshd_gateway_ports`: allow gateway ports. Default: `false` +- `sshd_permit_tunnel`: allow SSH tunneling. Default: `false` + +Access control + +- `sshd_allow_users`: optional space-separated list of users permitted to log in via SSH +- `sshd_allow_groups`: optional space-separated list of groups permitted to log in via SSH + +Behavior +-------- + +By default this role: + +- installs `openssh-server` +- enforces `Protocol 2` +- disables `PermitRootLogin` +- disables `X11Forwarding`, `HostbasedAuthentication`, `KbdInteractiveAuthentication`, and `PermitUserEnvironment` +- disables weak(-er) host keys (`ecdsa`, `dsa`) +- disables empty passwords +- sets `LogLevel VERBOSE` +- restricts `/etc/ssh/sshd_config` to `0600` + +Compatibility Notes +------------------- + +- The role uses `sshd` validation via `/usr/sbin/sshd -t -f %s`. +- Debian/Ubuntu systems use the `ssh` service name; RHEL/Rocky systems use `sshd`. +- The role is intentionally conservative with forwarding and tunneling defaults. Example Playbook ---------------- See: [converge.yml](molecule/default/converge.yml) +Dependencies +------------ + +None + License -------