Moved built-in policies from external files to internal database. (#75)

This commit is contained in:
Joe Testa 2020-10-19 17:27:37 -04:00
parent 2a7b9292bb
commit 046c866da4
53 changed files with 256 additions and 437 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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,11 +652,15 @@ 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):
try:
aconf.policy = Policy(policy_file=aconf.policy_file) # 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.
except Exception as e: aconf.policy = Policy.load_builtin_policy(aconf.policy_file)
print("Error while loading policy file: %s: %s" % (str(e), traceback.format_exc())) if aconf.policy is None:
sys.exit(exitcodes.UNKNOWN_ERROR) 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 the user wants to do a client audit, but provided a server policy, terminate.
if aconf.client_audit and aconf.policy.is_server_policy(): if aconf.client_audit and aconf.policy.is_server_policy():

View File

@ -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

View File

@ -0,0 +1 @@
{"errors": [], "host": "localhost", "passed": true, "policy": "Hardened OpenSSH Server v8.0 (version 1)"}

View File

@ -0,0 +1,3 @@
Host: localhost:2222
Policy: Hardened OpenSSH Server v8.0 (version 1)
Result: ✔ Passed

View File

@ -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)"}

View File

@ -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']

View File

@ -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):