From 40cf503c315c7d8358150539eaf1ac9a4c5a93d5 Mon Sep 17 00:00:00 2001 From: Kevin Barnoin Date: Mon, 2 Dec 2024 15:41:27 +0100 Subject: [PATCH 1/5] feat: add ssh config --- .infra/ansible/setup/tasks/configure-ssh.yml | 92 ++++++++++++++++++++ .infra/ansible/setup/tasks/main.yml | 3 + 2 files changed, 95 insertions(+) create mode 100644 .infra/ansible/setup/tasks/configure-ssh.yml diff --git a/.infra/ansible/setup/tasks/configure-ssh.yml b/.infra/ansible/setup/tasks/configure-ssh.yml new file mode 100644 index 0000000..6a190f3 --- /dev/null +++ b/.infra/ansible/setup/tasks/configure-ssh.yml @@ -0,0 +1,92 @@ +--- +# recommandation from https://www.sshaudit.com/hardening_guides.html#ubuntu_22_04_lts +# keys configuration is skipped (first 4 steps of the documentation above) +- name: Check if moduli file has been modified + stat: + path: /etc/ssh/moduli + register: moduli_stat + +- name: Create safe moduli file + command: "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe" + args: + creates: /etc/ssh/moduli.safe + when: moduli_stat.stat.exists and moduli_stat.stat.checksum != lookup('file', '/etc/ssh/moduli') + +- name: Move moduli.safe to moduli + command: mv /etc/ssh/moduli.safe /etc/ssh/moduli + args: + creates: /etc/ssh/moduli + when: moduli_stat.stat.exists and moduli_stat.stat.checksum != lookup('file', '/etc/ssh/moduli') + +- name: Check if SSH hardening configuration is already applied + stat: + path: /etc/ssh/sshd_config.d/ssh-audit_hardening.conf + register: sshd_config_stat + +- name: Create SSH hardening configuration + copy: + dest: /etc/ssh/sshd_config.d/ssh-audit_hardening.conf + content: | + # Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com + # hardening guide. + KexAlgorithms 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 + + CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sh2-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 + when: sshd_config_stat.stat.exists == false + +- name: Check if iptables rules are already set (IPv4) + command: iptables -C INPUT -p tcp --dport 22 -m state --state NEW -m recent --set + ignore_errors: true + register: iptables_check_ipv4 + failed_when: false + +- name: Add iptables rules for SSH flood protection (IPv4) + command: iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set + when: iptables_check_ipv4.rc != 0 + +- name: Check if ip6tables rules are already set (IPv6) + command: ip6tables -C INPUT -p tcp --dport 22 -m state --state NEW -m recent --set + ignore_errors: true + register: ip6tables_check_ipv6 + failed_when: false + +- name: Add ip6tables rules for SSH flood protection (IPv6) + command: ip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set + when: ip6tables_check_ipv6.rc != 0 + +- name: Install netfilter-persistent and iptables-persistent packages + apt: + name: + - netfilter-persistent + - iptables-persistent + state: present + update_cache: yes + +- name: Save iptables rules + command: service netfilter-persistent save + +- name: Restart SSH service + service: + name: ssh + state: restarted + when: sshd_config_stat.stat.exists == false or iptables_check_ipv4.rc != 0 or ip6tables_check_ipv6.rc != 0 + +- name: Wait for SSH service to be fully restarted + wait_for: + port: 22 + state: started + delay: 10 # Wait for 10 seconds to ensure SSH service is fully up + timeout: 60 # Timeout after 60 seconds if the port is not open + diff --git a/.infra/ansible/setup/tasks/main.yml b/.infra/ansible/setup/tasks/main.yml index 086a759..4e702d6 100644 --- a/.infra/ansible/setup/tasks/main.yml +++ b/.infra/ansible/setup/tasks/main.yml @@ -1,3 +1,6 @@ +- import_tasks: configure-ssh.yml + tags: ssh + - import_tasks: configure-system.yml tags: system From 4b3cd764dd76d9738f98783f784c6dc7c4d39b44 Mon Sep 17 00:00:00 2001 From: Kevin Barnoin Date: Mon, 2 Dec 2024 15:45:53 +0100 Subject: [PATCH 2/5] fix: comment --- .infra/ansible/setup/tasks/configure-ssh.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.infra/ansible/setup/tasks/configure-ssh.yml b/.infra/ansible/setup/tasks/configure-ssh.yml index 6a190f3..be86a54 100644 --- a/.infra/ansible/setup/tasks/configure-ssh.yml +++ b/.infra/ansible/setup/tasks/configure-ssh.yml @@ -1,6 +1,6 @@ --- # recommandation from https://www.sshaudit.com/hardening_guides.html#ubuntu_22_04_lts -# keys configuration is skipped (first 4 steps of the documentation above) +# keys configuration is skipped (first 2 steps of the documentation above) - name: Check if moduli file has been modified stat: path: /etc/ssh/moduli From af9f3f58d917f8f556f4f201eb1f1d937f88cd1a Mon Sep 17 00:00:00 2001 From: Antoine Bigard Date: Mon, 30 Dec 2024 10:54:11 +0100 Subject: [PATCH 3/5] fix: adapt for ubuntu 22 and 24 --- .infra/ansible/setup/tasks/configure-ssh.yml | 124 ++++++++++--------- .infra/files/app/tools/ssh/iptables.sh | 8 ++ .infra/files/app/tools/ssh/regen-host-key.sh | 8 ++ 3 files changed, 80 insertions(+), 60 deletions(-) create mode 100755 .infra/files/app/tools/ssh/iptables.sh create mode 100755 .infra/files/app/tools/ssh/regen-host-key.sh diff --git a/.infra/ansible/setup/tasks/configure-ssh.yml b/.infra/ansible/setup/tasks/configure-ssh.yml index be86a54..adccdb3 100644 --- a/.infra/ansible/setup/tasks/configure-ssh.yml +++ b/.infra/ansible/setup/tasks/configure-ssh.yml @@ -1,29 +1,34 @@ --- -# recommandation from https://www.sshaudit.com/hardening_guides.html#ubuntu_22_04_lts +# recommandation from https://www.sshaudit.com/hardening_guides.html # keys configuration is skipped (first 2 steps of the documentation above) -- name: Check if moduli file has been modified - stat: - path: /etc/ssh/moduli - register: moduli_stat + +- name: Regen host keys + command: /opt/app/tools/ssh/regen-host-key.sh + args: + creates: /etc/ssh/ssh_host_keys_generated - name: Create safe moduli file command: "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe" args: creates: /etc/ssh/moduli.safe - when: moduli_stat.stat.exists and moduli_stat.stat.checksum != lookup('file', '/etc/ssh/moduli') -- name: Move moduli.safe to moduli - command: mv /etc/ssh/moduli.safe /etc/ssh/moduli - args: - creates: /etc/ssh/moduli - when: moduli_stat.stat.exists and moduli_stat.stat.checksum != lookup('file', '/etc/ssh/moduli') +- name: Get if moduli file has been modified + stat: + path: /etc/ssh/moduli + register: moduli_stat -- name: Check if SSH hardening configuration is already applied +- name: Get if moduli safe file has been modified stat: - path: /etc/ssh/sshd_config.d/ssh-audit_hardening.conf - register: sshd_config_stat + path: /etc/ssh/moduli.safe + register: moduli_safe_stat + +- name: Move moduli.safe to moduli + command: mv /etc/ssh/moduli.safe /etc/ssh/moduli + when: moduli_stat.stat.checksum != moduli_safe_stat.stat.checksum + notify: + - restart-ssh -- name: Create SSH hardening configuration +- name: Create SSH hardening configuration Ubuntu 22 copy: dest: /etc/ssh/sshd_config.d/ssh-audit_hardening.conf content: | @@ -44,49 +49,48 @@ 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 - when: sshd_config_stat.stat.exists == false - -- name: Check if iptables rules are already set (IPv4) - command: iptables -C INPUT -p tcp --dport 22 -m state --state NEW -m recent --set - ignore_errors: true - register: iptables_check_ipv4 - failed_when: false - -- name: Add iptables rules for SSH flood protection (IPv4) - command: iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set - when: iptables_check_ipv4.rc != 0 - -- name: Check if ip6tables rules are already set (IPv6) - command: ip6tables -C INPUT -p tcp --dport 22 -m state --state NEW -m recent --set - ignore_errors: true - register: ip6tables_check_ipv6 - failed_when: false - -- name: Add ip6tables rules for SSH flood protection (IPv6) - command: ip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set - when: ip6tables_check_ipv6.rc != 0 - -- name: Install netfilter-persistent and iptables-persistent packages - apt: - name: - - netfilter-persistent - - iptables-persistent - state: present - update_cache: yes - -- name: Save iptables rules - command: service netfilter-persistent save - -- name: Restart SSH service - service: - name: ssh - state: restarted - when: sshd_config_stat.stat.exists == false or iptables_check_ipv4.rc != 0 or ip6tables_check_ipv6.rc != 0 - -- name: Wait for SSH service to be fully restarted - wait_for: - port: 22 - state: started - delay: 10 # Wait for 10 seconds to ensure SSH service is fully up - timeout: 60 # Timeout after 60 seconds if the port is not open + when: ansible_facts['distribution_major_version'] == '22' + notify: + - restart-ssh + +- name: Create SSH hardening configuration Ubuntu 24 + copy: + dest: /etc/ssh/sshd_config.d/ssh-audit_hardening.conf + content: | + # Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com + # hardening guide. + KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256,gss-group16-sha512-,diffie-hellman-group16-sha512 + + Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr + + MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com + + RequiredRSASize 3072 + + 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 + + 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,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 + + PubkeyAcceptedAlgorithms 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 + when: ansible_facts['distribution_major_version'] == '24' + notify: + - restart-ssh + +- name: IPtable + ansible.builtin.shell: + cmd: /opt/app/tools/ssh/iptables.sh + +# Do not use iptables-persistant due to conflict with fail2ban and docker policies +- name: Run IPtable script after reboot + ansible.builtin.cron: + name: "iptables" + special_time: "reboot" + job: "/opt/app/tools/ssh/iptables.sh >> /var/log/cron.log 2>&1; /opt/app/tools/monitoring/export-cron-status-prom.sh -c 'Restore SSH iptables' -v $?" +- name: Validate sshd Config + shell: + cmd: sshd -t diff --git a/.infra/files/app/tools/ssh/iptables.sh b/.infra/files/app/tools/ssh/iptables.sh new file mode 100755 index 0000000..8a1f9c2 --- /dev/null +++ b/.infra/files/app/tools/ssh/iptables.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +#Needs to be run as sudo + +iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set +iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP +ip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set +ip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP diff --git a/.infra/files/app/tools/ssh/regen-host-key.sh b/.infra/files/app/tools/ssh/regen-host-key.sh new file mode 100755 index 0000000..4227421 --- /dev/null +++ b/.infra/files/app/tools/ssh/regen-host-key.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +#Needs to be run as sudo + +rm /etc/ssh/ssh_host_* +ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" +ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N "" +touch /etc/ssh/ssh_host_keys_generated \ No newline at end of file From cf2b80dae02ef78f2e589e87a0b1a5a9025ded34 Mon Sep 17 00:00:00 2001 From: Antoine Bigard Date: Mon, 30 Dec 2024 10:56:29 +0100 Subject: [PATCH 4/5] fix: enable ED25519 and RSA host keys --- .infra/files/sshd/sshd_config | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.infra/files/sshd/sshd_config b/.infra/files/sshd/sshd_config index dd1e732..097ecf9 100644 --- a/.infra/files/sshd/sshd_config +++ b/.infra/files/sshd/sshd_config @@ -21,17 +21,20 @@ IgnoreRhosts yes X11Forwarding no PrintMotd no PrintLastLog yes -AcceptEnv LANG LC_* #Allow client to pass locale environment variables -ClientAliveInterval 300 #Client timeout (5 minutes) -ClientAliveCountMax 3 #This way enforces timeouts on the server side -LoginGraceTime 30 #Authentication must happen within 30 seconds -#MaxStartups 2 #Max concurrent SSH sessions +AcceptEnv LANG LC\_\* #Allow client to pass locale environment variables +ClientAliveInterval 300 #Client timeout (5 minutes) +ClientAliveCountMax 3 #This way enforces timeouts on the server side +LoginGraceTime 30 #Authentication must happen within 30 seconds +#MaxStartups 2 #Max concurrent SSH sessions TCPKeepAlive yes Subsystem sftp /usr/lib/openssh/sftp-server #Tunnel -#PermitTunnel no #Only SSH connection and nothing else -#AllowTcpForwarding no #Disablow tunneling out via SSH -#AllowStreamLocalForwarding no #Disablow tunneling out via SSH -#GatewayPorts no #Disablow tunneling out via SSH -#AllowAgentForwarding no #Do not allow agent forwardng +#PermitTunnel no #Only SSH connection and nothing else +#AllowTcpForwarding no #Disablow tunneling out via SSH +#AllowStreamLocalForwarding no #Disablow tunneling out via SSH +#GatewayPorts no #Disablow tunneling out via SSH +#AllowAgentForwarding no #Do not allow agent forwardng + +HostKey /etc/ssh/ssh_host_ed25519_key +HostKey /etc/ssh/ssh_host_rsa_key From 9e9ba9b437c8bc4882d77841edfd7739c7ebae75 Mon Sep 17 00:00:00 2001 From: Antoine Bigard Date: Mon, 30 Dec 2024 13:28:30 +0100 Subject: [PATCH 5/5] fix: scripts orders --- .infra/ansible/setup/tasks/configure-ssh.yml | 2 +- .infra/ansible/setup/tasks/main.yml | 6 +++--- .infra/files/app/tools/ssh/moduli_file.sh | 5 +++++ 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100755 .infra/files/app/tools/ssh/moduli_file.sh diff --git a/.infra/ansible/setup/tasks/configure-ssh.yml b/.infra/ansible/setup/tasks/configure-ssh.yml index adccdb3..24b3481 100644 --- a/.infra/ansible/setup/tasks/configure-ssh.yml +++ b/.infra/ansible/setup/tasks/configure-ssh.yml @@ -8,7 +8,7 @@ creates: /etc/ssh/ssh_host_keys_generated - name: Create safe moduli file - command: "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe" + command: /opt/app/tools/ssh/moduli_file.sh args: creates: /etc/ssh/moduli.safe diff --git a/.infra/ansible/setup/tasks/main.yml b/.infra/ansible/setup/tasks/main.yml index 481ede7..88f0d62 100644 --- a/.infra/ansible/setup/tasks/main.yml +++ b/.infra/ansible/setup/tasks/main.yml @@ -2,9 +2,6 @@ tags: kernel when: add_kernel_modification == true -- import_tasks: configure-ssh.yml - tags: ssh - - import_tasks: configure-system.yml tags: system @@ -26,6 +23,9 @@ - import_tasks: install-app.yml tags: app +- import_tasks: configure-ssh.yml + tags: ssh + - import_tasks: configure-fail2ban.yml tags: fail2ban diff --git a/.infra/files/app/tools/ssh/moduli_file.sh b/.infra/files/app/tools/ssh/moduli_file.sh new file mode 100755 index 0000000..c66d19a --- /dev/null +++ b/.infra/files/app/tools/ssh/moduli_file.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail +#Needs to be run as sudo + +awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe \ No newline at end of file