From 046c866da421359f945af2b35bee1868c97ba3e6 Mon Sep 17 00:00:00 2001 From: Joe Testa Date: Mon, 19 Oct 2020 17:27:37 -0400 Subject: [PATCH] Moved built-in policies from external files to internal database. (#75) --- README.md | 12 +- docker_test.sh | 84 ++++++++---- setup.cfg | 3 - src/ssh_audit/policies/openssh_7_7.txt | 24 ---- src/ssh_audit/policies/openssh_7_8.txt | 24 ---- src/ssh_audit/policies/openssh_7_9.txt | 24 ---- src/ssh_audit/policies/openssh_8_0.txt | 24 ---- src/ssh_audit/policies/openssh_8_1.txt | 24 ---- src/ssh_audit/policies/openssh_8_2.txt | 28 ---- src/ssh_audit/policies/openssh_8_3.txt | 28 ---- src/ssh_audit/policies/openssh_8_4.txt | 28 ---- .../policies/ubuntu_client_16_04.txt | 19 --- .../policies/ubuntu_client_18_04.txt | 19 --- .../policies/ubuntu_client_20_04.txt | 19 --- .../policies/ubuntu_server_16_04.txt | 24 ---- .../policies/ubuntu_server_18_04.txt | 24 ---- .../policies/ubuntu_server_20_04.txt | 28 ---- src/ssh_audit/policy.py | 129 ++++++++++++++++-- src/ssh_audit/ssh_audit.py | 67 +++------ ssh-audit.1 | 31 +++-- ...=> openssh_5.6p1_custom_policy_test1.json} | 0 ... => openssh_5.6p1_custom_policy_test1.txt} | 0 ...> openssh_5.6p1_custom_policy_test10.json} | 0 ...=> openssh_5.6p1_custom_policy_test10.txt} | 0 ...=> openssh_5.6p1_custom_policy_test2.json} | 0 ... => openssh_5.6p1_custom_policy_test2.txt} | 0 ...=> openssh_5.6p1_custom_policy_test3.json} | 0 ... => openssh_5.6p1_custom_policy_test3.txt} | 0 ...=> openssh_5.6p1_custom_policy_test4.json} | 0 ... => openssh_5.6p1_custom_policy_test4.txt} | 0 ...=> openssh_5.6p1_custom_policy_test5.json} | 0 ... => openssh_5.6p1_custom_policy_test5.txt} | 0 ...=> openssh_5.6p1_custom_policy_test7.json} | 0 ... => openssh_5.6p1_custom_policy_test7.txt} | 0 ...=> openssh_5.6p1_custom_policy_test8.json} | 0 ... => openssh_5.6p1_custom_policy_test8.txt} | 0 ...=> openssh_5.6p1_custom_policy_test9.json} | 0 ... => openssh_5.6p1_custom_policy_test9.txt} | 0 .../openssh_8.0p1_builtin_policy_test1.json | 1 + .../openssh_8.0p1_builtin_policy_test1.txt | 3 + .../openssh_8.0p1_builtin_policy_test2.json | 1 + .../openssh_8.0p1_builtin_policy_test2.txt | 6 + ...> openssh_8.0p1_custom_policy_test11.json} | 0 ...=> openssh_8.0p1_custom_policy_test11.txt} | 0 ...> openssh_8.0p1_custom_policy_test12.json} | 0 ...=> openssh_8.0p1_custom_policy_test12.txt} | 0 ...> openssh_8.0p1_custom_policy_test13.json} | 0 ...=> openssh_8.0p1_custom_policy_test13.txt} | 0 ...> openssh_8.0p1_custom_policy_test14.json} | 0 ...=> openssh_8.0p1_custom_policy_test14.txt} | 0 ...=> openssh_8.0p1_custom_policy_test6.json} | 0 ... => openssh_8.0p1_custom_policy_test6.txt} | 0 test/test_policy.py | 19 ++- 53 files changed, 256 insertions(+), 437 deletions(-) delete mode 100644 src/ssh_audit/policies/openssh_7_7.txt delete mode 100644 src/ssh_audit/policies/openssh_7_8.txt delete mode 100644 src/ssh_audit/policies/openssh_7_9.txt delete mode 100644 src/ssh_audit/policies/openssh_8_0.txt delete mode 100644 src/ssh_audit/policies/openssh_8_1.txt delete mode 100644 src/ssh_audit/policies/openssh_8_2.txt delete mode 100644 src/ssh_audit/policies/openssh_8_3.txt delete mode 100644 src/ssh_audit/policies/openssh_8_4.txt delete mode 100644 src/ssh_audit/policies/ubuntu_client_16_04.txt delete mode 100644 src/ssh_audit/policies/ubuntu_client_18_04.txt delete mode 100644 src/ssh_audit/policies/ubuntu_client_20_04.txt delete mode 100644 src/ssh_audit/policies/ubuntu_server_16_04.txt delete mode 100644 src/ssh_audit/policies/ubuntu_server_18_04.txt delete mode 100644 src/ssh_audit/policies/ubuntu_server_20_04.txt rename test/docker/expected_results/{openssh_5.6p1_policy_test1.json => openssh_5.6p1_custom_policy_test1.json} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test1.txt => openssh_5.6p1_custom_policy_test1.txt} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test10.json => openssh_5.6p1_custom_policy_test10.json} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test10.txt => openssh_5.6p1_custom_policy_test10.txt} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test2.json => openssh_5.6p1_custom_policy_test2.json} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test2.txt => openssh_5.6p1_custom_policy_test2.txt} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test3.json => openssh_5.6p1_custom_policy_test3.json} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test3.txt => openssh_5.6p1_custom_policy_test3.txt} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test4.json => openssh_5.6p1_custom_policy_test4.json} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test4.txt => openssh_5.6p1_custom_policy_test4.txt} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test5.json => openssh_5.6p1_custom_policy_test5.json} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test5.txt => openssh_5.6p1_custom_policy_test5.txt} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test7.json => openssh_5.6p1_custom_policy_test7.json} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test7.txt => openssh_5.6p1_custom_policy_test7.txt} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test8.json => openssh_5.6p1_custom_policy_test8.json} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test8.txt => openssh_5.6p1_custom_policy_test8.txt} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test9.json => openssh_5.6p1_custom_policy_test9.json} (100%) rename test/docker/expected_results/{openssh_5.6p1_policy_test9.txt => openssh_5.6p1_custom_policy_test9.txt} (100%) create mode 100644 test/docker/expected_results/openssh_8.0p1_builtin_policy_test1.json create mode 100644 test/docker/expected_results/openssh_8.0p1_builtin_policy_test1.txt create mode 100644 test/docker/expected_results/openssh_8.0p1_builtin_policy_test2.json create mode 100644 test/docker/expected_results/openssh_8.0p1_builtin_policy_test2.txt rename test/docker/expected_results/{openssh_8.0p1_policy_test11.json => openssh_8.0p1_custom_policy_test11.json} (100%) rename test/docker/expected_results/{openssh_8.0p1_policy_test11.txt => openssh_8.0p1_custom_policy_test11.txt} (100%) rename test/docker/expected_results/{openssh_8.0p1_policy_test12.json => openssh_8.0p1_custom_policy_test12.json} (100%) rename test/docker/expected_results/{openssh_8.0p1_policy_test12.txt => openssh_8.0p1_custom_policy_test12.txt} (100%) rename test/docker/expected_results/{openssh_8.0p1_policy_test13.json => openssh_8.0p1_custom_policy_test13.json} (100%) rename test/docker/expected_results/{openssh_8.0p1_policy_test13.txt => openssh_8.0p1_custom_policy_test13.txt} (100%) rename test/docker/expected_results/{openssh_8.0p1_policy_test14.json => openssh_8.0p1_custom_policy_test14.json} (100%) rename test/docker/expected_results/{openssh_8.0p1_policy_test14.txt => openssh_8.0p1_custom_policy_test14.txt} (100%) rename test/docker/expected_results/{openssh_8.0p1_policy_test6.json => openssh_8.0p1_custom_policy_test6.json} (100%) rename test/docker/expected_results/{openssh_8.0p1_policy_test6.txt => openssh_8.0p1_custom_policy_test6.txt} (100%) diff --git a/README.md b/README.md index 4c7ae74..b414afd 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ usage: ssh-audit.py [options] adhere to) -n, --no-colors disable colors -p, --port= port to connect - -P, --policy= run a policy test using the specified policy + -P, --policy=<"policy name" | policy.txt> run a policy test using the + specified policy -t, --timeout= timeout (in seconds) for connection and reading (default: 5) -T, --targets= a file containing a list of target hosts (one @@ -92,17 +93,17 @@ ssh-audit -L 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: ``` -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: ``` -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): @@ -151,8 +152,9 @@ For convenience, a web front-end on top of the command-line tool is available at ## ChangeLog ### 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). - - 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`. ### v2.3.0 (2020-09-27) diff --git a/docker_test.sh b/docker_test.sh index ebf92b5..6f482f8 100755 --- a/docker_test.sh +++ b/docker_test.sh @@ -489,8 +489,25 @@ function run_test { 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. test_number=$2 # The policy test number to run. 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}" policy_path="test/docker/policies/policy_${test_number}.txt" - test_result_stdout="${TEST_RESULT_DIR}/openssh_${version}_policy_${test_number}.txt" - test_result_json="${TEST_RESULT_DIR}/openssh_${version}_policy_${test_number}.json" - expected_result_stdout="test/docker/expected_results/openssh_${version}_policy_${test_number}.txt" - expected_result_json="test/docker/expected_results/openssh_${version}_policy_${test_number}.json" - test_name="OpenSSH ${version} policy ${test_number}" + test_result_stdout="${TEST_RESULT_DIR}/openssh_${version}_custom_policy_${test_number}.txt" + test_result_json="${TEST_RESULT_DIR}/openssh_${version}_custom_policy_${test_number}.json" + expected_result_stdout="test/docker/expected_results/openssh_${version}_custom_policy_${test_number}.txt" + expected_result_json="test/docker/expected_results/openssh_${version}_custom_policy_${test_number}.json" + 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}" cid=`docker run -d -p 2222:22 ${IMAGE_NAME}:${IMAGE_VERSION} ${server_exec}` @@ -523,8 +553,8 @@ function run_policy_test { exit 1 fi - #echo "Running: ./ssh-audit.py -P ${policy_path} localhost:2222 > ${test_result_stdout}" - ./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} actual_exit_code=$? 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" @@ -533,8 +563,8 @@ function run_policy_test { exit 1 fi - #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} + #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} actual_exit_code=$? 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" @@ -627,36 +657,42 @@ echo run_tinyssh_test '20190101' 'test1' $PROGRAM_RETVAL_WARNING echo echo -run_policy_test 'config1' 'test1' $PROGRAM_RETVAL_GOOD -run_policy_test 'config1' 'test2' $PROGRAM_RETVAL_FAILURE -run_policy_test 'config1' 'test3' $PROGRAM_RETVAL_FAILURE -run_policy_test 'config1' 'test4' $PROGRAM_RETVAL_FAILURE -run_policy_test 'config1' 'test5' $PROGRAM_RETVAL_FAILURE -run_policy_test 'config2' 'test6' $PROGRAM_RETVAL_GOOD +run_custom_policy_test 'config1' 'test1' $PROGRAM_RETVAL_GOOD +run_custom_policy_test 'config1' 'test2' $PROGRAM_RETVAL_FAILURE +run_custom_policy_test 'config1' 'test3' $PROGRAM_RETVAL_FAILURE +run_custom_policy_test 'config1' 'test4' $PROGRAM_RETVAL_FAILURE +run_custom_policy_test 'config1' 'test5' $PROGRAM_RETVAL_FAILURE +run_custom_policy_test 'config2' 'test6' $PROGRAM_RETVAL_GOOD # 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. -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. -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. -run_policy_test 'config3' 'test10' $PROGRAM_RETVAL_FAILURE +run_custom_policy_test 'config3' 'test10' $PROGRAM_RETVAL_FAILURE # 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. -run_policy_test 'config2' 'test12' $PROGRAM_RETVAL_FAILURE +run_custom_policy_test 'config2' 'test12' $PROGRAM_RETVAL_FAILURE # 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. -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, diff --git a/setup.cfg b/setup.cfg index 68c4018..cbc9976 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,9 +36,6 @@ python_requires = >=3.5,<4 [options.packages.find] where = src -[options.package_data] -ssh_audit = policies/* - [options.entry_points] console_scripts = ssh-audit = ssh_audit.ssh_audit:main diff --git a/src/ssh_audit/policies/openssh_7_7.txt b/src/ssh_audit/policies/openssh_7_7.txt deleted file mode 100644 index 41d5ba5..0000000 --- a/src/ssh_audit/policies/openssh_7_7.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policies/openssh_7_8.txt b/src/ssh_audit/policies/openssh_7_8.txt deleted file mode 100644 index d2505b5..0000000 --- a/src/ssh_audit/policies/openssh_7_8.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policies/openssh_7_9.txt b/src/ssh_audit/policies/openssh_7_9.txt deleted file mode 100644 index 0d164d3..0000000 --- a/src/ssh_audit/policies/openssh_7_9.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policies/openssh_8_0.txt b/src/ssh_audit/policies/openssh_8_0.txt deleted file mode 100644 index 5843323..0000000 --- a/src/ssh_audit/policies/openssh_8_0.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policies/openssh_8_1.txt b/src/ssh_audit/policies/openssh_8_1.txt deleted file mode 100644 index e25baf0..0000000 --- a/src/ssh_audit/policies/openssh_8_1.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policies/openssh_8_2.txt b/src/ssh_audit/policies/openssh_8_2.txt deleted file mode 100644 index 142c1bd..0000000 --- a/src/ssh_audit/policies/openssh_8_2.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policies/openssh_8_3.txt b/src/ssh_audit/policies/openssh_8_3.txt deleted file mode 100644 index b936460..0000000 --- a/src/ssh_audit/policies/openssh_8_3.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policies/openssh_8_4.txt b/src/ssh_audit/policies/openssh_8_4.txt deleted file mode 100644 index ef34152..0000000 --- a/src/ssh_audit/policies/openssh_8_4.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policies/ubuntu_client_16_04.txt b/src/ssh_audit/policies/ubuntu_client_16_04.txt deleted file mode 100644 index 7a8565d..0000000 --- a/src/ssh_audit/policies/ubuntu_client_16_04.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policies/ubuntu_client_18_04.txt b/src/ssh_audit/policies/ubuntu_client_18_04.txt deleted file mode 100644 index a1e15cd..0000000 --- a/src/ssh_audit/policies/ubuntu_client_18_04.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policies/ubuntu_client_20_04.txt b/src/ssh_audit/policies/ubuntu_client_20_04.txt deleted file mode 100644 index f188e53..0000000 --- a/src/ssh_audit/policies/ubuntu_client_20_04.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policies/ubuntu_server_16_04.txt b/src/ssh_audit/policies/ubuntu_server_16_04.txt deleted file mode 100644 index 8fe618c..0000000 --- a/src/ssh_audit/policies/ubuntu_server_16_04.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policies/ubuntu_server_18_04.txt b/src/ssh_audit/policies/ubuntu_server_18_04.txt deleted file mode 100644 index a9b6bd6..0000000 --- a/src/ssh_audit/policies/ubuntu_server_18_04.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policies/ubuntu_server_20_04.txt b/src/ssh_audit/policies/ubuntu_server_20_04.txt deleted file mode 100644 index 49db45c..0000000 --- a/src/ssh_audit/policies/ubuntu_server_20_04.txt +++ /dev/null @@ -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 diff --git a/src/ssh_audit/policy.py b/src/ssh_audit/policy.py index 62bd24f..c73509f 100644 --- a/src/ssh_audit/policy.py +++ b/src/ssh_audit/policy.py @@ -22,7 +22,7 @@ THE SOFTWARE. """ from typing import Dict, List, Tuple -from typing import Optional, Any +from typing import Optional, Any, Union, cast from datetime import date 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 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._version = 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._server_policy = True - if (policy_file is None) and (policy_data is None): - raise RuntimeError('policy_file and policy_data must not both be None.') - elif (policy_file is not None) and (policy_data is not None): - raise RuntimeError('policy_file and policy_data must not both be specified.') + self._name_and_version = '' # type: str + + # Ensure that only one mode was 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: with open(policy_file, "r") as f: @@ -142,6 +196,8 @@ class Policy: if self._version is None: raise ValueError('The policy does not have a version field.') + self._name_and_version = "%s (version %s)" % (self._name, self._version) + @staticmethod 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: '''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: @@ -347,6 +403,50 @@ macs = %s 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 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.''' @@ -367,9 +467,14 @@ macs = %s banner = undefined compressions_str = undefined host_keys_str = undefined + optional_host_keys_str = undefined kex_str = undefined ciphers_str = undefined macs_str = undefined + hostkey_sizes_str = undefined + cakey_sizes_str = undefined + dh_modulus_sizes_str = undefined + if self._name is not None: name = '[%s]' % self._name @@ -382,11 +487,19 @@ macs = %s compressions_str = ', '.join(self._compressions) if self._host_keys is not None: 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: kex_str = ', '.join(self._kex) if self._ciphers is not None: ciphers_str = ', '.join(self._ciphers) if self._macs is not None: 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) diff --git a/src/ssh_audit/ssh_audit.py b/src/ssh_audit/ssh_audit.py index 61e95b1..344498c 100755 --- a/src/ssh_audit/ssh_audit.py +++ b/src/ssh_audit/ssh_audit.py @@ -33,7 +33,7 @@ import traceback from typing import Dict, List, Set, Sequence, Tuple, Iterable # 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.algorithms import Algorithms 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: + '''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. - installed_dir = os.path.dirname(os.path.abspath(__file__)) - policies_dir = os.path.join(installed_dir, 'policies') + server_policy_names, client_policy_names = Policy.list_builtin_policies() - # If the path is not a directory, print a useful error and exit. - 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: + if len(server_policy_names) > 0: out.head('\nServer policies:\n') - print("\n\n".join(server_policies_summary)) - print() + print(" * \"%s\"" % "\"\n * \"".join(server_policy_names)) - if len(client_policies_summary) > 0: + if len(client_policy_names) > 0: out.head('\nClient policies:\n') - print("\n\n".join(client_policies_summary)) - print() + print(" * \"%s\"" % "\"\n * \"".join(client_policy_names)) - if len(server_policies_summary) == 0 and len(client_policies_summary) == 0: - print("Error: no built-in policies found in %s." % policies_dir) + if len(server_policy_names) == 0 and len(client_policy_names) == 0: + print("Error: no built-in policies found!") 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: @@ -685,11 +652,15 @@ def process_commandline(args: List[str], usage_cb: Callable[..., None]) -> 'Audi # If a policy file was provided, validate it. if (aconf.policy_file is not None) and (aconf.make_policy is False): - try: - aconf.policy = Policy(policy_file=aconf.policy_file) - except Exception as e: - print("Error while loading policy file: %s: %s" % (str(e), traceback.format_exc())) - sys.exit(exitcodes.UNKNOWN_ERROR) + + # 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: + aconf.policy = Policy(policy_file=aconf.policy_file) + except Exception as e: + print("Error while loading policy file: %s: %s" % (str(e), traceback.format_exc())) + sys.exit(exitcodes.UNKNOWN_ERROR) # If the user wants to do a client audit, but provided a server policy, terminate. if aconf.client_audit and aconf.policy.is_server_policy(): diff --git a/ssh-audit.1 b/ssh-audit.1 index b292032..7d8f2e5 100644 --- a/ssh-audit.1 +++ b/ssh-audit.1 @@ -1,4 +1,4 @@ -.TH SSH-AUDIT 1 "July 16, 2020" +.TH SSH-AUDIT 1 "October 19, 2020" .SH NAME \fBssh-audit\fP \- SSH server & client configuration auditor .SH SYNOPSIS @@ -59,7 +59,7 @@ Specify the minimum output level. Default is info. .TP .B -L, \-\-list-policies .br -List all official, built-in policies for common systems. Their file paths can then be provided using -P/--policy=. +List all official, built-in policies for common systems. Their full names can then be passed to -P/--policy. .TP .B \-\-lookup= @@ -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. .TP -.B -M, \-\-make-policy= +.B -M, \-\-make-policy= .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. @@ -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. .TP -.B -P, \-\-policy= +.B -P, \-\-policy=<"built-in policy name" | path/to/custom_policy.txt> .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. @@ -109,7 +109,7 @@ By default, \fBssh-audit\fP performs a standard audit. That is, it enumerates a .SH POLICY AUDIT .PP -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. +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 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 .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 .nf ssh-audit -c @@ -156,7 +156,7 @@ ssh-audit -c -p 4567 .RE .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 .nf ssh-audit -L @@ -164,10 +164,19 @@ ssh-audit -L .RE .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 .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 .RE @@ -175,7 +184,7 @@ ssh-audit -P path/to/server_policy targetserver To run a policy audit against a client: .RS .nf -ssh-audit -c -P path/to/client_policy +ssh-audit -c -P ["policy name" | path/to/client_policy.txt] .fi .RE @@ -183,7 +192,7 @@ ssh-audit -c -P path/to/client_policy To run a policy audit against many servers: .RS .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 .RE diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test1.json b/test/docker/expected_results/openssh_5.6p1_custom_policy_test1.json similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test1.json rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test1.json diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test1.txt b/test/docker/expected_results/openssh_5.6p1_custom_policy_test1.txt similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test1.txt rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test1.txt diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test10.json b/test/docker/expected_results/openssh_5.6p1_custom_policy_test10.json similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test10.json rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test10.json diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test10.txt b/test/docker/expected_results/openssh_5.6p1_custom_policy_test10.txt similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test10.txt rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test10.txt diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test2.json b/test/docker/expected_results/openssh_5.6p1_custom_policy_test2.json similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test2.json rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test2.json diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test2.txt b/test/docker/expected_results/openssh_5.6p1_custom_policy_test2.txt similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test2.txt rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test2.txt diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test3.json b/test/docker/expected_results/openssh_5.6p1_custom_policy_test3.json similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test3.json rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test3.json diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test3.txt b/test/docker/expected_results/openssh_5.6p1_custom_policy_test3.txt similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test3.txt rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test3.txt diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test4.json b/test/docker/expected_results/openssh_5.6p1_custom_policy_test4.json similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test4.json rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test4.json diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test4.txt b/test/docker/expected_results/openssh_5.6p1_custom_policy_test4.txt similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test4.txt rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test4.txt diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test5.json b/test/docker/expected_results/openssh_5.6p1_custom_policy_test5.json similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test5.json rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test5.json diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test5.txt b/test/docker/expected_results/openssh_5.6p1_custom_policy_test5.txt similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test5.txt rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test5.txt diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test7.json b/test/docker/expected_results/openssh_5.6p1_custom_policy_test7.json similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test7.json rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test7.json diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test7.txt b/test/docker/expected_results/openssh_5.6p1_custom_policy_test7.txt similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test7.txt rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test7.txt diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test8.json b/test/docker/expected_results/openssh_5.6p1_custom_policy_test8.json similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test8.json rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test8.json diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test8.txt b/test/docker/expected_results/openssh_5.6p1_custom_policy_test8.txt similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test8.txt rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test8.txt diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test9.json b/test/docker/expected_results/openssh_5.6p1_custom_policy_test9.json similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test9.json rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test9.json diff --git a/test/docker/expected_results/openssh_5.6p1_policy_test9.txt b/test/docker/expected_results/openssh_5.6p1_custom_policy_test9.txt similarity index 100% rename from test/docker/expected_results/openssh_5.6p1_policy_test9.txt rename to test/docker/expected_results/openssh_5.6p1_custom_policy_test9.txt diff --git a/test/docker/expected_results/openssh_8.0p1_builtin_policy_test1.json b/test/docker/expected_results/openssh_8.0p1_builtin_policy_test1.json new file mode 100644 index 0000000..f907cde --- /dev/null +++ b/test/docker/expected_results/openssh_8.0p1_builtin_policy_test1.json @@ -0,0 +1 @@ +{"errors": [], "host": "localhost", "passed": true, "policy": "Hardened OpenSSH Server v8.0 (version 1)"} diff --git a/test/docker/expected_results/openssh_8.0p1_builtin_policy_test1.txt b/test/docker/expected_results/openssh_8.0p1_builtin_policy_test1.txt new file mode 100644 index 0000000..1b6ddee --- /dev/null +++ b/test/docker/expected_results/openssh_8.0p1_builtin_policy_test1.txt @@ -0,0 +1,3 @@ +Host: localhost:2222 +Policy: Hardened OpenSSH Server v8.0 (version 1) +Result: ✔ Passed diff --git a/test/docker/expected_results/openssh_8.0p1_builtin_policy_test2.json b/test/docker/expected_results/openssh_8.0p1_builtin_policy_test2.json new file mode 100644 index 0000000..49a13bc --- /dev/null +++ b/test/docker/expected_results/openssh_8.0p1_builtin_policy_test2.json @@ -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)"} diff --git a/test/docker/expected_results/openssh_8.0p1_builtin_policy_test2.txt b/test/docker/expected_results/openssh_8.0p1_builtin_policy_test2.txt new file mode 100644 index 0000000..28d6575 --- /dev/null +++ b/test/docker/expected_results/openssh_8.0p1_builtin_policy_test2.txt @@ -0,0 +1,6 @@ +Host: localhost:2222 +Policy: Hardened OpenSSH Server v8.0 (version 1) +Result: ❌ Failed! + +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'] diff --git a/test/docker/expected_results/openssh_8.0p1_policy_test11.json b/test/docker/expected_results/openssh_8.0p1_custom_policy_test11.json similarity index 100% rename from test/docker/expected_results/openssh_8.0p1_policy_test11.json rename to test/docker/expected_results/openssh_8.0p1_custom_policy_test11.json diff --git a/test/docker/expected_results/openssh_8.0p1_policy_test11.txt b/test/docker/expected_results/openssh_8.0p1_custom_policy_test11.txt similarity index 100% rename from test/docker/expected_results/openssh_8.0p1_policy_test11.txt rename to test/docker/expected_results/openssh_8.0p1_custom_policy_test11.txt diff --git a/test/docker/expected_results/openssh_8.0p1_policy_test12.json b/test/docker/expected_results/openssh_8.0p1_custom_policy_test12.json similarity index 100% rename from test/docker/expected_results/openssh_8.0p1_policy_test12.json rename to test/docker/expected_results/openssh_8.0p1_custom_policy_test12.json diff --git a/test/docker/expected_results/openssh_8.0p1_policy_test12.txt b/test/docker/expected_results/openssh_8.0p1_custom_policy_test12.txt similarity index 100% rename from test/docker/expected_results/openssh_8.0p1_policy_test12.txt rename to test/docker/expected_results/openssh_8.0p1_custom_policy_test12.txt diff --git a/test/docker/expected_results/openssh_8.0p1_policy_test13.json b/test/docker/expected_results/openssh_8.0p1_custom_policy_test13.json similarity index 100% rename from test/docker/expected_results/openssh_8.0p1_policy_test13.json rename to test/docker/expected_results/openssh_8.0p1_custom_policy_test13.json diff --git a/test/docker/expected_results/openssh_8.0p1_policy_test13.txt b/test/docker/expected_results/openssh_8.0p1_custom_policy_test13.txt similarity index 100% rename from test/docker/expected_results/openssh_8.0p1_policy_test13.txt rename to test/docker/expected_results/openssh_8.0p1_custom_policy_test13.txt diff --git a/test/docker/expected_results/openssh_8.0p1_policy_test14.json b/test/docker/expected_results/openssh_8.0p1_custom_policy_test14.json similarity index 100% rename from test/docker/expected_results/openssh_8.0p1_policy_test14.json rename to test/docker/expected_results/openssh_8.0p1_custom_policy_test14.json diff --git a/test/docker/expected_results/openssh_8.0p1_policy_test14.txt b/test/docker/expected_results/openssh_8.0p1_custom_policy_test14.txt similarity index 100% rename from test/docker/expected_results/openssh_8.0p1_policy_test14.txt rename to test/docker/expected_results/openssh_8.0p1_custom_policy_test14.txt diff --git a/test/docker/expected_results/openssh_8.0p1_policy_test6.json b/test/docker/expected_results/openssh_8.0p1_custom_policy_test6.json similarity index 100% rename from test/docker/expected_results/openssh_8.0p1_policy_test6.json rename to test/docker/expected_results/openssh_8.0p1_custom_policy_test6.json diff --git a/test/docker/expected_results/openssh_8.0p1_policy_test6.txt b/test/docker/expected_results/openssh_8.0p1_custom_policy_test6.txt similarity index 100% rename from test/docker/expected_results/openssh_8.0p1_policy_test6.txt rename to test/docker/expected_results/openssh_8.0p1_custom_policy_test6.txt diff --git a/test/test_policy.py b/test/test_policy.py index 857afdc..b78fab2 100644 --- a/test/test_policy.py +++ b/test/test_policy.py @@ -35,6 +35,23 @@ class TestPolicy: 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): '''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''' 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):