mirror of
				https://github.com/jtesta/ssh-audit.git
				synced 2025-10-30 21:15:27 +01:00 
			
		
		
		
	Moved built-in policies from external files to internal database. (#75)
This commit is contained in:
		
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							| @@ -47,7 +47,8 @@ usage: ssh-audit.py [options] <host> | |||||||
|                                     adhere to) |                                     adhere to) | ||||||
|    -n,  --no-colors        disable colors |    -n,  --no-colors        disable colors | ||||||
|    -p,  --port=<port>      port to connect |    -p,  --port=<port>      port to connect | ||||||
|    -P,  --policy=<policy.txt>  run a policy test using the specified policy |    -P,  --policy=<"policy name" | policy.txt>  run a policy test using the | ||||||
|  |                                                    specified policy | ||||||
|    -t,  --timeout=<secs>   timeout (in seconds) for connection and reading |    -t,  --timeout=<secs>   timeout (in seconds) for connection and reading | ||||||
|                                (default: 5) |                                (default: 5) | ||||||
|    -T,  --targets=<hosts.txt>  a file containing a list of target hosts (one |    -T,  --targets=<hosts.txt>  a file containing a list of target hosts (one | ||||||
| @@ -92,17 +93,17 @@ ssh-audit -L | |||||||
|  |  | ||||||
| To run a policy audit against a server: | To run a policy audit against a server: | ||||||
| ``` | ``` | ||||||
| ssh-audit -P path/to/server_policy targetserver | ssh-audit -P ["policy name" | path/to/server_policy.txt] targetserver | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| To run a policy audit against a client: | To run a policy audit against a client: | ||||||
| ``` | ``` | ||||||
| ssh-audit -c -P path/to/client_policy | ssh-audit -c -P ["policy name" | path/to/client_policy.txt] | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| To run a policy audit against many servers: | To run a policy audit against many servers: | ||||||
| ``` | ``` | ||||||
| ssh-audit -T servers.txt -P path/to/server_policy | ssh-audit -T servers.txt -P ["policy name" | path/to/server_policy.txt] | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| To create a policy based on a target server (which can be manually edited; see official built-in policies for syntax examples): | To create a policy based on a target server (which can be manually edited; see official built-in policies for syntax examples): | ||||||
| @@ -151,8 +152,9 @@ For convenience, a web front-end on top of the command-line tool is available at | |||||||
|  |  | ||||||
| ## ChangeLog | ## ChangeLog | ||||||
| ### v2.3.1-dev (???) | ### v2.3.1-dev (???) | ||||||
|  |  - Migrated pre-made policies from external files to internal database. | ||||||
|  - Split single 3,500 line script into many files (by class). |  - Split single 3,500 line script into many files (by class). | ||||||
|  - Added setup.py support; credit [Ganden Schaffner](https://github.com/gschaffner) |  - Added setup.py support; credit [Ganden Schaffner](https://github.com/gschaffner). | ||||||
|  - Added 1 new cipher: `des-cbc@ssh.com`. |  - Added 1 new cipher: `des-cbc@ssh.com`. | ||||||
|  |  | ||||||
| ### v2.3.0 (2020-09-27) | ### v2.3.0 (2020-09-27) | ||||||
|   | |||||||
| @@ -489,8 +489,25 @@ function run_test { | |||||||
|     echo -e "${test_name} ${GREEN}passed${CLR}." |     echo -e "${test_name} ${GREEN}passed${CLR}." | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function run_builtin_policy_test { | ||||||
|  |     policy_name=$1         # The built-in policy name to use. | ||||||
|  |     version=$2             # Version of OpenSSH to test with. | ||||||
|  |     test_number=$3         # The test number to run. | ||||||
|  |     server_options=$4      # The options to start the server with (i.e.: "-o option1,options2,...") | ||||||
|  |     expected_exit_code=$5  # The expected exit code of ssh-audit.py. | ||||||
|  |  | ||||||
| function run_policy_test { |     server_exec="/openssh/sshd-${version} -D -f /etc/ssh/sshd_config-8.0p1_test1 ${server_options}" | ||||||
|  |     test_result_stdout="${TEST_RESULT_DIR}/openssh_${version}_builtin_policy_${test_number}.txt" | ||||||
|  |     test_result_json="${TEST_RESULT_DIR}/openssh_${version}_builtin_policy_${test_number}.json" | ||||||
|  |     expected_result_stdout="test/docker/expected_results/openssh_${version}_builtin_policy_${test_number}.txt" | ||||||
|  |     expected_result_json="test/docker/expected_results/openssh_${version}_builtin_policy_${test_number}.json" | ||||||
|  |     test_name="OpenSSH ${version} built-in policy ${test_number}" | ||||||
|  |  | ||||||
|  |     run_policy_test "${test_name}" "${server_exec}" "${policy_name}" "${test_result_stdout}" "${test_result_json}" "${expected_exit_code}" | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | function run_custom_policy_test { | ||||||
|     config_number=$1  # The configuration number to use. |     config_number=$1  # The configuration number to use. | ||||||
|     test_number=$2    # The policy test number to run. |     test_number=$2    # The policy test number to run. | ||||||
|     expected_exit_code=$3  # The expected exit code of ssh-audit.py. |     expected_exit_code=$3  # The expected exit code of ssh-audit.py. | ||||||
| @@ -510,11 +527,24 @@ function run_policy_test { | |||||||
|  |  | ||||||
|     server_exec="/openssh/sshd-${version} -D -f /etc/ssh/${config}" |     server_exec="/openssh/sshd-${version} -D -f /etc/ssh/${config}" | ||||||
|     policy_path="test/docker/policies/policy_${test_number}.txt" |     policy_path="test/docker/policies/policy_${test_number}.txt" | ||||||
|     test_result_stdout="${TEST_RESULT_DIR}/openssh_${version}_policy_${test_number}.txt" |     test_result_stdout="${TEST_RESULT_DIR}/openssh_${version}_custom_policy_${test_number}.txt" | ||||||
|     test_result_json="${TEST_RESULT_DIR}/openssh_${version}_policy_${test_number}.json" |     test_result_json="${TEST_RESULT_DIR}/openssh_${version}_custom_policy_${test_number}.json" | ||||||
|     expected_result_stdout="test/docker/expected_results/openssh_${version}_policy_${test_number}.txt" |     expected_result_stdout="test/docker/expected_results/openssh_${version}_custom_policy_${test_number}.txt" | ||||||
|     expected_result_json="test/docker/expected_results/openssh_${version}_policy_${test_number}.json" |     expected_result_json="test/docker/expected_results/openssh_${version}_custom_policy_${test_number}.json" | ||||||
|     test_name="OpenSSH ${version} policy ${test_number}" |     test_name="OpenSSH ${version} custom policy ${test_number}" | ||||||
|  |  | ||||||
|  |     run_policy_test "${test_name}" "${server_exec}" "${policy_path}" "${test_result_stdout}" "${test_result_json}" "${expected_exit_code}" | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | function run_policy_test { | ||||||
|  |     test_name=$1 | ||||||
|  |     server_exec=$2 | ||||||
|  |     policy_path=$3 | ||||||
|  |     test_result_stdout=$4 | ||||||
|  |     test_result_json=$5 | ||||||
|  |     expected_exit_code=$6 | ||||||
|  |  | ||||||
|  |  | ||||||
|     #echo "Running: docker run -d -p 2222:22 ${IMAGE_NAME}:${IMAGE_VERSION} ${server_exec}" |     #echo "Running: docker run -d -p 2222:22 ${IMAGE_NAME}:${IMAGE_VERSION} ${server_exec}" | ||||||
|     cid=`docker run -d -p 2222:22 ${IMAGE_NAME}:${IMAGE_VERSION} ${server_exec}` |     cid=`docker run -d -p 2222:22 ${IMAGE_NAME}:${IMAGE_VERSION} ${server_exec}` | ||||||
| @@ -523,8 +553,8 @@ function run_policy_test { | |||||||
| 	exit 1 | 	exit 1 | ||||||
|     fi |     fi | ||||||
|  |  | ||||||
|     #echo "Running: ./ssh-audit.py -P ${policy_path} localhost:2222 > ${test_result_stdout}" |     #echo "Running: ./ssh-audit.py -P \"${policy_path}\" localhost:2222 > ${test_result_stdout}" | ||||||
|     ./ssh-audit.py -P ${policy_path} localhost:2222 > ${test_result_stdout} |     ./ssh-audit.py -P "${policy_path}" localhost:2222 > ${test_result_stdout} | ||||||
|     actual_exit_code=$? |     actual_exit_code=$? | ||||||
|     if [[ ${actual_exit_code} != ${expected_exit_code} ]]; then |     if [[ ${actual_exit_code} != ${expected_exit_code} ]]; then | ||||||
| 	echo -e "${test_name} ${REDB}FAILED${CLR} (expected exit code: ${expected_exit_code}; actual exit code: ${actual_exit_code}\n" | 	echo -e "${test_name} ${REDB}FAILED${CLR} (expected exit code: ${expected_exit_code}; actual exit code: ${actual_exit_code}\n" | ||||||
| @@ -533,8 +563,8 @@ function run_policy_test { | |||||||
| 	exit 1 | 	exit 1 | ||||||
|     fi |     fi | ||||||
|  |  | ||||||
|     #echo "Running: ./ssh-audit.py -P ${policy_path} -j localhost:2222 > ${test_result_json}" |     #echo "Running: ./ssh-audit.py -P \"${policy_path}\" -j localhost:2222 > ${test_result_json}" | ||||||
|     ./ssh-audit.py -P ${policy_path} -j localhost:2222 > ${test_result_json} |     ./ssh-audit.py -P "${policy_path}" -j localhost:2222 > ${test_result_json} | ||||||
|     actual_exit_code=$? |     actual_exit_code=$? | ||||||
|     if [[ ${actual_exit_code} != ${expected_exit_code} ]]; then |     if [[ ${actual_exit_code} != ${expected_exit_code} ]]; then | ||||||
| 	echo -e "${test_name} ${REDB}FAILED${CLR} (expected exit code: ${expected_exit_code}; actual exit code: ${actual_exit_code}\n" | 	echo -e "${test_name} ${REDB}FAILED${CLR} (expected exit code: ${expected_exit_code}; actual exit code: ${actual_exit_code}\n" | ||||||
| @@ -627,36 +657,42 @@ echo | |||||||
| run_tinyssh_test '20190101' 'test1' $PROGRAM_RETVAL_WARNING | run_tinyssh_test '20190101' 'test1' $PROGRAM_RETVAL_WARNING | ||||||
| echo | echo | ||||||
| echo | echo | ||||||
| run_policy_test 'config1' 'test1' $PROGRAM_RETVAL_GOOD | run_custom_policy_test 'config1' 'test1' $PROGRAM_RETVAL_GOOD | ||||||
| run_policy_test 'config1' 'test2' $PROGRAM_RETVAL_FAILURE | run_custom_policy_test 'config1' 'test2' $PROGRAM_RETVAL_FAILURE | ||||||
| run_policy_test 'config1' 'test3' $PROGRAM_RETVAL_FAILURE | run_custom_policy_test 'config1' 'test3' $PROGRAM_RETVAL_FAILURE | ||||||
| run_policy_test 'config1' 'test4' $PROGRAM_RETVAL_FAILURE | run_custom_policy_test 'config1' 'test4' $PROGRAM_RETVAL_FAILURE | ||||||
| run_policy_test 'config1' 'test5' $PROGRAM_RETVAL_FAILURE | run_custom_policy_test 'config1' 'test5' $PROGRAM_RETVAL_FAILURE | ||||||
| run_policy_test 'config2' 'test6' $PROGRAM_RETVAL_GOOD | run_custom_policy_test 'config2' 'test6' $PROGRAM_RETVAL_GOOD | ||||||
|  |  | ||||||
| # Passing test with host key certificate and CA key certificates. | # Passing test with host key certificate and CA key certificates. | ||||||
| run_policy_test 'config3' 'test7' $PROGRAM_RETVAL_GOOD | run_custom_policy_test 'config3' 'test7' $PROGRAM_RETVAL_GOOD | ||||||
|  |  | ||||||
| # Failing test with host key certificate and non-compliant CA key length. | # Failing test with host key certificate and non-compliant CA key length. | ||||||
| run_policy_test 'config3' 'test8' $PROGRAM_RETVAL_FAILURE | run_custom_policy_test 'config3' 'test8' $PROGRAM_RETVAL_FAILURE | ||||||
|  |  | ||||||
| # Failing test with non-compliant host key certificate and CA key certificate. | # Failing test with non-compliant host key certificate and CA key certificate. | ||||||
| run_policy_test 'config3' 'test9' $PROGRAM_RETVAL_FAILURE | run_custom_policy_test 'config3' 'test9' $PROGRAM_RETVAL_FAILURE | ||||||
|  |  | ||||||
| # Failing test with non-compliant host key certificate and non-compliant CA key certificate. | # Failing test with non-compliant host key certificate and non-compliant CA key certificate. | ||||||
| run_policy_test 'config3' 'test10' $PROGRAM_RETVAL_FAILURE | run_custom_policy_test 'config3' 'test10' $PROGRAM_RETVAL_FAILURE | ||||||
|  |  | ||||||
| # Passing test with host key size check. | # Passing test with host key size check. | ||||||
| run_policy_test 'config2' 'test11' $PROGRAM_RETVAL_GOOD | run_custom_policy_test 'config2' 'test11' $PROGRAM_RETVAL_GOOD | ||||||
|  |  | ||||||
| # Failing test with non-compliant host key size check. | # Failing test with non-compliant host key size check. | ||||||
| run_policy_test 'config2' 'test12' $PROGRAM_RETVAL_FAILURE | run_custom_policy_test 'config2' 'test12' $PROGRAM_RETVAL_FAILURE | ||||||
|  |  | ||||||
| # Passing test with DH modulus test. | # Passing test with DH modulus test. | ||||||
| run_policy_test 'config2' 'test13' $PROGRAM_RETVAL_GOOD | run_custom_policy_test 'config2' 'test13' $PROGRAM_RETVAL_GOOD | ||||||
|  |  | ||||||
| # Failing test with DH modulus test. | # Failing test with DH modulus test. | ||||||
| run_policy_test 'config2' 'test14' $PROGRAM_RETVAL_FAILURE | run_custom_policy_test 'config2' 'test14' $PROGRAM_RETVAL_FAILURE | ||||||
|  |  | ||||||
|  | # Passing test for built-in OpenSSH 8.0p1 server policy. | ||||||
|  | run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 1)" "8.0p1" "test1" "-o HostKeyAlgorithms=ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr -o MACs=hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com" $PROGRAM_RETVAL_GOOD | ||||||
|  |  | ||||||
|  | # Failing test for built-in OpenSSH 8.0p1 server policy (MACs not hardened). | ||||||
|  | run_builtin_policy_test "Hardened OpenSSH Server v8.0 (version 1)" "8.0p1" "test2" "-o HostKeyAlgorithms=ssh-ed25519 -o KexAlgorithms=curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 -o Ciphers=chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr" $PROGRAM_RETVAL_FAILURE | ||||||
|  |  | ||||||
|  |  | ||||||
| # The test functions above will terminate the script on failure, so if we reached here, | # The test functions above will terminate the script on failure, so if we reached here, | ||||||
|   | |||||||
| @@ -36,9 +36,6 @@ python_requires = >=3.5,<4 | |||||||
| [options.packages.find] | [options.packages.find] | ||||||
| where = src | where = src | ||||||
|  |  | ||||||
| [options.package_data] |  | ||||||
| ssh_audit = policies/* |  | ||||||
|  |  | ||||||
| [options.entry_points] | [options.entry_points] | ||||||
| console_scripts = | console_scripts = | ||||||
|     ssh-audit = ssh_audit.ssh_audit:main |     ssh-audit = ssh_audit.ssh_audit:main | ||||||
|   | |||||||
| @@ -1,24 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH v7.7. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| name = "Hardened OpenSSH v7.7" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # Group exchange DH modulus sizes. |  | ||||||
| dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = ssh-ed25519 |  | ||||||
|  |  | ||||||
| # Host key types that may optionally appear. |  | ||||||
| optional host keys = ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256 |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH v7.8. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| name = "Hardened OpenSSH v7.8" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # Group exchange DH modulus sizes. |  | ||||||
| dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = ssh-ed25519 |  | ||||||
|  |  | ||||||
| # Host key types that may optionally appear. |  | ||||||
| optional host keys = ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256 |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH v7.9. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| name = "Hardened OpenSSH v7.9" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # Group exchange DH modulus sizes. |  | ||||||
| dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = ssh-ed25519 |  | ||||||
|  |  | ||||||
| # Host key types that may optionally appear. |  | ||||||
| optional host keys = ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256 |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH v8.0. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| name = "Hardened OpenSSH v8.0" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # Group exchange DH modulus sizes. |  | ||||||
| dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = ssh-ed25519 |  | ||||||
|  |  | ||||||
| # Host key types that may optionally appear. |  | ||||||
| optional host keys = ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256 |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH v8.1. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| name = "Hardened OpenSSH v8.1" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # Group exchange DH modulus sizes. |  | ||||||
| dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = ssh-ed25519 |  | ||||||
|  |  | ||||||
| # Host key types that may optionally appear. |  | ||||||
| optional host keys = sk-ssh-ed25519@openssh.com, ssh-ed25519-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256 |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH v8.2. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| name = "Hardened OpenSSH v8.2" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # RSA host key sizes. |  | ||||||
| hostkey_size_rsa-sha2-256 = 4096 |  | ||||||
| hostkey_size_rsa-sha2-512 = 4096 |  | ||||||
|  |  | ||||||
| # Group exchange DH modulus sizes. |  | ||||||
| dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519 |  | ||||||
|  |  | ||||||
| # Host key types that may optionally appear. |  | ||||||
| optional host keys = sk-ssh-ed25519@openssh.com, ssh-ed25519-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256 |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH v8.3. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| name = "Hardened OpenSSH v8.3" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # RSA host key sizes. |  | ||||||
| hostkey_size_rsa-sha2-256 = 4096 |  | ||||||
| hostkey_size_rsa-sha2-512 = 4096 |  | ||||||
|  |  | ||||||
| # Group exchange DH modulus sizes. |  | ||||||
| dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519 |  | ||||||
|  |  | ||||||
| # Host key types that may optionally appear. |  | ||||||
| optional host keys = sk-ssh-ed25519@openssh.com, ssh-ed25519-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256 |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH v8.4. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| name = "Hardened OpenSSH v8.4" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # RSA host key sizes. |  | ||||||
| hostkey_size_rsa-sha2-256 = 4096 |  | ||||||
| hostkey_size_rsa-sha2-512 = 4096 |  | ||||||
|  |  | ||||||
| # Group exchange DH modulus sizes. |  | ||||||
| dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519 |  | ||||||
|  |  | ||||||
| # Host key types that may optionally appear. |  | ||||||
| optional host keys = sk-ssh-ed25519@openssh.com, ssh-ed25519-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256 |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH on Ubuntu 16.04 LTS. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| client policy = true |  | ||||||
| name = "Hardened Ubuntu Client 16.04 LTS" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = ssh-ed25519, ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256, rsa-sha2-512, ssh-rsa-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256@libssh.org, diffie-hellman-group-exchange-sha256, ext-info-c |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH on Ubuntu 18.04 LTS. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| client policy = true |  | ||||||
| name = "Hardened Ubuntu Client 18.04 LTS" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = ssh-ed25519, ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256, rsa-sha2-512, ssh-rsa-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256, ext-info-c |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH on Ubuntu 20.04 LTS. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| client policy = true |  | ||||||
| name = "Hardened Ubuntu Client 20.04 LTS" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = 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, ssh-rsa-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256, ext-info-c |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH on Ubuntu Server 16.04 LTS. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| name = "Hardened Ubuntu Server 16.04 LTS" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # Group exchange DH modulus sizes. |  | ||||||
| dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = ssh-ed25519 |  | ||||||
|  |  | ||||||
| # Host key types that may optionally appear. |  | ||||||
| optional host keys = ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256@libssh.org, diffie-hellman-group-exchange-sha256 |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH on Ubuntu Server 18.04 LTS. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| name = "Hardened Ubuntu Server 18.04 LTS" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # Group exchange DH modulus sizes. |  | ||||||
| dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = ssh-ed25519 |  | ||||||
|  |  | ||||||
| # Host key types that may optionally appear. |  | ||||||
| optional host keys = ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256 |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| # |  | ||||||
| # Official policy for hardened OpenSSH on Ubuntu Server 20.04 LTS. |  | ||||||
| # |  | ||||||
|  |  | ||||||
| name = "Hardened Ubuntu Server 20.04 LTS" |  | ||||||
| version = 1 |  | ||||||
|  |  | ||||||
| # RSA host key sizes. |  | ||||||
| hostkey_size_rsa-sha2-256 = 4096 |  | ||||||
| hostkey_size_rsa-sha2-512 = 4096 |  | ||||||
|  |  | ||||||
| # Group exchange DH modulus sizes. |  | ||||||
| dh_modulus_size_diffie-hellman-group-exchange-sha256 = 2048 |  | ||||||
|  |  | ||||||
| # The host key types that must match exactly (order matters). |  | ||||||
| host keys = rsa-sha2-512, rsa-sha2-256, ssh-ed25519 |  | ||||||
|  |  | ||||||
| # Host key types that may optionally appear. |  | ||||||
| optional host keys = sk-ssh-ed25519@openssh.com, ssh-ed25519-cert-v01@openssh.com, sk-ssh-ed25519-cert-v01@openssh.com, rsa-sha2-256-cert-v01@openssh.com, rsa-sha2-512-cert-v01@openssh.com |  | ||||||
|  |  | ||||||
| # The key exchange algorithms that must match exactly (order matters). |  | ||||||
| key exchanges = curve25519-sha256, curve25519-sha256@libssh.org, diffie-hellman-group16-sha512, diffie-hellman-group18-sha512, diffie-hellman-group-exchange-sha256 |  | ||||||
|  |  | ||||||
| # The ciphers that must match exactly (order matters). |  | ||||||
| ciphers = chacha20-poly1305@openssh.com, aes256-gcm@openssh.com, aes128-gcm@openssh.com, aes256-ctr, aes192-ctr, aes128-ctr |  | ||||||
|  |  | ||||||
| # The MACs that must match exactly (order matters). |  | ||||||
| macs = hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com, umac-128-etm@openssh.com |  | ||||||
| @@ -22,7 +22,7 @@ | |||||||
|    THE SOFTWARE. |    THE SOFTWARE. | ||||||
| """ | """ | ||||||
| from typing import Dict, List, Tuple | from typing import Dict, List, Tuple | ||||||
| from typing import Optional, Any | from typing import Optional, Any, Union, cast | ||||||
| from datetime import date | from datetime import date | ||||||
|  |  | ||||||
| from ssh_audit.ssh2_kex import SSH2_Kex  # pylint: disable=unused-import | from ssh_audit.ssh2_kex import SSH2_Kex  # pylint: disable=unused-import | ||||||
| @@ -32,7 +32,49 @@ from ssh_audit.banner import Banner | |||||||
| # Validates policy files and performs policy testing | # Validates policy files and performs policy testing | ||||||
| class Policy: | class Policy: | ||||||
|  |  | ||||||
|     def __init__(self, policy_file: Optional[str] = None, policy_data: Optional[str] = None) -> None: |     # Each field maps directly to a private member variable of the Policy class. | ||||||
|  |     BUILTIN_POLICIES = { | ||||||
|  |  | ||||||
|  |         # Ubuntu Server policies | ||||||
|  |  | ||||||
|  |         'Hardened Ubuntu Server 16.04 LTS (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256@libssh.org', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, | ||||||
|  |  | ||||||
|  |         'Hardened Ubuntu Server 18.04 LTS (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, | ||||||
|  |  | ||||||
|  |         'Hardened Ubuntu Server 20.04 LTS (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         # Generic OpenSSH Server policies | ||||||
|  |  | ||||||
|  |         'Hardened OpenSSH Server v7.7 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, | ||||||
|  |  | ||||||
|  |         'Hardened OpenSSH Server v7.8 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, | ||||||
|  |  | ||||||
|  |         'Hardened OpenSSH Server v7.9 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, | ||||||
|  |  | ||||||
|  |         'Hardened OpenSSH Server v8.0 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, | ||||||
|  |  | ||||||
|  |         'Hardened OpenSSH Server v8.1 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, | ||||||
|  |  | ||||||
|  |         'Hardened OpenSSH Server v8.2 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, | ||||||
|  |  | ||||||
|  |         'Hardened OpenSSH Server v8.3 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, | ||||||
|  |  | ||||||
|  |         'Hardened OpenSSH Server v8.4 (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['rsa-sha2-512', 'rsa-sha2-256', 'ssh-ed25519'], 'optional_host_keys': ['sk-ssh-ed25519@openssh.com', 'ssh-ed25519-cert-v01@openssh.com', 'sk-ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256-cert-v01@openssh.com', 'rsa-sha2-512-cert-v01@openssh.com'], 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': {'rsa-sha2-256': 4096, 'rsa-sha2-512': 4096}, 'cakey_sizes': None, 'dh_modulus_sizes': {'diffie-hellman-group-exchange-sha256': 2048}, 'server_policy': True}, | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         # Ubuntu Client policies | ||||||
|  |  | ||||||
|  |         'Hardened Ubuntu Client 16.04 LTS (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa-cert-v01@openssh.com'], 'optional_host_keys': None, 'kex': ['curve25519-sha256@libssh.org', 'diffie-hellman-group-exchange-sha256', 'ext-info-c'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False}, | ||||||
|  |  | ||||||
|  |         'Hardened Ubuntu Client 18.04 LTS (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['ssh-ed25519', 'ssh-ed25519-cert-v01@openssh.com', 'rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa-cert-v01@openssh.com'], 'optional_host_keys': None, 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-c'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False}, | ||||||
|  |  | ||||||
|  |         'Hardened Ubuntu Client 20.04 LTS (version 1)': {'version': '1', 'banner': None, 'compressions': None, 'host_keys': ['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', 'ssh-rsa-cert-v01@openssh.com'], 'optional_host_keys': None, 'kex': ['curve25519-sha256', 'curve25519-sha256@libssh.org', 'diffie-hellman-group16-sha512', 'diffie-hellman-group18-sha512', 'diffie-hellman-group-exchange-sha256', 'ext-info-c'], 'ciphers': ['chacha20-poly1305@openssh.com', 'aes256-gcm@openssh.com', 'aes128-gcm@openssh.com', 'aes256-ctr', 'aes192-ctr', 'aes128-ctr'], 'macs': ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com'], 'hostkey_sizes': None, 'cakey_sizes': None, 'dh_modulus_sizes': None, 'server_policy': False}, | ||||||
|  |  | ||||||
|  |     }  # type: Dict[str, Dict[str, Union[Optional[str], Optional[List[str]], bool, Dict[str, int]]]] | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def __init__(self, policy_file: Optional[str] = None, policy_data: Optional[str] = None, manual_load: bool = False) -> None: | ||||||
|         self._name = None  # type: Optional[str] |         self._name = None  # type: Optional[str] | ||||||
|         self._version = None  # type: Optional[str] |         self._version = None  # type: Optional[str] | ||||||
|         self._banner = None  # type: Optional[str] |         self._banner = None  # type: Optional[str] | ||||||
| @@ -47,10 +89,22 @@ class Policy: | |||||||
|         self._dh_modulus_sizes = None  # type: Optional[Dict[str, int]] |         self._dh_modulus_sizes = None  # type: Optional[Dict[str, int]] | ||||||
|         self._server_policy = True |         self._server_policy = True | ||||||
|  |  | ||||||
|         if (policy_file is None) and (policy_data is None): |         self._name_and_version = ''  # type: str | ||||||
|             raise RuntimeError('policy_file and policy_data must not both be None.') |  | ||||||
|         elif (policy_file is not None) and (policy_data is not None): |         # Ensure that only one mode was specified. | ||||||
|             raise RuntimeError('policy_file and policy_data must not both be specified.') |         num_modes = 0 | ||||||
|  |         if policy_file is not None: | ||||||
|  |             num_modes += 1 | ||||||
|  |         if policy_data is not None: | ||||||
|  |             num_modes += 1 | ||||||
|  |         if manual_load is True: | ||||||
|  |             num_modes += 1 | ||||||
|  |  | ||||||
|  |         if num_modes != 1: | ||||||
|  |             raise RuntimeError('Exactly one of the following can be specified only: policy_file, policy_data, or manual_load') | ||||||
|  |  | ||||||
|  |         if manual_load: | ||||||
|  |             return | ||||||
|  |  | ||||||
|         if policy_file is not None: |         if policy_file is not None: | ||||||
|             with open(policy_file, "r") as f: |             with open(policy_file, "r") as f: | ||||||
| @@ -142,6 +196,8 @@ class Policy: | |||||||
|         if self._version is None: |         if self._version is None: | ||||||
|             raise ValueError('The policy does not have a version field.') |             raise ValueError('The policy does not have a version field.') | ||||||
|  |  | ||||||
|  |         self._name_and_version = "%s (version %s)" % (self._name, self._version) | ||||||
|  |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def _append_error(errors: List[Any], mismatched_field: str, expected_required: Optional[List[str]], expected_optional: Optional[List[str]], actual: List[str]) -> None: |     def _append_error(errors: List[Any], mismatched_field: str, expected_required: Optional[List[str]], expected_optional: Optional[List[str]], actual: List[str]) -> None: | ||||||
| @@ -339,7 +395,7 @@ macs = %s | |||||||
|  |  | ||||||
|     def get_name_and_version(self) -> str: |     def get_name_and_version(self) -> str: | ||||||
|         '''Returns a string of this Policy's name and version.''' |         '''Returns a string of this Policy's name and version.''' | ||||||
|         return '%s (version %s)' % (self._name, self._version) |         return self._name_and_version | ||||||
|  |  | ||||||
|  |  | ||||||
|     def is_server_policy(self) -> bool: |     def is_server_policy(self) -> bool: | ||||||
| @@ -347,6 +403,50 @@ macs = %s | |||||||
|         return self._server_policy |         return self._server_policy | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def list_builtin_policies() -> Tuple[List[str], List[str]]: | ||||||
|  |         '''Returns two lists: a list of names of built-in server policies, and a list of names of built-in client policies, respectively.''' | ||||||
|  |         server_policy_names = [] | ||||||
|  |         client_policy_names = [] | ||||||
|  |  | ||||||
|  |         for policy_name in Policy.BUILTIN_POLICIES: | ||||||
|  |             if Policy.BUILTIN_POLICIES[policy_name]['server_policy']: | ||||||
|  |                 server_policy_names.append(policy_name) | ||||||
|  |             else: | ||||||
|  |                 client_policy_names.append(policy_name) | ||||||
|  |  | ||||||
|  |         server_policy_names.sort() | ||||||
|  |         client_policy_names.sort() | ||||||
|  |         return server_policy_names, client_policy_names | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def load_builtin_policy(policy_name: str) -> Optional['Policy']: | ||||||
|  |         '''Returns a Policy with the specified built-in policy name loaded, or None if no policy of that name exists.''' | ||||||
|  |         p = None | ||||||
|  |         if policy_name in Policy.BUILTIN_POLICIES: | ||||||
|  |             policy_struct = Policy.BUILTIN_POLICIES[policy_name] | ||||||
|  |             p = Policy(manual_load=True) | ||||||
|  |             policy_name_without_version = policy_name[0:policy_name.rfind(' (')] | ||||||
|  |             p._name = policy_name_without_version  # pylint: disable=protected-access | ||||||
|  |             p._version = cast(str, policy_struct['version'])  # pylint: disable=protected-access | ||||||
|  |             p._banner = cast(Optional[str], policy_struct['banner'])  # pylint: disable=protected-access | ||||||
|  |             p._compressions = cast(Optional[List[str]], policy_struct['compressions'])  # pylint: disable=protected-access | ||||||
|  |             p._host_keys = cast(Optional[List[str]], policy_struct['host_keys'])  # pylint: disable=protected-access | ||||||
|  |             p._optional_host_keys = cast(Optional[List[str]], policy_struct['optional_host_keys'])  # pylint: disable=protected-access | ||||||
|  |             p._kex = cast(Optional[List[str]], policy_struct['kex'])  # pylint: disable=protected-access | ||||||
|  |             p._ciphers = cast(Optional[List[str]], policy_struct['ciphers'])  # pylint: disable=protected-access | ||||||
|  |             p._macs = cast(Optional[List[str]], policy_struct['macs'])  # pylint: disable=protected-access | ||||||
|  |             p._hostkey_sizes = cast(Optional[Dict[str, int]], policy_struct['hostkey_sizes'])  # pylint: disable=protected-access | ||||||
|  |             p._cakey_sizes = cast(Optional[Dict[str, int]], policy_struct['cakey_sizes'])  # pylint: disable=protected-access | ||||||
|  |             p._dh_modulus_sizes = cast(Optional[Dict[str, int]], policy_struct['dh_modulus_sizes'])  # pylint: disable=protected-access | ||||||
|  |             p._server_policy = cast(bool, policy_struct['server_policy'])  # pylint: disable=protected-access | ||||||
|  |  | ||||||
|  |             p._name_and_version = "%s (version %s)" % (p._name, p._version)  # pylint: disable=protected-access | ||||||
|  |  | ||||||
|  |         return p | ||||||
|  |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def _normalize_error_field(field: List[str]) -> Any: |     def _normalize_error_field(field: List[str]) -> Any: | ||||||
|         '''If field is an array with a string parsable as an integer, return that integer.  Otherwise, return the field unmodified.''' |         '''If field is an array with a string parsable as an integer, return that integer.  Otherwise, return the field unmodified.''' | ||||||
| @@ -367,9 +467,14 @@ macs = %s | |||||||
|         banner = undefined |         banner = undefined | ||||||
|         compressions_str = undefined |         compressions_str = undefined | ||||||
|         host_keys_str = undefined |         host_keys_str = undefined | ||||||
|  |         optional_host_keys_str = undefined | ||||||
|         kex_str = undefined |         kex_str = undefined | ||||||
|         ciphers_str = undefined |         ciphers_str = undefined | ||||||
|         macs_str = undefined |         macs_str = undefined | ||||||
|  |         hostkey_sizes_str = undefined | ||||||
|  |         cakey_sizes_str = undefined | ||||||
|  |         dh_modulus_sizes_str = undefined | ||||||
|  |  | ||||||
|  |  | ||||||
|         if self._name is not None: |         if self._name is not None: | ||||||
|             name = '[%s]' % self._name |             name = '[%s]' % self._name | ||||||
| @@ -382,11 +487,19 @@ macs = %s | |||||||
|             compressions_str = ', '.join(self._compressions) |             compressions_str = ', '.join(self._compressions) | ||||||
|         if self._host_keys is not None: |         if self._host_keys is not None: | ||||||
|             host_keys_str = ', '.join(self._host_keys) |             host_keys_str = ', '.join(self._host_keys) | ||||||
|  |         if self._optional_host_keys is not None: | ||||||
|  |             optional_host_keys_str = ', '.join(self._optional_host_keys) | ||||||
|         if self._kex is not None: |         if self._kex is not None: | ||||||
|             kex_str = ', '.join(self._kex) |             kex_str = ', '.join(self._kex) | ||||||
|         if self._ciphers is not None: |         if self._ciphers is not None: | ||||||
|             ciphers_str = ', '.join(self._ciphers) |             ciphers_str = ', '.join(self._ciphers) | ||||||
|         if self._macs is not None: |         if self._macs is not None: | ||||||
|             macs_str = ', '.join(self._macs) |             macs_str = ', '.join(self._macs) | ||||||
|  |         if self._hostkey_sizes is not None: | ||||||
|  |             hostkey_sizes_str = str(self._hostkey_sizes) | ||||||
|  |         if self._cakey_sizes is not None: | ||||||
|  |             cakey_sizes_str = str(self._cakey_sizes) | ||||||
|  |         if self._dh_modulus_sizes is not None: | ||||||
|  |             dh_modulus_sizes_str = str(self._dh_modulus_sizes) | ||||||
|  |  | ||||||
|         return "Name: %s\nVersion: %s\nBanner: %s\nCompressions: %s\nHost Keys: %s\nKey Exchanges: %s\nCiphers: %s\nMACs: %s" % (name, version, banner, compressions_str, host_keys_str, kex_str, ciphers_str, macs_str) |         return "Name: %s\nVersion: %s\nBanner: %s\nCompressions: %s\nHost Keys: %s\nOptional Host Keys: %s\nKey Exchanges: %s\nCiphers: %s\nMACs: %s\nHost Key Sizes: %s\nCA Key Sizes: %s\nDH Modulus Sizes: %s\nServer Policy: %r" % (name, version, banner, compressions_str, host_keys_str, optional_host_keys_str, kex_str, ciphers_str, macs_str, hostkey_sizes_str, cakey_sizes_str, dh_modulus_sizes_str, self._server_policy) | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ import traceback | |||||||
| from typing import Dict, List, Set, Sequence, Tuple, Iterable  # noqa: F401 | from typing import Dict, List, Set, Sequence, Tuple, Iterable  # noqa: F401 | ||||||
| from typing import Callable, Optional, Union, Any  # noqa: F401 | from typing import Callable, Optional, Union, Any  # noqa: F401 | ||||||
|  |  | ||||||
| from ssh_audit.globals import GITHUB_ISSUES_URL, VERSION | from ssh_audit.globals import VERSION | ||||||
| from ssh_audit.algorithm import Algorithm | from ssh_audit.algorithm import Algorithm | ||||||
| from ssh_audit.algorithms import Algorithms | from ssh_audit.algorithms import Algorithms | ||||||
| from ssh_audit.auditconf import AuditConf | from ssh_audit.auditconf import AuditConf | ||||||
| @@ -508,55 +508,22 @@ def evaluate_policy(aconf: AuditConf, banner: Optional['Banner'], client_host: O | |||||||
|  |  | ||||||
|  |  | ||||||
| def list_policies() -> None: | def list_policies() -> None: | ||||||
|  |     '''Prints a list of server & client policies.''' | ||||||
|  |  | ||||||
|     # Get a list of all the files in the policies sub-directory, relative to the path of this script. |     server_policy_names, client_policy_names = Policy.list_builtin_policies() | ||||||
|     installed_dir = os.path.dirname(os.path.abspath(__file__)) |  | ||||||
|     policies_dir = os.path.join(installed_dir, 'policies') |  | ||||||
|  |  | ||||||
|     # If the path is not a directory, print a useful error and exit. |     if len(server_policy_names) > 0: | ||||||
|     if not os.path.isdir(policies_dir): |  | ||||||
|         print("Error: could not find policies directory.  Please report this full output to <%s>:" % GITHUB_ISSUES_URL) |  | ||||||
|         print("\nsys.argv[0]: %s" % sys.argv[0]) |  | ||||||
|         print("__file__: %s" % __file__) |  | ||||||
|         print("policies_dir: %s" % policies_dir) |  | ||||||
|         sys.exit(exitcodes.UNKNOWN_ERROR) |  | ||||||
|  |  | ||||||
|     # Get a list of all the files in the policies sub-directory. |  | ||||||
|     files = [] |  | ||||||
|     for f in os.listdir(policies_dir): |  | ||||||
|         files.append(f) |  | ||||||
|  |  | ||||||
|     files.sort()  # Now the files will be in order, like 'ubuntu_client_16_04.txt', 'ubuntu_client_18_04.txt', 'ubuntu_client_20_04.txt', ... |  | ||||||
|  |  | ||||||
|     server_policies_summary = [] |  | ||||||
|     client_policies_summary = [] |  | ||||||
|     for f in files: |  | ||||||
|  |  | ||||||
|         # Load each policy, and generate a short summary from its name and absolute file path. |  | ||||||
|         policy_file = os.path.join(policies_dir, f) |  | ||||||
|         policy = Policy(policy_file=policy_file) |  | ||||||
|         policy_summary = "Name:        %s\nPolicy path: %s" % (policy.get_name_and_version(), policy_file) |  | ||||||
|  |  | ||||||
|         # We will print the server policies separately from thee client policies... |  | ||||||
|         if policy.is_server_policy(): |  | ||||||
|             server_policies_summary.append(policy_summary) |  | ||||||
|         else: |  | ||||||
|             client_policies_summary.append(policy_summary) |  | ||||||
|  |  | ||||||
|     if len(server_policies_summary) > 0: |  | ||||||
|         out.head('\nServer policies:\n') |         out.head('\nServer policies:\n') | ||||||
|         print("\n\n".join(server_policies_summary)) |         print("  * \"%s\"" % "\"\n  * \"".join(server_policy_names)) | ||||||
|         print() |  | ||||||
|  |  | ||||||
|     if len(client_policies_summary) > 0: |     if len(client_policy_names) > 0: | ||||||
|         out.head('\nClient policies:\n') |         out.head('\nClient policies:\n') | ||||||
|         print("\n\n".join(client_policies_summary)) |         print("  * \"%s\"" % "\"\n  * \"".join(client_policy_names)) | ||||||
|         print() |  | ||||||
|  |  | ||||||
|     if len(server_policies_summary) == 0 and len(client_policies_summary) == 0: |     if len(server_policy_names) == 0 and len(client_policy_names) == 0: | ||||||
|         print("Error: no built-in policies found in %s." % policies_dir) |         print("Error: no built-in policies found!") | ||||||
|     else: |     else: | ||||||
|         print("\nHint: Use -P and provide the path to a policy to run a policy scan.\n") |         print("\nHint: Use -P and provide the full name of a policy to run a policy scan with.\n") | ||||||
|  |  | ||||||
|  |  | ||||||
| def make_policy(aconf: AuditConf, banner: Optional['Banner'], kex: Optional['SSH2_Kex'], client_host: Optional[str]) -> None: | def make_policy(aconf: AuditConf, banner: Optional['Banner'], kex: Optional['SSH2_Kex'], client_host: Optional[str]) -> None: | ||||||
| @@ -685,6 +652,10 @@ def process_commandline(args: List[str], usage_cb: Callable[..., None]) -> 'Audi | |||||||
|  |  | ||||||
|     # If a policy file was provided, validate it. |     # If a policy file was provided, validate it. | ||||||
|     if (aconf.policy_file is not None) and (aconf.make_policy is False): |     if (aconf.policy_file is not None) and (aconf.make_policy is False): | ||||||
|  |  | ||||||
|  |         # First, see if this is a built-in policy name.  If not, assume a file path was provided, and try to load it from disk. | ||||||
|  |         aconf.policy = Policy.load_builtin_policy(aconf.policy_file) | ||||||
|  |         if aconf.policy is None: | ||||||
|             try: |             try: | ||||||
|                 aconf.policy = Policy(policy_file=aconf.policy_file) |                 aconf.policy = Policy(policy_file=aconf.policy_file) | ||||||
|             except Exception as e: |             except Exception as e: | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								ssh-audit.1
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								ssh-audit.1
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| .TH SSH-AUDIT 1 "July 16, 2020" | .TH SSH-AUDIT 1 "October 19, 2020" | ||||||
| .SH NAME | .SH NAME | ||||||
| \fBssh-audit\fP \- SSH server & client configuration auditor | \fBssh-audit\fP \- SSH server & client configuration auditor | ||||||
| .SH SYNOPSIS | .SH SYNOPSIS | ||||||
| @@ -59,7 +59,7 @@ Specify the minimum output level.  Default is info. | |||||||
| .TP | .TP | ||||||
| .B -L, \-\-list-policies | .B -L, \-\-list-policies | ||||||
| .br | .br | ||||||
| List all official, built-in policies for common systems.  Their file paths can then be provided using -P/--policy=<path/to/policy.txt>. | List all official, built-in policies for common systems.  Their full names can then be passed to -P/--policy. | ||||||
|  |  | ||||||
| .TP | .TP | ||||||
| .B \-\-lookup=<alg1,alg2,...> | .B \-\-lookup=<alg1,alg2,...> | ||||||
| @@ -67,7 +67,7 @@ List all official, built-in policies for common systems.  Their file paths can t | |||||||
| Look up the security information of an algorithm(s) in the internal database.  Does not connect to a server. | Look up the security information of an algorithm(s) in the internal database.  Does not connect to a server. | ||||||
|  |  | ||||||
| .TP | .TP | ||||||
| .B -M, \-\-make-policy=<policy.txt> | .B -M, \-\-make-policy=<custom_policy.txt> | ||||||
| .br | .br | ||||||
| Creates a policy based on the target server.  Useful when other servers should be compared to the target server's custom configuration (i.e.: a cluster environment).  Note that the resulting policy can be edited manually. | Creates a policy based on the target server.  Useful when other servers should be compared to the target server's custom configuration (i.e.: a cluster environment).  Note that the resulting policy can be edited manually. | ||||||
|  |  | ||||||
| @@ -82,7 +82,7 @@ Disable color output. | |||||||
| The TCP port to connect to when auditing a server, or the port to listen on when auditing a client. | The TCP port to connect to when auditing a server, or the port to listen on when auditing a client. | ||||||
|  |  | ||||||
| .TP | .TP | ||||||
| .B -P, \-\-policy=<policy.txt> | .B -P, \-\-policy=<"built-in policy name" | path/to/custom_policy.txt> | ||||||
| .br | .br | ||||||
| Runs a policy audit against a target using the specified policy (see \fBPOLICY AUDIT\fP section for detailed description of this mode of operation).  Combine with -c/--client-audit to audit a client configuration instead of a server.  Use -L/--list-policies to list all official, built-in policies for common systems. | Runs a policy audit against a target using the specified policy (see \fBPOLICY AUDIT\fP section for detailed description of this mode of operation).  Combine with -c/--client-audit to audit a client configuration instead of a server.  Use -L/--list-policies to list all official, built-in policies for common systems. | ||||||
|  |  | ||||||
| @@ -109,7 +109,7 @@ By default, \fBssh-audit\fP performs a standard audit.  That is, it enumerates a | |||||||
|  |  | ||||||
| .SH POLICY AUDIT | .SH POLICY AUDIT | ||||||
| .PP | .PP | ||||||
| When the -P/--policy=<policy.txt> option is used, \fBssh-audit\fP performs a policy audit.  The target's host key types, key exchanges, ciphers, MACs, and other information is compared to a set of expected values defined in the specified policy file.  If everything matches, only a short message stating a passing result is reported.  Otherwise, the field(s) that did not match are reported. | When the -P/--policy option is used, \fBssh-audit\fP performs a policy audit.  The target's host key types, key exchanges, ciphers, MACs, and other information is compared to a set of expected values defined in the specified policy file.  If everything matches, only a short message stating a passing result is reported.  Otherwise, the field(s) that did not match are reported. | ||||||
|  |  | ||||||
| .PP | .PP | ||||||
| Policy auditing is helpful for ensuring a group of related servers are properly hardened to an exact specification. | Policy auditing is helpful for ensuring a group of related servers are properly hardened to an exact specification. | ||||||
| @@ -140,7 +140,7 @@ ssh-audit -T servers.txt | |||||||
| .RE | .RE | ||||||
|  |  | ||||||
| .LP | .LP | ||||||
| To audit a client configuration (listens on port 2222 by default; connect using "ssh anything@localhost"): | To audit a client configuration (listens on port 2222 by default; connect using "ssh -p 2222 anything@localhost"): | ||||||
| .RS | .RS | ||||||
| .nf | .nf | ||||||
| ssh-audit -c | ssh-audit -c | ||||||
| @@ -156,7 +156,7 @@ ssh-audit -c -p 4567 | |||||||
| .RE | .RE | ||||||
|  |  | ||||||
| .LP | .LP | ||||||
| To list all official built-in policies (hint: use resulting file paths with -P/--policy): | To list all official built-in policies (hint: use their full names with -P/--policy): | ||||||
| .RS | .RS | ||||||
| .nf | .nf | ||||||
| ssh-audit -L | ssh-audit -L | ||||||
| @@ -164,10 +164,19 @@ ssh-audit -L | |||||||
| .RE | .RE | ||||||
|  |  | ||||||
| .LP | .LP | ||||||
| To run a policy audit against a server: | To run a built-in policy audit against a server (hint: use -L to see list of built-in policies): | ||||||
| .RS | .RS | ||||||
| .nf | .nf | ||||||
| ssh-audit -P path/to/server_policy targetserver | ssh-audit -P "Hardened Ubuntu Server 20.04 LTS (version 1)" targetserver | ||||||
|  | .fi | ||||||
|  | .RE | ||||||
|  |  | ||||||
|  |  | ||||||
|  | .LP | ||||||
|  | To run a custom policy audit against a server (hint: use -M/--make-policy to create a custom policy file): | ||||||
|  | .RS | ||||||
|  | .nf | ||||||
|  | ssh-audit -P path/to/server_policy.txt targetserver | ||||||
| .fi | .fi | ||||||
| .RE | .RE | ||||||
|  |  | ||||||
| @@ -175,7 +184,7 @@ ssh-audit -P path/to/server_policy targetserver | |||||||
| To run a policy audit against a client: | To run a policy audit against a client: | ||||||
| .RS | .RS | ||||||
| .nf | .nf | ||||||
| ssh-audit -c -P path/to/client_policy | ssh-audit -c -P ["policy name" | path/to/client_policy.txt] | ||||||
| .fi | .fi | ||||||
| .RE | .RE | ||||||
|  |  | ||||||
| @@ -183,7 +192,7 @@ ssh-audit -c -P path/to/client_policy | |||||||
| To run a policy audit against many servers: | To run a policy audit against many servers: | ||||||
| .RS | .RS | ||||||
| .nf | .nf | ||||||
| ssh-audit -T servers.txt -P path/to/server_policy | ssh-audit -T servers.txt -P ["policy name" | path/to/server_policy.txt] | ||||||
| .fi | .fi | ||||||
| .RE | .RE | ||||||
|  |  | ||||||
|   | |||||||
| @@ -0,0 +1 @@ | |||||||
|  | {"errors": [], "host": "localhost", "passed": true, "policy": "Hardened OpenSSH Server v8.0 (version 1)"} | ||||||
| @@ -0,0 +1,3 @@ | |||||||
|  | Host:   localhost:2222 | ||||||
|  | Policy: Hardened OpenSSH Server v8.0 (version 1) | ||||||
|  | Result: [0;32m✔ Passed[0m | ||||||
| @@ -0,0 +1 @@ | |||||||
|  | {"errors": [{"actual": ["umac-64-etm@openssh.com", "umac-128-etm@openssh.com", "hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "hmac-sha1-etm@openssh.com", "umac-64@openssh.com", "umac-128@openssh.com", "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1"], "expected_optional": [""], "expected_required": ["hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "umac-128-etm@openssh.com"], "mismatched_field": "MACs"}], "host": "localhost", "passed": false, "policy": "Hardened OpenSSH Server v8.0 (version 1)"} | ||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | Host:   localhost:2222 | ||||||
|  | Policy: Hardened OpenSSH Server v8.0 (version 1) | ||||||
|  | Result: [0;31m❌ Failed![0m | ||||||
|  | [0;33m | ||||||
|  | Errors: | ||||||
|  |   * MACs did not match. Expected: ['hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'umac-128-etm@openssh.com']; Actual: ['umac-64-etm@openssh.com', 'umac-128-etm@openssh.com', 'hmac-sha2-256-etm@openssh.com', 'hmac-sha2-512-etm@openssh.com', 'hmac-sha1-etm@openssh.com', 'umac-64@openssh.com', 'umac-128@openssh.com', 'hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'][0m | ||||||
| @@ -35,6 +35,23 @@ class TestPolicy: | |||||||
|         return self.ssh2_kex.parse(w.write_flush()) |         return self.ssh2_kex.parse(w.write_flush()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     def test_builtin_policy_consistency(self): | ||||||
|  |         '''Ensure that the BUILTIN_POLICIES struct is consistent.''' | ||||||
|  |  | ||||||
|  |         for policy_name in Policy.BUILTIN_POLICIES: | ||||||
|  |             # Ensure that the policy name ends with " (version X)", where X is the 'version' field. | ||||||
|  |             version_str = " (version %s)" % Policy.BUILTIN_POLICIES[policy_name]['version'] | ||||||
|  |             assert(policy_name.endswith(version_str)) | ||||||
|  |  | ||||||
|  |             # Ensure that each built-in policy can be loaded with Policy.load_builtin_policy(). | ||||||
|  |             assert(Policy.load_builtin_policy(policy_name) is not None) | ||||||
|  |  | ||||||
|  |         # Ensure that both server and client policy names are returned. | ||||||
|  |         server_policy_names, client_policy_names = Policy.list_builtin_policies() | ||||||
|  |         assert(len(server_policy_names) > 0) | ||||||
|  |         assert(len(client_policy_names) > 0) | ||||||
|  |  | ||||||
|  |  | ||||||
|     def test_policy_basic(self): |     def test_policy_basic(self): | ||||||
|         '''Ensure that a basic policy can be parsed correctly.''' |         '''Ensure that a basic policy can be parsed correctly.''' | ||||||
|  |  | ||||||
| @@ -49,7 +66,7 @@ ciphers = cipher_alg1, cipher_alg2, cipher_alg3 | |||||||
| macs = mac_alg1, mac_alg2, mac_alg3''' | macs = mac_alg1, mac_alg2, mac_alg3''' | ||||||
|  |  | ||||||
|         policy = self.Policy(policy_data=policy_data) |         policy = self.Policy(policy_data=policy_data) | ||||||
|         assert str(policy) == "Name: [Test Policy]\nVersion: [1]\nBanner: {undefined}\nCompressions: comp_alg1\nHost Keys: key_alg1\nKey Exchanges: kex_alg1, kex_alg2\nCiphers: cipher_alg1, cipher_alg2, cipher_alg3\nMACs: mac_alg1, mac_alg2, mac_alg3" |         assert str(policy) == "Name: [Test Policy]\nVersion: [1]\nBanner: {undefined}\nCompressions: comp_alg1\nHost Keys: key_alg1\nOptional Host Keys: {undefined}\nKey Exchanges: kex_alg1, kex_alg2\nCiphers: cipher_alg1, cipher_alg2, cipher_alg3\nMACs: mac_alg1, mac_alg2, mac_alg3\nHost Key Sizes: {undefined}\nCA Key Sizes: {undefined}\nDH Modulus Sizes: {undefined}\nServer Policy: True" | ||||||
|  |  | ||||||
|  |  | ||||||
|     def test_policy_invalid_1(self): |     def test_policy_invalid_1(self): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Joe Testa
					Joe Testa