From cefe2f7cfa84655f279f1541b2607ae6474b8375 Mon Sep 17 00:00:00 2001 From: reachableceo Date: Thu, 30 Jul 2026 12:16:13 -0500 Subject: [PATCH] feat(security): add SSH hardening role MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port Modules/Security/secharden-ssh.sh. Deploys: - Authorized keys for root, localuser, subodev (conditional on existence) - Hardened /etc/ssh/sshd_config (skipped on dev workstations) - ssh-audit hardening drop-in for algorithm restrictions (skipped on Ubuntu where it breaks openssh-server) - Lockdown of sshd_config.d directory permissions SSH service is validated (sshd -t) before restart. 🤖 Generated with [Crush](https://github.com/charmassociates/crush) Assisted-by: GLM-5 via Crush --- .../files/authorized_keys/localuser | 2 + roles/security_ssh/files/authorized_keys/root | 2 + .../files/configs/ssh-audit-hardening.conf | 19 ++++ roles/security_ssh/files/configs/sshd_config | 20 ++++ roles/security_ssh/handlers/main.yml | 10 ++ roles/security_ssh/tasks/main.yml | 91 +++++++++++++++++++ 6 files changed, 144 insertions(+) create mode 100644 roles/security_ssh/files/authorized_keys/localuser create mode 100644 roles/security_ssh/files/authorized_keys/root create mode 100644 roles/security_ssh/files/configs/ssh-audit-hardening.conf create mode 100644 roles/security_ssh/files/configs/sshd_config create mode 100644 roles/security_ssh/handlers/main.yml create mode 100644 roles/security_ssh/tasks/main.yml diff --git a/roles/security_ssh/files/authorized_keys/localuser b/roles/security_ssh/files/authorized_keys/localuser new file mode 100644 index 0000000..36216a1 --- /dev/null +++ b/roles/security_ssh/files/authorized_keys/localuser @@ -0,0 +1,2 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDHaBNuLS+GYGRPc9wne63Ocr+R+/Q01Y9V0FTv0RnG3 +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPyMR0lFgiMKhQJ5aqy68nR0BQp1cNzi/wIThyuTV4a8 tsyscto@ultix-control diff --git a/roles/security_ssh/files/authorized_keys/root b/roles/security_ssh/files/authorized_keys/root new file mode 100644 index 0000000..36216a1 --- /dev/null +++ b/roles/security_ssh/files/authorized_keys/root @@ -0,0 +1,2 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDHaBNuLS+GYGRPc9wne63Ocr+R+/Q01Y9V0FTv0RnG3 +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPyMR0lFgiMKhQJ5aqy68nR0BQp1cNzi/wIThyuTV4a8 tsyscto@ultix-control diff --git a/roles/security_ssh/files/configs/ssh-audit-hardening.conf b/roles/security_ssh/files/configs/ssh-audit-hardening.conf new file mode 100644 index 0000000..8e80118 --- /dev/null +++ b/roles/security_ssh/files/configs/ssh-audit-hardening.conf @@ -0,0 +1,19 @@ +# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com +# hardening guide. +KexAlgorithms sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,gss-curve25519-sha256-,diffie-hellman-group16-sha512,gss-group16-sha512-,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 + +Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr + +MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com + +HostKeyAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256 + +RequiredRSASize 3072 + +CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256 + +GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512- + +HostbasedAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256 + +PubkeyAcceptedAlgorithms sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-256 diff --git a/roles/security_ssh/files/configs/sshd_config b/roles/security_ssh/files/configs/sshd_config new file mode 100644 index 0000000..fca1eb0 --- /dev/null +++ b/roles/security_ssh/files/configs/sshd_config @@ -0,0 +1,20 @@ +Include /etc/ssh/sshd_config.d/*.conf +HostKey /etc/ssh/ssh_host_rsa_key +HostKey /etc/ssh/ssh_host_ed25519_key +KbdInteractiveAuthentication no +PrintMotd no +PasswordAuthentication no +AllowTcpForwarding no +X11Forwarding no +ChallengeResponseAuthentication no +AcceptEnv LANG LC_* +Subsystem sftp /usr/lib/openssh/sftp-server +UsePAM yes +Banner /etc/issue.net +MaxAuthTries 2 +MaxStartups 10:30:100 +PermitRootLogin prohibit-password +ClientAliveInterval 300 +ClientAliveCountMax 3 +AllowUsers root localuser subodev +LoginGraceTime 60 diff --git a/roles/security_ssh/handlers/main.yml b/roles/security_ssh/handlers/main.yml new file mode 100644 index 0000000..ed44c05 --- /dev/null +++ b/roles/security_ssh/handlers/main.yml @@ -0,0 +1,10 @@ +--- +- name: Validate and restart sshd + ansible.builtin.command: sshd -t + changed_when: false + notify: Restart sshd + +- name: Restart sshd + ansible.builtin.systemd: + name: sshd + state: restarted diff --git a/roles/security_ssh/tasks/main.yml b/roles/security_ssh/tasks/main.yml new file mode 100644 index 0000000..f43a16d --- /dev/null +++ b/roles/security_ssh/tasks/main.yml @@ -0,0 +1,91 @@ +--- +# SSH hardening — port of Modules/Security/secharden-ssh.sh. + +# Per-user authorized_keys. The legacy script wrote localuser's keys into both +# localuser and subodev; replicate that mapping here. Deployed conditionally +# based on whether each user actually exists (legacy LOCALUSER_CHECK / SUBODEV_CHECK). +- name: Ensure root .ssh directory exists + ansible.builtin.file: + path: /root/.ssh + state: directory + owner: root + group: root + mode: "0700" + +- name: Ensure localuser .ssh directory exists (if user exists) + ansible.builtin.file: + path: /home/localuser/.ssh + state: directory + owner: localuser + group: localuser + mode: "0700" + when: localuser_exists | bool + +- name: Ensure subodev .ssh directory exists (if user exists) + ansible.builtin.file: + path: /home/subodev/.ssh + state: directory + owner: subodev + group: subodev + mode: "0700" + when: subodev_exists | bool + +- name: Deploy root authorized_keys + ansible.builtin.copy: + src: authorized_keys/root + dest: /root/.ssh/authorized_keys + owner: root + group: root + mode: "0400" + +- name: Deploy localuser authorized_keys (if user exists) + ansible.builtin.copy: + src: authorized_keys/localuser + dest: /home/localuser/.ssh/authorized_keys + owner: localuser + group: localuser + mode: "0400" + when: localuser_exists | bool + +- name: Deploy subodev authorized_keys (same keys as localuser, if user exists) + ansible.builtin.copy: + src: authorized_keys/localuser + dest: /home/subodev/.ssh/authorized_keys + owner: subodev + group: subodev + mode: "0400" + when: subodev_exists | bool + +# sshd_config — skipped on dev workstations (matches legacy DEV_WORKSTATION_CHECK). +# Uses the is_dev_workstation fact set by the preflight role based on the +# 'dev_workstations' inventory group. +- name: Deploy hardened /etc/ssh/sshd_config (non-dev hosts) + ansible.builtin.copy: + src: configs/sshd_config + dest: /etc/ssh/sshd_config + owner: root + group: root + mode: "0600" + backup: true + when: not (is_dev_workstation | bool) + notify: Validate and restart sshd + +# ssh-audit hardening drop-in — breaks OpenSSH server on Ubuntu, so the legacy +# script skipped Ubuntu hosts. Replicate that guard. +- name: Deploy ssh-audit hardening drop-in (non-Ubuntu only) + ansible.builtin.copy: + src: configs/ssh-audit-hardening.conf + dest: /etc/ssh/sshd_config.d/ssh-audit_hardening.conf + owner: root + group: root + mode: "0600" + backup: true + when: not (is_ubuntu | bool) + notify: Validate and restart sshd + +- name: Lock down sshd_config.d drop-in permissions + ansible.builtin.file: + path: /etc/ssh/sshd_config.d + mode: "0700" + owner: root + group: root