From 970d747dcb42127a1789313c31e5a49ff5076e17 Mon Sep 17 00:00:00 2001 From: Joe Testa Date: Sun, 17 Aug 2025 16:34:32 -0400 Subject: [PATCH] Smoothed out some rough edges from PR #307. --- README.md | 101 +++- src/ssh_audit/hardeningguides.py | 981 ++++++++++++++++++------------- src/ssh_audit/ssh_audit.py | 40 +- ssh-audit.1 | 14 +- test/test_hardeningguides.py | 102 +++- 5 files changed, 723 insertions(+), 515 deletions(-) diff --git a/README.md b/README.md index 8f8fa73..131da15 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,18 @@ ## Usage ``` usage: ssh-audit.py [-h] [-4] [-6] [-b] [-c] [-d] - [-g / ] [-j] [-l {info,warn,fail}] [-L] - [-M custom_policy.txt] [-m] [-n] [-P "Built-In Policy Name" / custom_policy.txt] [-p N] - [-T targets.txt] [-t N] [-v] [--conn-rate-test N[:max_rate]] [--dheat N[:kex[:e_len]]] - [--lookup alg1[,alg2,...]] [--skip-rate-test] [--threads N] + [-g / ] + [-j] [-l {info,warn,fail}] [-L] [-M custom_policy.txt] + [-m] [-n] [-P "Built-In Policy Name" / custom_policy.txt] + [-p N] [-T targets.txt] [-t N] [-v] + [--conn-rate-test N[:max_rate]] [--dheat N[:kex[:e_len]]] + [--get-hardening-guide platform] [--list-hardening-guides] + [--lookup alg1[,alg2,...]] [--skip-rate-test] + [--threads N] [host] +# ssh-audit.py v3.4.0-dev, https://github.com/jtesta/ssh-audit + positional arguments: host target hostname or IPv4/IPv6 address @@ -55,44 +61,74 @@ optional arguments: -4, --ipv4 enable IPv4 (order of precedence) -6, --ipv6 enable IPv6 (order of precedence) -b, --batch batch output - -c, --client-audit starts a server on port 2222 to audit client software config (use -p to change port; use -t - to change timeout) + -c, --client-audit starts a server on port 2222 to audit client software + config (use -p to change port; use -t to change + timeout) -d, --debug enable debugging output -g / , --gex-test / - conducts a very customized Diffie-Hellman GEX modulus size test. Tests an array of minimum, - preferred, and maximum values, or a range of values with an optional incremental step amount - -j, --json enable JSON output (use -jj to enable indentation for better readability) + conducts a very customized Diffie-Hellman GEX modulus + size test. Tests an array of minimum, preferred, and + maximum values, or a range of values with an optional + incremental step amount + -j, --json enable JSON output (use -jj to enable indentation for + better readability) -l {info,warn,fail}, --level {info,warn,fail} minimum output level (default: info) - -L, --list-policies list all the official, built-in policies. Combine with -v to view policy change logs + -L, --list-policies list all the official, built-in policies. Combine with + -v to view policy change logs -M custom_policy.txt, --make-policy custom_policy.txt - creates a policy based on the target server (i.e.: the target server has the ideal - configuration that other servers should adhere to), and stores it in the file path specified - -m, --manual print the man page (Docker, PyPI, Snap, and Windows builds only) - -n, --no-colors disable colors (automatic when the NO_COLOR environment variable is set) + creates a policy based on the target server (i.e.: the + target server has the ideal configuration that other + servers should adhere to), and stores it in the file + path specified + -m, --manual print the man page (Docker, PyPI, Snap, and Windows + builds only) + -n, --no-colors disable colors (automatic when the NO_COLOR + environment variable is set) -P "Built-In Policy Name" / custom_policy.txt, --policy "Built-In Policy Name" / custom_policy.txt - run a policy test using the specified policy (use -L to see built-in policies, or specify - filesystem path to custom policy created by -M) - -p N, --port N the TCP port to connect to (or to listen on when -c is used) + run a policy test using the specified policy (use -L + to see built-in policies, or specify filesystem path + to custom policy created by -M) + -p N, --port N the TCP port to connect to (or to listen on when -c is + used) -T targets.txt, --targets targets.txt - a file containing a list of target hosts (one per line, format HOST[:PORT]). Use -p/--port - to set the default port for all hosts. Use --threads to control concurrent scans - -t N, --timeout N timeout (in seconds) for connection and reading (default: 5) + a file containing a list of target hosts (one per + line, format HOST[:PORT]). Use -p/--port to set the + default port for all hosts. Use --threads to control + concurrent scans + -t N, --timeout N timeout (in seconds) for connection and reading + (default: 5) -v, --verbose enable verbose output --conn-rate-test N[:max_rate] - perform a connection rate test (useful for collecting metrics related to susceptibility of - the DHEat vuln). Testing is conducted with N concurrent sockets with an optional maximum - rate of connections per second + perform a connection rate test (useful for collecting + metrics related to susceptibility of the DHEat vuln). + Testing is conducted with N concurrent sockets with an + optional maximum rate of connections per second --dheat N[:kex[:e_len]] - continuously perform the DHEat DoS attack (CVE-2002-20001) against the target using N - concurrent sockets. Optionally, a specific key exchange algorithm can be specified instead - of allowing it to be automatically chosen. Additionally, a small length of the fake e value - sent to the server can be chosen for a more efficient attack (such as 4). + continuously perform the DHEat DoS attack + (CVE-2002-20001) against the target using N concurrent + sockets. Optionally, a specific key exchange algorithm + can be specified instead of allowing it to be + automatically chosen. Additionally, a small length of + the fake e value sent to the server can be chosen for + a more efficient attack (such as 4). + --get-hardening-guide platform + retrieves the hardening guide for the specified + platform name (use --list-hardening-guides to see list + of available guides). + --list-hardening-guides + list all official, built-in hardening guides for + common systems. Their full names can then be passed to + --get-hardening-guide. Add -v to this option to view + hardening guide change logs and prior versions. --lookup alg1[,alg2,...] - looks up an algorithm(s) without connecting to a server. - --skip-rate-test skip the connection rate test during standard audits (used to safely infer whether the DHEat - attack is viable) - --threads N number of threads to use when scanning multiple targets (-T/--targets) (default: 32) + looks up an algorithm(s) without connecting to a + server. + --skip-rate-test skip the connection rate test during standard audits + (used to safely infer whether the DHEat attack is + viable) + --threads N number of threads to use when scanning multiple + targets (-T/--targets) (default: 32) ``` * if both IPv4 and IPv6 are used, order of precedence can be set by using either `-46` or `-64`. * batch flag `-b` will output sections without header and without empty lines (implies verbose flag). @@ -183,7 +219,7 @@ Below is a screen shot of the client-auditing output when an unhardened OpenSSH ![client_screenshot](https://user-images.githubusercontent.com/2982011/68867998-b946c100-06c4-11ea-975f-1f47e4178a74.png) ## Hardening Guides -Guides to harden server & client configuration can be found here: [https://www.ssh-audit.com/hardening_guides.html](https://www.ssh-audit.com/hardening_guides.html) +Guides to harden server & client configuration are built into the tool (see `--list-hardening-guides` and `--get-hardening-guide` options). Additionally, they are also available online at: [https://www.ssh-audit.com/hardening_guides.html](https://www.ssh-audit.com/hardening_guides.html) ## Pre-Built Packages Pre-built packages are available for Windows (see the [Releases](https://github.com/jtesta/ssh-audit/releases) page), PyPI, Snap, and Docker: @@ -217,6 +253,7 @@ For convenience, a web front-end on top of the command-line tool is available at - BIG THANKS to [realmiwi](https://github.com/realmiwi) for being the project's *very first sponsor!!* - Added warning to all key exchanges that do not include protections against quantum attacks due to the Harvest Now, Decrypt Later strategy (see https://en.wikipedia.org/wiki/Harvest_now,_decrypt_later). - Removed SSHv1 support (rationale is documented in: https://github.com/jtesta/ssh-audit/issues/298). + - Added hardening guides (see `--list-hardening-guides` and `--get-hardening-guide`). Previously, they were only available at , but now they are built-in for convenience; partial credit [oam7575](https://github.com/oam7575). - Migrated from deprecated `getopt` module to `argparse`; partial credit [oam7575](https://github.com/oam7575). - When running against multiple hosts, now prints each target host regardless of output level. - Batch mode (`-b`) no longer automatically enables verbose mode, due to sometimes confusing results; users can still explicitly enable verbose mode using the `-v` flag. diff --git a/src/ssh_audit/hardeningguides.py b/src/ssh_audit/hardeningguides.py index a6dc1f4..e483742 100644 --- a/src/ssh_audit/hardeningguides.py +++ b/src/ssh_audit/hardeningguides.py @@ -1,7 +1,7 @@ """ The MIT License (MIT) - Copyright (C) 2020-2024 Joe Testa (jtesta@positronsecurity.com) + Copyright (C) 2025 Joe Testa (jtesta@positronsecurity.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -21,457 +21,592 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from typing import Dict, Any -import sys +from typing import Any, Dict -from ssh_audit import exitcodes -from ssh_audit.globals import VERSION +from ssh_audit.outputbuffer import OutputBuffer +class Hardening_Guides: -BUILTIN_GUIDES: Dict[str, Dict[str, Any]] = { + HARDENING_GUIDES: Dict[str, Any] = { + "Amazon Linux 2023": [ + { + "server_guide": True, + "version": 3, + "version_date": "2024-10-01", + "change_log": "Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.", + "notes": "all commands below are to be executed as the root user.", + "commands": [ + { + "heading": "Re-generate the RSA and ED25519 keys", + "comment": "", + "command": "rm -f /etc/ssh/ssh_host_*\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\"\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\"" + }, + { + "heading": "Enable the ED25519 and RSA keys", + "comment": "Enable the ED25519 and RSA HostKey directives in the /etc/ssh/sshd_config file:", + "command": "echo -e \"\\nHostKey /etc/ssh/ssh_host_ed25519_key\\nHostKey /etc/ssh/ssh_host_rsa_key\" >> /etc/ssh/sshd_config" + }, + { + "heading": "Remove small Diffie-Hellman moduli", + "comment": "", + "command": "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe\nmv -f /etc/ssh/moduli.safe /etc/ssh/moduli" + }, + { + "heading": "Restrict supported key exchange, cipher, and MAC algorithms", + "comment": "", + "command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com hardening guide.\\nKexAlgorithms 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\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\nHostKeyAlgorithms 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\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms 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\\n\\nPubkeyAcceptedAlgorithms 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\\n\\n\" > /etc/crypto-policies/back-ends/opensshserver.config" + }, + { + "heading": "Restart OpenSSH server", + "comment": "", + "command": "systemctl restart sshd" + }, + { + "heading": "Implement connection rate throttling", + "comment": "Connection rate throttling is needed in order to protect against the DHEat denial-of-service attack. A complete and flexible solution is to use iptables to allow up to 10 connections every 10 seconds from any one source address. An alternate solution is to set OpenSSH's PerSourceMaxStartups directive to 1 (note, however, that this can cause incomplete results during ssh-audit scans, as well as other client failures when bursts of connections are made).", + "command": "dnf install -y iptables\niptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\niptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\ndnf install -y iptables-services\niptables-save > /etc/sysconfig/iptables\nip6tables-save > /etc/sysconfig/ip6tables\nsystemctl enable iptables\nsystemctl enable ip6tables\nsystemctl start iptables\nsystemctl start ip6tables" + }, + ] + }, + { + "server_guide": True, + "version": 2, + "version_date": "2024-04-22", + "change_log": "Added connection throttling instructions to counteract the DHEat denial-of-service attack.", + "notes": "", + "commands": [] # Commands for this older version are not tracked here. + }, + { + "server_guide": True, + "version": 1, + "version_date": "2024-03-15", + "change_log": "Initial revision.", + "notes": "", + "commands": [] # Commands for this older version are not tracked here. + }, + { + "server_guide": False, + "version": 3, + "version_date": "2024-10-01", + "change_log": "Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.", + "notes": "", + "commands": [ + { + "heading": "Run the following in a terminal to harden the SSH client for the local user:", + "comment": "", + "command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\n 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n 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\\n\\n 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\\n\\n\" >> ~/.ssh/config" + }, + ] + }, + { + "server_guide": False, + "version": 2, + "version_date": "2024-04-22", + "change_log": "added connection throttling instructions to counteract the DHEat denial-of-service attack.", + "notes": "", + "commands": [] # Commands for this older version are not tracked here. + }, + { + "server_guide": False, + "version": 1, + "version_date": "2024-03-15", + "change_log": "Initial revision.", + "notes": "", + "commands": [] # Commands for this older version are not tracked here. + }, + ], - # Server - # Amazon Server - 'Amazon 2023 Server (version 1)': {'version': '1', 'changelog': {'2024-10-01': 'Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks', '2024-04-22': 'added connection throttling instructions to counteract the DHEat denial-of-service attack.', '2024-03-15': 'Initial revision'}, 'server_policy': True}, + "Debian 11": [ + { + "server_guide": True, + "version": 1, + "version_date": "2021-09-17", + "change_log": "Latest version.", + "notes": "all commands below are to be executed as the root user.", + "commands": [ + { + "heading": "Re-generate the RSA and ED25519 keys", + "comment": "", + "command": "rm -f /etc/ssh/ssh_host_*\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\"\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\"" + }, + { + "heading": "Enable the RSA and ED25519 keys", + "comment": "Enable the RSA and ED25519 HostKey directives in the /etc/ssh/sshd_config file:", + "command": "sed -i 's/^\\#HostKey \\/etc\\/ssh\\/ssh_host_\\(rsa\\|ed25519\\)_key$/HostKey \\/etc\\/ssh\\/ssh_host_\\1_key/g' /etc/ssh/sshd_config" + }, + { + "heading": "Remove small Diffie-Hellman moduli", + "comment": "", + "command": "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe\nmv -f /etc/ssh/moduli.safe /etc/ssh/moduli" + }, + { + "heading": "Restrict supported key exchange, cipher, and MAC algorithms", + "comment": "", + "command": "echo -e \"\\n# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\nKexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\nHostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-512,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com\" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf" + }, + { + "heading": "Restart OpenSSH server", + "comment": "", + "command": "service ssh restart" + }, + ] + }, + ], - # Debian Server - 'Debian Bullseye Server (version 1)': {'version': '1', 'changelog': {'2021-09-17': 'Initial Revision.'}, 'server_policy': True}, - 'Debian Bookworm Server (version 1)': {'version': '1', 'changelog': {'2021-09-17': 'Initial Revision.'}, 'server_policy': True}, + "Debian 12": [ + { + "server_guide": True, + "version": 3, + "version_date": "2025-04-18", + "change_log": "Added sntrup761x25519-sha512 to KexAlgorithms.", + "notes": "all commands below are to be executed as the root user.", + "commands": [ + { + "heading": "Re-generate the RSA and ED25519 keys", + "comment": "", + "command": "rm /etc/ssh/ssh_host_*\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\"\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\"" + }, + { + "heading": "Enable the ED25519 and RSA keys", + "comment": "Enable the ED25519 and RSA HostKey directives in the /etc/ssh/sshd_config file:", + "command": "echo -e \"\\nHostKey /etc/ssh/ssh_host_ed25519_key\\nHostKey /etc/ssh/ssh_host_rsa_key\" >> /etc/ssh/sshd_config" + }, + { + "heading": "Remove small Diffie-Hellman moduli", + "comment": "", + "command": "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe\nmv /etc/ssh/moduli.safe /etc/ssh/moduli" + }, + { + "heading": "Restrict supported key exchange, cipher, and MAC algorithms", + "comment": "", + "command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\n 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\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\nHostKeyAlgorithms 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\\n\\nRequiredRSASize 3072\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms 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\\n\\nPubkeyAcceptedAlgorithms 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\\n\\n\" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf" + }, + { + "heading": "Restart OpenSSH server", + "comment": "", + "command": "service ssh restart" + }, + { + "heading": "Implement connection rate throttling", + "comment": "Connection rate throttling is needed in order to protect against the DHEat denial-of-service attack. A complete and flexible solution is to use iptables to allow up to 10 connections every 10 seconds from any one source address. An alternate solution is to set OpenSSH's PerSourceMaxStartups directive to 1 (note, however, that this can cause incomplete results during ssh-audit scans, as well as other client failures when bursts of connections are made).", + "command": "apt update\nDEBIAN_FRONTEND=noninteractive apt install -q -y iptables netfilter-persistent iptables-persistent\niptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\niptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nservice netfilter-persistent save" + }, + ] + }, + { + "server_guide": True, + "version": 2, + "version_date": "2024-10-01", + "change_log": "Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.", + "notes": "", + "commands": [] # Commands for this older version are not tracked here. + }, + { + "server_guide": True, + "version": 1, + "version_date": "2024-04-24", + "change_log": "Added connection throttling instructions to counteract the DHEat denial-of-service attack.", + "notes": "", + "commands": [] # Commands for this older version are not tracked here. + }, + { + "server_guide": False, + "version": 2, + "version_date": "2024-10-01", + "change_log": "Added RequiredRSASize directive to enforce a minimum of 3072-bit user and host-based authentication keys. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.", + "notes": "", + "commands": [ + { + "heading": "Run the following in a terminal to harden the SSH client for the local user:", + "comment": "", + "command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\n RequiredRSASize 3072\\n\\n 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n 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\\n\\n 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\\n\\n\" >> ~/.ssh/config" + }, + ] + }, + { + "server_guide": False, + "version": 1, + "version_date": "2024-03-15", + "change_log": "Initial revision.", + "notes": "", + "commands": [] # Commands for this older version are not tracked here. + }, + ], - # Rocky Linux - 'Rocky 9 Server (version 1)': {'version': '1', 'changelog': {'2024-10-01': 'Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks', '\n2024-04-24': 'Added connection throttling instructions to counteract the DHEat denial-of-service attack.'}, 'server_policy': True}, + "Rocky Linux 9": [ + { + "server_guide": True, + "version": 2, + "version_date": "2024-10-01", + "change_log": "Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.", + "notes": "all commands below are to be executed as the root user.", + "commands": [ + { + "heading": "Re-generate the RSA and ED25519 keys", + "comment": "", + "command": "rm -f /etc/ssh/ssh_host_*\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\"\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\"" + }, + { + "heading": "Enable the ED25519 and RSA keys", + "comment": "Enable the ED25519 and RSA HostKey directives in the /etc/ssh/sshd_config file:", + "command": "echo -e \"\\nHostKey /etc/ssh/ssh_host_ed25519_key\\nHostKey /etc/ssh/ssh_host_rsa_key\" >> /etc/ssh/sshd_config" + }, + { + "heading": "Remove small Diffie-Hellman moduli", + "comment": "", + "command": "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe\nmv -f /etc/ssh/moduli.safe /etc/ssh/moduli" + }, + { + "heading": "Restrict supported key exchange, cipher, and MAC algorithms", + "comment": "", + "command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\nKexAlgorithms 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\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\nHostKeyAlgorithms 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\\n\\nRequiredRSASize 3072\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms 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\\n\\nPubkeyAcceptedAlgorithms 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\\n\\n\" > /etc/crypto-policies/back-ends/opensshserver.config" + }, + { + "heading": "Restart OpenSSH server", + "comment": "", + "command": "systemctl restart sshd" + }, + { + "heading": "Implement connection rate throttling", + "comment": "Connection rate throttling is needed in order to protect against the DHEat denial-of-service attack. A complete and flexible solution is to use iptables/firewalld to allow up to 10 connections every 10 seconds from any one source address. An alternate solution is to set OpenSSH's PerSourceMaxStartups directive to 1 (note, however, that this can cause incomplete results during ssh-audit scans, as well as other client failures when bursts of connections are made).", + "command": "firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p tcp --dport 22 -m state --state NEW -m recent --set\nfirewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nfirewall-cmd --permanent --direct --add-rule ipv6 filter INPUT 0 -p tcp --dport 22 -m state --state NEW -m recent --set\nfirewall-cmd --permanent --direct --add-rule ipv6 filter INPUT 1 -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nsystemctl reload firewalld" + }, + ] + }, + { + "server_guide": True, + "version": 1, + "version_date": "2024-04-24", + "change_log": "Added connection throttling instructions to counteract the DHEat denial-of-service attack.", + "notes": "", + "commands": [] # Commands for this older version are not tracked here. + }, + { + "server_guide": False, + "version": 2, + "version_date": "2024-10-01", + "change_log": "Added RequiredRSASize directive to enforce a minimum of 3072-bit user and host-based authentication keys. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.", + "notes": "", + "commands": [ + { + "heading": "Run the following in a terminal to harden the SSH client for the local user:", + "comment": "", + "command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\n RequiredRSASize 3072\\n\\n 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n 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\\n\\n 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\\n\\n\" >> ~/.ssh/config" + }, + ] + }, + { + "server_guide": False, + "version": 1, + "version_date": "2024-03-15", + "change_log": "Initial revision.", + "notes": "", + "commands": [] # Commands for this older version are not tracked here. + }, + ], - # Ubuntu Server - 'Ubuntu 2004 Server (version 1)': {'version': '1', 'changelog': {'2024-04-24': '\nAdded connection throttling instructions to counteract the DHEat denial-of-service attack.'}, 'server_policy': True}, - 'Ubuntu 2204 Server (version 1)': {'version': '1', 'changelog': {'2024-10-01': '\nRe-ordered host keys to prioritize ED25519 due to efficiency. \nRe-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks', '\n2024-04-22': '\nAdded connection throttling instructions to counteract the DHEat denial-of-service attack.'}, 'server_policy': True}, - 'Ubuntu 2404 Server (version 1)': {'version': '1', 'changelog': {'2024-10-01': '\nAdded Required RSASize directive to enforce a minimum of 3072-bit user and host-based authentication keys.', '\n2024-04-29': '\nInitial revision. In comparison to Ubuntu 22.04 LTS guide, the following changes were made: \n1.) For key exchanges, diffie-hellman-group18-sha512 and diffie-hellman-group-exchange-sha256 were prioritized over diffie-hellman-group16-sha512 due to greater security strength; GSS algorithms were prioritized over their non-GSS equivalents in order to match the client guide, \n2.) For ciphers, 256-bit AES ciphers were prioritized over 192 and 128-bit AES ciphers due to their increased resistence against quantum computing attacks (previously, weaker GCM ciphers had priority over CTR ciphers), \n3.) The HostbasedAcceptedAlgorithms and PubkeyAcceptedAlgorithms settings are now the same as HostKeyAlgorithms setting, \n4.) The hmac-sha2-512-etm@openssh.com MAC was increased in priority due to its increased resistence against quantum computing attacks, and \n5.) The ED25519 host keys were given priority over RSA host keys due to their greater efficiency.'}, 'server_policy': True}, + "Ubuntu 22.04": [ + { + "server_guide": True, + "version": 2, + "version_date": "2024-10-01", + "change_log": "Re-ordered host keys to prioritize ED25519 due to efficiency. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.", + "notes": "all commands below are to be executed as the root user.", + "commands": [ + { + "heading": "Re-generate the RSA and ED25519 keys", + "comment": "", + "command": "rm /etc/ssh/ssh_host_*\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\"\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\"" + }, + { + "heading": "Enable the ED25519 and RSA keys", + "comment": "Enable the ED25519 and RSA HostKey directives in the /etc/ssh/sshd_config file:", + "command": "echo -e \"\\nHostKey /etc/ssh/ssh_host_ed25519_key\\nHostKey /etc/ssh/ssh_host_rsa_key\" >> /etc/ssh/sshd_config" + }, + { + "heading": "Remove small Diffie-Hellman moduli", + "comment": "", + "command": "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe\nmv /etc/ssh/moduli.safe /etc/ssh/moduli" + }, + { + "heading": "Restrict supported key exchange, cipher, and MAC algorithms", + "comment": "", + "command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\nKexAlgorithms 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\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\nHostKeyAlgorithms 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\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms 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\\n\\nPubkeyAcceptedAlgorithms 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\" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf" + }, + { + "heading": "Implement connection rate throttling", + "comment": "Connection rate throttling is needed in order to protect against the DHEat denial-of-service attack. A complete and flexible solution is to use iptables to allow up to 10 connections every 10 seconds from any one source address. An alternate solution is to set OpenSSH's PerSourceMaxStartups directive to 1 (note, however, that this can cause incomplete results during ssh-audit scans, as well as other client failures when bursts of connections are made).", + "command": "iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\niptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nDEBIAN_FRONTEND=noninteractive apt install -q -y netfilter-persistent iptables-persistent\nservice netfilter-persistent save" + }, + { + "heading": "Restart OpenSSH server", + "comment": "", + "command": "service ssh restart" + }, + ] + }, + { + "server_guide": True, + "version": 1, + "version_date": "2024-04-22", + "change_log": "Added connection throttling instructions to counteract the DHEat denial-of-service attack.", + "notes": "", + "commands": [] # Commands for this older version are not tracked here. + }, + { + "server_guide": False, + "version": 1, + "version_date": "2024-10-01", + "change_log": "Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.", + "notes": "", + "commands": [ + { + "heading": "Run the following in a terminal to harden the SSH client for the local user:", + "comment": "", + "command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\n 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n 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\\n\\n 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\\n\\n\" >> ~/.ssh/config" + }, + ] + }, + ], - # Client - # Amazon - 'Amazon 2023 Client (version 1)': {'version': '1', 'changelog': {'2024-10-01': 'Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', '2024-04-22': 'added connection throttling instructions to counteract the DHEat denial-of-service attack.', '2024-03-15': 'Initial revision'}, 'server_policy': False}, + "Ubuntu 24.04": [ + { + "server_guide": True, + "version": 2, + "version_date": "2024-10-01", + "change_log": "Added RequiredRSASize directive to enforce a minimum of 3072-bit user and host-based authentication keys.", + "notes": "all commands below are to be executed as the root user.", + "commands": [ + { + "heading": "Re-generate the ED25519 and RSA keys", + "comment": "", + "command": "rm /etc/ssh/ssh_host_*\nssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N \"\"\nssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N \"\"" + }, + { + "heading": "Remove small Diffie-Hellman moduli", + "comment": "", + "command": "awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe\nmv /etc/ssh/moduli.safe /etc/ssh/moduli" + }, + { + "heading": "Enable the ED25519 and RSA keys", + "comment": "Enable the ED25519 and RSA HostKey directives in the /etc/ssh/sshd_config file:", + "command": "echo -e \"\\nHostKey /etc/ssh/ssh_host_ed25519_key\\nHostKey /etc/ssh/ssh_host_rsa_key\" >> /etc/ssh/sshd_config" + }, + { + "heading": "Restrict supported key exchange, cipher, and MAC algorithms", + "comment": "", + "command": "echo -e \"# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\\n# hardening guide.\\nKexAlgorithms 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\\n\\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\nMACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\\n\\nRequiredRSASize 3072\\n\\nHostKeyAlgorithms 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\\n\\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\nHostbasedAcceptedAlgorithms 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\\n\\nPubkeyAcceptedAlgorithms 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\" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf" + }, + { + "heading": "Restart OpenSSH server", + "comment": "", + "command": "service ssh restart" + }, + { + "heading": "Implement connection rate throttling", + "comment": "Connection rate throttling is needed in order to protect against the DHEat denial-of-service attack. A complete and flexible solution is to use iptables to allow up to 10 connections every 10 seconds from any one source address. An alternate solution is to set OpenSSH's PerSourceMaxStartups directive to 1 (note, however, that this can cause incomplete results during ssh-audit scans, as well as other client failures when bursts of connections are made).", + "command": "iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\niptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set\nip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP\nDEBIAN_FRONTEND=noninteractive apt install -q -y netfilter-persistent iptables-persistent\nservice netfilter-persistent save" + }, + ] + }, + { + "server_guide": True, + "version": 1, + "version_date": "2024-04-29", + "change_log": "Initial revision. In comparison to Ubuntu 22.04 LTS guide, the following changes were made: 1.) For key exchanges, diffie-hellman-group18-sha512 and diffie-hellman-group-exchange-sha256 were prioritized over diffie-hellman-group16-sha512 due to greater security strength; GSS algorithms were prioritized over their non-GSS equivalents in order to match the client guide, 2.) For ciphers, 256-bit AES ciphers were prioritized over 192 and 128-bit AES ciphers due to their increased resistence against quantum computing attacks (previously, weaker GCM ciphers had priority over CTR ciphers), 3.) The HostbasedAcceptedAlgorithms and PubkeyAcceptedAlgorithms settings are now the same as HostKeyAlgorithms setting, 4.) The hmac-sha2-512-etm@openssh.com MAC was increased in priority due to its increased resistence against quantum computing attacks, and 5.) The ED25519 host keys were given priority over RSA host keys due to their greater efficiency.", + "notes": "", + "commands": [] # Commands for this older version are not tracked here. + }, + { + "server_guide": False, + "version": 2, + "version_date": "2024-10-01", + "change_log": "Added RequiredRSASize directive to enforce a minimum of 3072-bit user and host-based authentication keys.", + "notes": "", + "commands": [ + { + "heading": "Run the following in a terminal to harden the SSH client for the local user:", + "comment": "", + "command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n 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\\n\\n MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\\n\\n RequiredRSASize 3072\\n\\n 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n 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\\n\\n 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\\n\\n\" >> ~/.ssh/config" + }, + ] + }, + { + "server_guide": False, + "version": 1, + "version_date": "2024-04-29", + "change_log": "Initial revision. In comparison to Ubuntu 22.04 LTS Client guide, the following changes were made: 1.) For key exchanges, diffie-hellman-group18-sha512 and diffie-hellman-group-exchange-sha256 were prioritized over diffie-hellman-group16-sha512 due to greater security strength, 2.) For ciphers, 256-bit AES ciphers were prioritized over 192 and 128-bit AES ciphers due to their increased resistence against quantum computing attacks (previously, weaker GCM ciphers had priority over CTR ciphers), 3.) The HostbasedAcceptedAlgorithms and PubkeyAcceptedAlgorithms settings are now the same as HostKeyAlgorithms setting, and 4.) The hmac-sha2-512-etm@openssh.com MAC was increased in priority due to its increased resistence against quantum computing attacks.", + "notes": "", + "commands": [] # Commands for this older version are not tracked here. + }, + ], - # Debian - 'Debian Bookworm Client (version 1)': {'version': '1', 'changelog': {'2024-10-01': 'Added RequiredRSASize directive to enforce a minimum of 3072-bit user and host-based authentication keys. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', '2024-03-15': 'Initial Revision'}, 'server_policy': False}, + "Linux Mint 21": [ + { + "server_guide": False, + "version": 1, + "version_date": "2024-10-01", + "change_log": "Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.", + "notes": "", + "commands": [ + { + "heading": "Run the following in a terminal to harden the SSH client for the local user:", + "comment": "", + "command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\\n\\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\\n\\n 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n 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\\n\\n 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\\n\\n\" >> ~/.ssh/config" + }, + ] + }, + ], - # Rocky Linux - 'Rocky 9 Client (version 1)': {'version': '1', 'changelog': {'2024-10-01': 'Added RequiredRSASize directive to enforce a minimum of 3072-bit user and host-based authentication keys. Re-ordered cipher list to prioritize larger key sizes as a countermeasure to quantum attacks.', '2024-03-15': 'Initial Revision'}, 'server_policy': False}, - - # Mint - 'Mint 20 Client (version 1)': {'version': '1', 'changelog': {'2020-10-20': 'Initial Revision'}, 'server_policy': False}, - 'Mint 21 Client (version 1)': {'version': '1', 'changelog': {'2020-10-20': 'Initial Revision'}, 'server_policy': False}, - 'Mint 22 Client (version 1)': {'version': '1', 'changelog': {'2020-10-20': 'Initial Revision'}, 'server_policy': False}, - - # Ubuntu - 'Ubuntu 2004 Client (version 1)': {'version': '1', 'changelog': {'2020-10-20': 'Initial Revision'}, 'server_policy': False}, - 'Ubuntu 2204 Client (version 1)': {'version': '1', 'changelog': {'2020-10-20': 'Initial Revision'}, 'server_policy': False}, - 'Ubuntu 2404 Client (version 1)': {'version': '1', 'changelog': {'2020-10-20': 'Initial Revision'}, 'server_policy': False}, -} - - - -class PrintHardeningGuides: - def __init__(self, os_type: str, os_ver: str, clientserver: str) -> None: - self.os_type = os_type - self.os_ver = os_ver - self.clientserver = clientserver - - self.get_config() - - def get_config(self) -> None: - - retval = exitcodes.GOOD - - os_type = self.os_type - os_ver = self.os_ver - clientserver = self.clientserver - policy_name = os_type + " " + os_ver + " " + clientserver - - supported_os = ["Amazon", "Debian", "Mint", "Rocky", "Ubuntu"] - supported_edition = ["2404", "2204", "2004", "1804", "2023", "22", "21", "20", "9", "Bookworm", "Bullseye"] - if clientserver not in ["Server", "Client"] or os_type not in supported_os and os_ver not in supported_edition: - print(" ") - print(f"\033[1mssh-audit Version : {VERSION}\033[0m") - print(" ") - print(f"\033[1mConfiguration : {os_type} {os_ver} {clientserver} is not supported\033[0m") - PrintHardeningGuides.supported_varient() - sys.exit(retval) - - # Server Configs - if clientserver in ["Server"]: - # Amazon Linux - if os_type in ["Amazon"] and os_ver in ["2023"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.server_modern_common() - PrintHardeningGuides.amazon_server_2023() - sys.exit(retval) - # Debian - elif os_type in ["Debian"] and os_ver in ["Bookworm"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.server_modern_common() - PrintHardeningGuides.bookworm_server() - PrintHardeningGuides.debian_ubuntu_rate_throttling() - sys.exit(retval) - elif os_type in ["Debian"] and os_ver in ["Bullseye"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.server_modern_common() - PrintHardeningGuides.bullseye_server() - sys.exit(retval) - # Rocky Linux - elif os_type in ["Rocky"] and os_ver in ["9"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.server_modern_common() - PrintHardeningGuides.rocky_9_server() - sys.exit(retval) - # Ubuntu - elif os_type in ["Ubuntu"] and os_ver in ["2404"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.server_modern_common() - PrintHardeningGuides.ubuntu_server_2404() - PrintHardeningGuides.debian_ubuntu_rate_throttling() - sys.exit(retval) - elif os_type in ["Ubuntu"] and os_ver in ["2204"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.server_modern_common() - PrintHardeningGuides.ubuntu_server_2204() - PrintHardeningGuides.debian_ubuntu_rate_throttling() - sys.exit(retval) - elif os_type in ["Ubuntu"] and os_ver in ["2004"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.server_modern_common() - PrintHardeningGuides.ubuntu_server_2004() - PrintHardeningGuides.debian_ubuntu_rate_throttling() - sys.exit(retval) - elif os_type in ["Ubuntu"] and os_ver in ["1804"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.server_legacy_common() - PrintHardeningGuides.ubuntu_server_1804() - sys.exit(retval) - else: - PrintHardeningGuides.supported_varient() - sys.exit(retval) - - - # Client Configs - if clientserver in ["Client"]: - # Amazon - if os_type in ["Amazon"] and os_ver in ["2023"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.amazon_2023_client() - sys.exit(retval) - # Debian - elif os_type in ["Debian"] and os_ver in ["Bookworm"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.debian_bookworm_client() - sys.exit(retval) - # Mint - elif os_type in ["Mint"] and os_ver in ["22"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.ubuntu_2404_mint_22_client() - sys.exit(retval) - elif os_type in ["Mint"] and os_ver in ["21"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.ubuntu_2204_mint_21_client() - sys.exit(retval) - elif os_type in ["Mint"] and os_ver in ["20"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.ubuntu_2004_mint_20_client() - sys.exit(retval) - # Rocky - elif os_type in ["Rocky"] and os_ver in ["9"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.rocky_9_client() - sys.exit(retval) - # Ubuntu - elif os_type in ["Ubuntu"] and os_ver in ["2404"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.ubuntu_2404_mint_22_client() - sys.exit(retval) - elif os_type in ["Ubuntu"] and os_ver in ["2204"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.ubuntu_2204_mint_21_client() - sys.exit(retval) - elif os_type in ["Ubuntu"] and os_ver in ["2004"]: - PrintHardeningGuides.print_ver_changelog(policy_name) - PrintHardeningGuides.ubuntu_2004_mint_20_client() - sys.exit(retval) - else: - PrintHardeningGuides.supported_varient() - sys.exit(retval) - - - - @staticmethod - def supported_varient() -> None: - retval = exitcodes.GOOD - print(" ") - print("For current, community developed and legacy guides") - print("check the website : https://www.ssh-audit.com/hardening_guides.html") - print(" ") - print("\033[1mSupported Server Configurations : \033[0m") - print(" ") - print(r"Amazon 2023 Server") - print(r"Debian Bookworm Server") - print(r"Debian Bullseye Server") - print(r"Rocky 9 Server") - print(r"Ubuntu 2404 Server") - print(r"Ubuntu 2204 Server") - print(r"Ubuntu 2004 Server") - print(" ") - print("\033[1mSupported Client Configurations : \033[0m") - print(r"Amazon 2023 Client") - print(r"Debian Bookworm Client") - print(r"Mint 22 Client") - print(r"Mint 21 Client") - print(r"Mint 20 Client") - print(r"Rocky 9 Client") - print(r"Ubuntu 2404 Client") - print(r"Ubuntu 2204 Client") - print(r"Ubuntu 2004 Client") - print(" ") - print("\033[1mExample Usage : \033[0m ") - print(r"python3 ssh-audit.py --get-hardening-guides Ubuntu 2404 Server") - print(" ") - sys.exit(retval) - - - # Client Configurations + "Linux Mint 22": [ + { + "server_guide": False, + "version": 2, + "version_date": "2024-10-01", + "change_log": "Added RequiredRSASize directive to enforce a minimum of 3072-bit user and host-based authentication keys.", + "notes": "", + "commands": [ + { + "heading": "Run the following in a terminal to harden the SSH client for the local user:", + "comment": "", + "command": "mkdir -p -m 0700 ~/.ssh; echo -e \"\\nHost *\\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\\n\\n 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\\n\\n MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\\n\\n RequiredRSASize 3072\\n\\n 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\\n\\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\\n\\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\\n\\n 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\\n\\n 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\\n\\n\" >> ~/.ssh/config" + }, + ] + }, + { + "server_guide": False, + "version": 1, + "version_date": "2024-04-29", + "change_log": "Initial revision. In comparison to Ubuntu 22.04 LTS Client guide, the following changes were made: 1.) For key exchanges, diffie-hellman-group18-sha512 and diffie-hellman-group-exchange-sha256 were prioritized over diffie-hellman-group16-sha512 due to greater security strength, 2.) For ciphers, 256-bit AES ciphers were prioritized over 192 and 128-bit AES ciphers due to their increased resistence against quantum computing attacks (previously, weaker GCM ciphers had priority over CTR ciphers), 3.) The HostbasedAcceptedAlgorithms and PubkeyAcceptedAlgorithms settings are now the same as HostKeyAlgorithms setting, and 4.) The hmac-sha2-512-etm@openssh.com MAC was increased in priority due to its increased resistence against quantum computing attacks.", + "notes": "", + "commands": [] # Commands for this older version are not tracked here. + }, + ], + } @staticmethod - def amazon_2023_client() -> None: - print(" ") - print("\033[1mRun the following in a terminal to harden the SSH client for the local user:\033[0m") - print(" ") - print(r'mkdir -p -m 0700 ~/.ssh; echo -e "\nHost *\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\n\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\n\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\n\n 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\n\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\n\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\n\n 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\n\n 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\n\n" >> ~/.ssh/config') - - @staticmethod - def debian_bookworm_client() -> None: - print(" ") - print("\033[1mRun the following in a terminal to harden the SSH client for the local user:\033[0m") - print(" ") - print(r'mkdir -p -m 0700 ~/.ssh; echo -e "\nHost *\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\n\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\n\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\n\n RequiredRSASize 3072\n\n 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\n\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\n\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\n\n 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\n\n 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\n\n" >> ~/.ssh/config') - - @staticmethod - def rocky_9_client() -> None: - print("\033[1mRun the following in a terminal to harden the SSH client for the local user:\033[0m") - print(" ") - print(r'mkdir -p -m 0700 ~/.ssh; echo -e "\nHost *\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\n\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\n\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\n\n RequiredRSASize 3072\n\n 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\n\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\n\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\n\n 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\n\n 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\n\n" >> ~/.ssh/config') - - @staticmethod - def ubuntu_2404_mint_22_client() -> None: - print("\033[1mRun the following in a terminal to harden the SSH client for the local user:\033[0m") - print(" ") - print(r'mkdir -p -m 0700 ~/.ssh; echo -e "\nHost *\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\n\n 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\n\n MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\n\n RequiredRSASize 3072\n\n 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\n\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\n\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\n\n 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\n\n 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\n\n" >> ~/.ssh/config') - - @staticmethod - def ubuntu_2204_mint_21_client() -> None: - print("\033[1mRun the following in a terminal to harden the SSH client for the local user:\033[0m") - print(" ") - print(r'mkdir -p -m 0700 ~/.ssh; echo -e "\nHost *\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\n\n KexAlgorithms sntrup761x25519-sha512@openssh.com,gss-curve25519-sha256-,curve25519-sha256,curve25519-sha256@libssh.org,gss-group16-sha512-,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\n\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\n\n 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\n\n CASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\n\n GSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\n\n 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\n\n 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\n\n" >> ~/.ssh/config') - - @staticmethod - def ubuntu_2004_mint_20_client() -> None: - print("\033[1mRun the following in a terminal to harden the SSH client for the local user:\033[0m") - print(" ") - print(r'mkdir -p -m 0700 ~/.ssh; echo -e "\nHost *\n Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\n KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\n MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\n HostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com\n" >> ~/.ssh/config') + def list_guides(out: OutputBuffer, verbose: bool) -> None: + '''Print all the server and client hardening guides.''' - # Server Configurations + server_guide_names = [] + client_guide_names = [] + + # Iterate through the guides, and record a list of server guide names, along with a separate list for client guide names. + for name, guides in Hardening_Guides.HARDENING_GUIDES.items(): + for guide in guides: + version = guide["version"] + version_date = guide["version_date"] + change_log = guide["change_log"] + if guide["server_guide"]: + full_name = f"{name} Server" if not verbose else f"{name} Server (version {version}): {version_date}: {change_log}" + if full_name not in server_guide_names: + server_guide_names.append(full_name) + else: + full_name = f"{name} Client" if not verbose else f"{name} Client (version {version}): {version_date}: {change_log}" + if full_name not in client_guide_names: + client_guide_names.append(full_name) + + # Sort the names. + server_guide_names.sort() + client_guide_names.sort() + + # Print the lists. + out.head("\nServer hardening guides:\n") + out.info(" * %s" % "\n * ".join(server_guide_names)) + + out.head("\nClient hardening guides:\n") + out.info(" * %s" % "\n * ".join(client_guide_names)) + out.info("\n") + + if not verbose: + out.info("Hint: add -v to --list-hardening-guides in order to see change log messages and prior versions. Prior versions of hardening guides can be retrieved as well with --get-hardening-guide (i.e.: --get-hardening-guide \"Ubuntu 24.04 Server (version 1)\").\n") + out.write() @staticmethod - def server_modern_common() -> None: - print("\033[1mRe-generate the ED25519 and RSA keys\033[0m") - print(" ") - print("rm /etc/ssh/ssh_host_*") - print(r'ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""') - print(r'ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""') - print(" ") - print("\033[1mRemove small Diffie-Hellman moduli\033[0m") - print(" ") - print(r"awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe") - print("mv /etc/ssh/moduli.safe /etc/ssh/moduli") - print(" ") - print("\033[1mEnable the ED25519 and RSA keys\033[0m") - print(" ") - print("Enable the ED25519 and RSA HostKey directives in the /etc/ssh/sshd_config file:") - print(" ") - print(r'echo -e "\nHostKey /etc/ssh/ssh_host_ed25519_key\nHostKey /etc/ssh/ssh_host_rsa_key" >> /etc/ssh/sshd_config') - print(" ") + def print_hardening_guide(out: OutputBuffer, platform: str) -> None: + '''Prints a hardening guide for the specified platform.''' - @staticmethod - def server_legacy_common() -> None: - print("\033[1mRe-generate the ED25519 and RSA keys\033[0m") - print(" ") - print(r"rm /etc/ssh/ssh_host_*") - print(r'ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""') - print(r'ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""') - print(" ") - print("\033[1mRemove small Diffie-Hellman moduli\033[0m") - print(" ") - print(r"awk '$5 >= 3071' /etc/ssh/moduli > /etc/ssh/moduli.safe") - print("mv /etc/ssh/moduli.safe /etc/ssh/moduli") - print(" ") - print("\033[1mDisable the DSA and ECDSA host keys\033[0m") - print(" ") - print("Comment out the DSA and ECDSA HostKey directives in the /etc/ssh/sshd_config file:") - print(" ") - print(r"sed -i 's/^HostKey \/etc\/ssh\/ssh_host_\(dsa\|ecdsa\)_key$/\#HostKey \/etc\/ssh\/ssh_host_\1_key/g' /etc/ssh/sshd_config") - print(" ") - @staticmethod - def debian_ubuntu_rate_throttling() -> None: - print("\033[1mImplement connection rate throttling\033[0m") - print(" ") - print("iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set") - print("iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP") - print("ip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set") - print("ip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP") - print(" ") - print("\033[1mEnable persistence of the iptables rules across server reboots: \033[0m") - print(" ") - print("DEBIAN_FRONTEND=noninteractive apt install -q -y netfilter-persistent iptables-persistent service netfilter-persistent save") - print(" ") - print("\033[1mRestart OpenSSH server\033[0m") - print(" ") - print("service ssh restart") + platform_orig = platform + invalid_guide_name_error = "Invalid guide name. Run --list-hardening-guides to see list of valid guide names." - @staticmethod - def ubuntu_server_2404() -> None: - print("\033[1mRestrict supported key exchange, cipher, and MAC algorithms\033[0m") - print(" ") - print(r'echo -e "# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms 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\n\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\n\nMACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\n\nRequiredRSASize 3072\n\nHostKeyAlgorithms 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\n\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\n\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\n\nHostbasedAcceptedAlgorithms 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\n\nPubkeyAcceptedAlgorithms 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" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf') - print(" ") - print("\033[1mRestart OpenSSH server\033[0m") - print(" ") - print("service ssh restart") - print(" ") + # If the user provided a version with the platform name, parse the version number they're interested in. + use_latest_version = True + use_version = 0 + pos = platform.find(" (version ") + if pos != -1: + use_latest_version = False + end_pos = platform.find(")", pos) + try: + use_version = int(platform[pos + 10:end_pos]) + except ValueError: + out.fail(invalid_guide_name_error, write_now=True) + return - @staticmethod - def ubuntu_server_2204() -> None: - print("\033[1mRestrict supported key exchange, cipher, and MAC algorithms\033[0m") - print(" ") - print(r'echo -e "# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms 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\n\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\n\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\n\nHostKeyAlgorithms 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\n\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\n\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\n\nHostbasedAcceptedAlgorithms 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\n\nPubkeyAcceptedAlgorithms 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" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf') - print(" ") - print("\033[1mRestart OpenSSH server\033[0m") - print(" ") - print("service ssh restart") - print(" ") + platform = platform[0:pos] - @staticmethod - def ubuntu_server_2004() -> None: - print("\033[1mRestrict supported key exchange, cipher, and MAC algorithms\033[0m") - print(" ") - print(r'echo -e "# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms 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\n\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\n\nMACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\n\nRequiredRSASize 3072\n\nHostKeyAlgorithms 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\n\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\n\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\n\nHostbasedAcceptedAlgorithms 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\n\nPubkeyAcceptedAlgorithms 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" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf') - print(" ") - print("\033[1mRestart OpenSSH server\033[0m") - print(" ") - print("service ssh restart") - print(" ") + last_space_pos = platform.rfind(" ") + if last_space_pos == -1: + out.fail(invalid_guide_name_error, write_now=True) + return - @staticmethod - def ubuntu_server_1804() -> None: - print("\033[1mRestrict supported key exchange, cipher, and MAC algorithms\033[0m") - print(" ") - print(r'echo -e "# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\nHostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com" >> /etc/ssh/sshd_config') - print(" ") - print("\033[1mRestart OpenSSH server\033[0m") - print(" ") - print("service ssh restart") - print(" ") + # From input such as "Ubuntu 24.04 Server", parse the OS name ("Ubuntu 24.04") and last word ("Server"). + os_name = platform[0:last_space_pos] + last_word = platform[last_space_pos + 1:] - @staticmethod - def bookworm_server() -> None: - print("\033[1mRestrict supported key exchange, cipher, and MAC algorithms\033[0m") - print(" ") - print(r'echo -e "# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms 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\n\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\n\nMACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com\n\nRequiredRSASize 3072\n\nHostKeyAlgorithms 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\n\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\n\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\n\nHostbasedAcceptedAlgorithms 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\n\nPubkeyAcceptedAlgorithms 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" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf') - print(" ") - print("\033[1mRestart OpenSSH server\033[0m") - print(" ") - print("service ssh restart") - print(" ") + # Determine if this is a server or client guide. + is_server = False + if last_word == "Server": + is_server = True + elif last_word != "Client": + out.fail(invalid_guide_name_error, write_now=True) + return - @staticmethod - def bullseye_server() -> None: - print("\033[1mRestrict supported key exchange, cipher, and MAC algorithms\033[0m") - print(" ") - print(r'echo -e "# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms 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\n\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\n\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\n\nHostKeyAlgorithms 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\n\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\n\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\n\nHostbasedAcceptedAlgorithms 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\n\nPubkeyAcceptedAlgorithms 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" > /etc/ssh/sshd_config.d/ssh-audit_hardening.conf') - print(" ") - print("\033[1mRestart OpenSSH server\033[0m") - print(" ") - print("service ssh restart") - print(" ") + # Ensure that this OS exists in the database. + if os_name not in Hardening_Guides.HARDENING_GUIDES: + out.fail(invalid_guide_name_error, write_now=True) + return - @staticmethod - def rocky_9_server() -> None: - print("\033[1mRestrict supported key exchange, cipher, and MAC algorithms\033[0m") - print(" ") - print(r'echo -e "# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms 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\n\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\n\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\n\nHostKeyAlgorithms 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\n\nRequiredRSASize 3072\n\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\n\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\n\nHostbasedAcceptedAlgorithms 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\n\nPubkeyAcceptedAlgorithms 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\n\n" > /etc/crypto-policies/back-ends/opensshserver.config') - print(" ") - print("\033[1mRestart OpenSSH server\033[0m") - print(" ") - print("systemctl restart sshd") - print(" ") - print("\033[1mImplement connection rate throttling\033[0m") - print(" ") - print("firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 0 -p tcp --dport 22 -m state --state NEW -m recent --set") - print("firewall-cmd --permanent --direct --add-rule ipv4 filter INPUT 1 -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP") - print("firewall-cmd --permanent --direct --add-rule ipv6 filter INPUT 0 -p tcp --dport 22 -m state --state NEW -m recent --set") - print("firewall-cmd --permanent --direct --add-rule ipv6 filter INPUT 1 -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP") - print(" ") - print("\033[1mReload firewalld to enable new rules:\033[0m") - print(" ") - print("systemctl reload firewalld") - print(" ") + # Pull all guides for this OS name. + guides = Hardening_Guides.HARDENING_GUIDES[os_name] - @staticmethod - def amazon_server_2023() -> None: - print("\033[1mRestrict supported key exchange, cipher, and MAC algorithms\033[0m") - print(" ") - print(r'echo -e "# Restrict key exchange, cipher, and MAC algorithms, as per sshaudit.com\n# hardening guide.\nKexAlgorithms 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\n\nCiphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-gcm@openssh.com,aes128-ctr\n\nMACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com\n\nHostKeyAlgorithms 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\n\nCASignatureAlgorithms sk-ssh-ed25519@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256\n\nGSSAPIKexAlgorithms gss-curve25519-sha256-,gss-group16-sha512-\n\nHostbasedAcceptedAlgorithms 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\n\nPubkeyAcceptedAlgorithms 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\n\n" > /etc/crypto-policies/back-ends/opensshserver.config') - print(" ") - print("\033[1mRestart OpenSSH server\033[0m") - print(" ") - print("systemctl restart sshd") - print(" ") - print("\033[1mImplement connection rate throttling\033[0m") - print(" ") - print("dnf install -y iptables") - print("iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set") - print("iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP") - print("ip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set") - print("ip6tables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 10 --hitcount 10 -j DROP") - print(" ") - print("\033[1mEnable persistence of the iptables rules across server reboots:\033[0m") - print(" ") - print("dnf install -y iptables-services") - print("iptables-save > /etc/sysconfig/iptables") - print("ip6tables-save > /etc/sysconfig/ip6tables") - print("systemctl enable iptables") - print("systemctl enable ip6tables") - print("systemctl start iptables") - print("systemctl start ip6tables") - print(" ") + # Iterate over guides until we find the type (server/client) we need, as well as the version of the guide we need. + selected_guide = None + latest_version = 0 + for guide in guides: + if guide["server_guide"] == is_server: + version = guide["version"] + if use_latest_version and (version > latest_version): + selected_guide = guide + latest_version = version + elif use_latest_version is False and use_version == version: + selected_guide = guide - @staticmethod - def print_ver_changelog(policy_name: str) -> None: - '''Prints ssh-audit version and change log for a supported configuration''' + # Ensure we found a guide from above. + if selected_guide is None: + out.fail(invalid_guide_name_error, write_now=True) + return - for key_name, policy in BUILTIN_GUIDES.items(): - if policy_name in key_name: + # Now print the guide. + version_header = f"\n#\n# Hardening guide for {platform_orig}\n#\n" if not use_latest_version else f"\n#\n# Hardening guide for {platform_orig} (version {latest_version})\n#\n" + out.info(version_header) - policy_struct = policy - policy_name_without_version = policy_name.split('(')[0] - name = policy_name_without_version # pylint: disable=protected-access - changelog_struct = policy_struct['changelog'] # pylint: disable=protected-access - print(" ") - print(f"\033[1mssh-audit Version : {VERSION}\033[0m") - print(" ") - print(f"\033[1mLocating configuration for {name}\033[0m") - print(" ") - print("\033[1mChange Log :\033[0m") - for date, change in changelog_struct.items(): - print(f"\033[1m{date} : {change}\033[0m") - print(" ") + commands = selected_guide["commands"] + for command_dict in commands: + heading = command_dict["heading"] + comment = command_dict["comment"] + command = command_dict["command"] + if heading != "": + out.info(f"# {heading}") + + if comment != "": + out.info(f"# {comment}") + + out.info(f"{command}") + out.info(s="") + + out.write() diff --git a/src/ssh_audit/ssh_audit.py b/src/ssh_audit/ssh_audit.py index ac410d8..2b3e66e 100755 --- a/src/ssh_audit/ssh_audit.py +++ b/src/ssh_audit/ssh_audit.py @@ -53,7 +53,7 @@ from ssh_audit.gextest import GEXTest from ssh_audit.hostkeytest import HostKeyTest from ssh_audit.outputbuffer import OutputBuffer from ssh_audit.policy import Policy -from ssh_audit.hardeningguides import PrintHardeningGuides +from ssh_audit.hardeningguides import Hardening_Guides from ssh_audit.product import Product from ssh_audit.protocol import Protocol from ssh_audit.software import Software @@ -790,13 +790,12 @@ def process_commandline(out: OutputBuffer, args: List[str]) -> 'AuditConf': # p # Add long options to the parser parser.add_argument("--conn-rate-test", action="store", dest="conn_rate_test", metavar="N[:max_rate]", type=str, default=None, help="perform a connection rate test (useful for collecting metrics related to susceptibility of the DHEat vuln). Testing is conducted with N concurrent sockets with an optional maximum rate of connections per second") parser.add_argument("--dheat", action="store", dest="dheat", metavar="N[:kex[:e_len]]", type=str, default=None, help="continuously perform the DHEat DoS attack (CVE-2002-20001) against the target using N concurrent sockets. Optionally, a specific key exchange algorithm can be specified instead of allowing it to be automatically chosen. Additionally, a small length of the fake e value sent to the server can be chosen for a more efficient attack (such as 4).") + parser.add_argument("--get-hardening-guide", action="store", metavar="platform", dest="get_hardening_guide", type=str, default=None, help="retrieves the hardening guide for the specified platform name (use --list-hardening-guides to see list of available guides).") + parser.add_argument("--list-hardening-guides", action="store_true", dest="list_hardening_guides", default=False, help="list all official, built-in hardening guides for common systems. Their full names can then be passed to --get-hardening-guide. Add -v to this option to view hardening guide change logs and prior versions.") parser.add_argument("--lookup", action="store", dest="lookup", metavar="alg1[,alg2,...]", type=str, default=None, help="looks up an algorithm(s) without connecting to a server.") parser.add_argument("--skip-rate-test", action="store_true", dest="skip_rate_test", default=False, help="skip the connection rate test during standard audits (used to safely infer whether the DHEat attack is viable)") parser.add_argument("--threads", action="store", dest="threads", metavar="N", type=int, default=32, help="number of threads to use when scanning multiple targets (-T/--targets) (default: %(default)s)") - # Print Suggested Configurations from : https://www.ssh-audit.com/hardening_guides.html - parser.add_argument("--get-hardening-guides", nargs="*", action="append", metavar="OS Ver Client/Server", dest="get_hardening_guides", type=str, default=None, help="Print suggested server or client configurations. Usage Example : Ubuntu 2404 Server") - parser.add_argument("--list-hardening-guides", action="store_true", dest="list_hardening_guides", default=False, help="List supported server and client configurations.") # The mandatory target option. Or rather, mandatory when -L, -T, --lookup or --print-config are not used. parser.add_argument("host", nargs="?", action="store", type=str, default="", help="target hostname or IPv4/IPv6 address") @@ -810,27 +809,6 @@ def process_commandline(out: OutputBuffer, args: List[str]) -> 'AuditConf': # p try: argument = parser.parse_args(args=args) - if argument.list_hardening_guides is True: - PrintHardeningGuides.supported_varient() - - if argument.get_hardening_guides is not None: - print_guides = (getattr(argument, 'get_hardening_guides'))[0] - arg_len = len(print_guides) - if arg_len <= 2: - user_arg = "" - for i in range(arg_len): - user_arg = user_arg + " " + str(print_guides[i]) - print(f"\033[1mUnsupported configuration : {user_arg}\033[0m") - PrintHardeningGuides.supported_varient() - else: - print_guides = (getattr(argument, 'get_hardening_guides'))[0] - os_type = print_guides[0] - os_ver = print_guides[1] - clientserver = print_guides[2] - - PrintHardeningGuides(os_type, os_ver, clientserver) - - # Set simple flags. aconf.client_audit = argument.client_audit aconf.ipv4 = argument.ipv4 @@ -915,8 +893,8 @@ def process_commandline(out: OutputBuffer, args: List[str]) -> 'AuditConf': # p parser.print_help() sys.exit(exitcodes.UNKNOWN_ERROR) - if argument.host == "" and argument.client_audit is False and argument.targets is None and argument.list_policies is False and argument.lookup is None and argument.manual is False and argument.get_hardening_guides is None: - out.fail("target host must be specified, unless -c, -m, -L, -T, --lookup or --print-configuration are used", write_now=True) + if argument.host == "" and argument.client_audit is False and argument.targets is None and argument.list_policies is False and argument.lookup is None and argument.manual is False and argument.list_hardening_guides is False and argument.get_hardening_guide is None: + out.fail("target host must be specified, unless -c, -m, -L, -T, --lookup or --list-hardening-guides are used", write_now=True) sys.exit(exitcodes.UNKNOWN_ERROR) if aconf.manual: @@ -929,6 +907,14 @@ def process_commandline(out: OutputBuffer, args: List[str]) -> 'AuditConf': # p list_policies(out, aconf.verbose) sys.exit(exitcodes.GOOD) + # Print a list of the hardening guides, or the specific guide requested by the user. + if argument.list_hardening_guides is True: + Hardening_Guides.list_guides(out, aconf.verbose) + sys.exit(exitcodes.GOOD) + elif argument.get_hardening_guide is not None: + Hardening_Guides.print_hardening_guide(out, argument.get_hardening_guide) + sys.exit(exitcodes.GOOD) + if aconf.client_audit is False and aconf.target_file is None: if oport is not None: host = argument.host diff --git a/ssh-audit.1 b/ssh-audit.1 index a3ff5f9..3f3de4b 100644 --- a/ssh-audit.1 +++ b/ssh-audit.1 @@ -1,4 +1,4 @@ -.TH SSH-AUDIT 1 "July 26, 2025" +.TH SSH-AUDIT 1 "August 17, 2025" .SH NAME \fBssh-audit\fP \- SSH server & client configuration auditor .SH SYNOPSIS @@ -51,6 +51,11 @@ Enable debug output. .br Run the DHEat DoS attack (CVE-2002-20001) against the target server (which will consume all available CPU resources). The number of concurrent sockets, N, needed to achieve this effect will be highly dependent on the CPU resources available on the target, as well as the latency between the source and target machines. The key exchange is automatically chosen based on which would cause maximum effect, unless explicitly chosen in the second field. Lastly, an (experimental) option allows the length in bytes of the fake e value sent to the server to be specified in the third field. Normally, the length of e is roughly the length of the modulus of the Diffie-Hellman exchange (hence, an 8192-bit / 1024-byte value of e is sent in each connection when targeting the diffie-hellman-group18-sha512 algorithm). Instead, it was observed that many SSH implementations accept small values, such as 4 bytes; this results in a much more network-efficient attack. +.TP +.B \-\-get\-hardening\-guide= +.br +Retrieves the hardening guide for the specified platform name (use \-\-list\-hardening\-guides to see list of available guides). + .TP .B -g, \-\-gex-test= .br @@ -91,10 +96,15 @@ Output results in JSON format. Specify twice (-jj) to enable indent printing (u .br Specify the minimum output level. Default is info. +.TP +.B \-\-list-hardening-guides +.br +List all official, built-in hardening guides for common systems. Their full names can then be passed to \-\-get\-hardening\-guide. Add \-v to this option to view hardening guide change logs and prior versions. + .TP .B -L, \-\-list-policies .br -List all official, built-in policies for common systems. Their full names can then be passed to -P/--policy. Add \-v to \-L to view policy change logs. +List all official, built-in policies for common systems. Their full names can then be passed to \-P/\-\-policy. Add \-v to \-L to view policy change logs. .TP .B \-\-lookup= diff --git a/test/test_hardeningguides.py b/test/test_hardeningguides.py index 929b8c7..5f3b744 100644 --- a/test/test_hardeningguides.py +++ b/test/test_hardeningguides.py @@ -1,41 +1,81 @@ import pytest -from ssh_audit.ssh_audit import process_commandline + +from ssh_audit.hardeningguides import Hardening_Guides -# pylint: disable=attribute-defined-outside-init class TestHardeningGuides: @pytest.fixture(autouse=True) def init(self, ssh_audit): self.OutputBuffer = ssh_audit.OutputBuffer() - self.process_commandline = process_commandline - @staticmethod - def _test_conf(conf, **kwargs): - options = { - 'get_hardening_guides': '', - } - for k, v in kwargs.items(): - options[k] = v - assert conf.get_hardening_guides == options['get_hardening_guides'] - def test_printconfig_conf_process_commandline(self): - # pylint: disable=too-many-statements - c = lambda x: self.process_commandline(self.OutputBuffer, x.split()) # noqa - with pytest.raises(SystemExit): - conf = c('') - with pytest.raises(SystemExit): - conf = c('--get-hardening-guides') - self._test_conf(conf) - with pytest.raises(SystemExit): - conf = c('--list-hardening-guides') - self._test_conf(conf) + def test_hardening_guides_consistency(self): + '''Ensure that the HARDENING_GUIDES struct is consistent.''' - for vendor in ["Amazon", "Debian", "Rocky", "Mint", "Ubuntu", "NoOS", " "]: - vendor = vendor - for os_ver in ["2404", "2204", "2004", "1804", "2023", "22", "21", "20", "9", "Bookworm", "Bullseye", "NoVersion", ""]: - os_ver = os_ver - for cs_type in ["Client", "Server", "Mistake", ""]: - cs_type = cs_type - with pytest.raises(SystemExit): - conf = c(f'--get-hardening-guides {vendor} {os_ver} {cs_type}') - self._test_conf(conf) + # Required keys in each guide dict. + required_guide_fields = ["server_guide", "version", "version_date", "change_log", "notes", "commands"] + + # Required keys in the commands dict. + required_command_fields = ["heading", "comment", "command"] + + for name, guides in Hardening_Guides.HARDENING_GUIDES.items(): + + # Ensure the key (guide name) is a string. + assert type(name) is str + + # Ensure the value (guides) is a list. + assert type(guides) is list + + for guide in guides: + + # Ensure each guide is a dict. + assert type(guide) is dict + + # Ensure each required key is in this guide. + for required_guide_field in required_guide_fields: + assert required_guide_field in guide + + # Check the guide values are the correct type. + assert type(guide["server_guide"]) is bool + assert type(guide["version"]) is int + assert type(guide["version_date"]) is str + assert type(guide["change_log"]) is str + assert type(guide["notes"]) is str + assert type(guide["commands"]) is list + + # The version must be creater than zero. + assert guide["version"] > 0 + + # Ensure the format is "YYYY-MM-DD". + version_date = guide["version_date"] + date_fields = version_date.split("-") + assert len(date_fields) == 3 + + # Check that the year is 4 digits and greater than 0. + year = date_fields[0] + assert len(year) == 4 + assert int(year) > 0 + + # Check that the month is 2 digits and between 1 and 12. + month = date_fields[1] + assert len(month) == 2 + assert 1 <= int(month) <= 12 + + # Check that the day is 2 digits and between 1 and 31. + day = date_fields[2] + assert len(day) == 2 + assert 1 <= int(day) <= 31 + + # Check that the change log is filled in. + assert len(guide["change_log"]) > 0 + + commands = guide["commands"] + for command in commands: + + # Ensure each required key is in this command list. + for required_command_field in required_command_fields: + assert required_command_field in command + + # Check that these fields are not empty. + assert len(command["heading"]) > 0 + assert len(command["command"]) > 0