feat(security): add SSH hardening role

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 <crush@charm.land>
This commit is contained in:
2026-07-30 12:16:13 -05:00
parent 3919ff400d
commit cefe2f7cfa
6 changed files with 144 additions and 0 deletions
@@ -0,0 +1,2 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDHaBNuLS+GYGRPc9wne63Ocr+R+/Q01Y9V0FTv0RnG3
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPyMR0lFgiMKhQJ5aqy68nR0BQp1cNzi/wIThyuTV4a8 tsyscto@ultix-control
@@ -0,0 +1,2 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDHaBNuLS+GYGRPc9wne63Ocr+R+/Q01Y9V0FTv0RnG3
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPyMR0lFgiMKhQJ5aqy68nR0BQp1cNzi/wIThyuTV4a8 tsyscto@ultix-control
@@ -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
@@ -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
+10
View File
@@ -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
+91
View File
@@ -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