diff --git a/roles/mail_server/molecule/default/tests/test_optional.py b/roles/mail_server/molecule/default/tests/test_optional.py index ba5f621400cdf1f0246d4e8c03498b0146b2b0d3..e562d7fcee637adc541956c4c0a4a160c54f25a0 100644 --- a/roles/mail_server/molecule/default/tests/test_optional.py +++ b/roles/mail_server/molecule/default/tests/test_optional.py @@ -3,6 +3,10 @@ import re import time import uuid +import defusedxml.ElementTree as ElementTree + +import pytest + import testinfra.utils.ansible_runner @@ -129,12 +133,11 @@ def test_mail_owner(host): assert user.groups == ["virtmail"] -def test_imap_tls_configuration(host): +def test_imap_tls_connectivity(host): """ - Tests TLS configuration for IMAP in Dovecot. + Tests connectivity over STARTTLS/TLS towards IMAP server. """ - # Test plain connectivity first. starttls = host.run('echo "a0001 LOGOUT" | openssl s_client -quiet -starttls imap -connect parameters-optional:143') assert starttls.rc == 0 assert '* BYE Logging out' in starttls.stdout @@ -143,40 +146,53 @@ def test_imap_tls_configuration(host): assert tls.rc == 0 assert '* BYE Logging out' in starttls.stdout - # Test TLS protocol versions. - starttls = host.run('echo "a0001 LOGOUT" | openssl s_client -quiet -starttls imap -no_tls1_2 -connect parameters-optional:143') - assert starttls.rc == 0 - assert '* BYE Logging out' in starttls.stdout - tls = host.run('echo "a0001 LOGOUT" | openssl s_client -quiet -no_tls1_2 -connect parameters-optional:993') - assert tls.rc == 0 - assert '* BYE Logging out' in starttls.stdout +@pytest.mark.parametrize("port", [ + 143, + 993, + 587, +]) +def test_imap_and_smtp_submission_tls_version_and_ciphers(host, port): + """ + Tests if the correct TLS version and ciphers have been enabled for + IMAP and SMTP submission. + """ + + expected_tls_versions = ["TLSv1.1", "TLSv1.2"] + + expected_tls_ciphers = [ + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + ] - starttls = host.run("echo 'a0001 LOGOUT' | openssl s_client -quiet -starttls imap -no_tls1_1 -no_tls1_2 -connect parameters-optional:143") - assert starttls.rc != 0 - assert 'SSL alert number 70' in starttls.stderr + # Run the nmap scanner against the server, and fetch the results. + nmap = host.run("nmap -sV --script ssl-enum-ciphers -p %s localhost -oX /tmp/report.xml", str(port)) + assert nmap.rc == 0 + report_content = host.file('/tmp/report.xml').content_string - tls = host.run("echo 'a0001 LOGOUT' | openssl s_client -quiet -no_tls1_1 -no_tls1_2 -connect parameters-optional:993") - assert tls.rc != 0 - assert 'SSL alert number 70' in tls.stderr + report_root = ElementTree.fromstring(report_content) - # Test at least one strong TLS cipher. - starttls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -starttls imap -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-optional:143") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in starttls_cipher.stdout + tls_versions = [] + tls_ciphers = set() - tls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-optional:993") - assert tls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in tls_cipher.stdout + for child in report_root.findall("./host/ports/port/script/table"): + tls_versions.append(child.attrib['key']) - # Test weaker TLS cipher that was explicitly configured - starttls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -starttls imap -cipher ECDHE-RSA-AES128-SHA -connect parameters-optional:143") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA" in starttls_cipher.stdout + for child in report_root.findall(".//table[@key='ciphers']/table/elem[@key='name']"): + tls_ciphers.add(child.text) - tls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -cipher ECDHE-RSA-AES128-SHA -connect parameters-optional:993") - assert tls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA" in tls_cipher.stdout + tls_versions.sort() + tls_ciphers = sorted(list(tls_ciphers)) + + assert tls_versions == expected_tls_versions + assert tls_ciphers == expected_tls_ciphers def test_dovecot_postmaster(host): @@ -205,54 +221,106 @@ def test_imap_max_user_connections_per_ip(host): assert " mail_max_userip_connections = 2" in config.stdout -def test_postfix_tls_configuration(host): +def test_smtp_tls_connectivity(host): """ - Tests TLS configuration for SMTP in Postfix. + Tests connectivity over default/submission port towards SMTP + server. """ - # Test TLS protocol versions for default port (all should be enabled). - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1 -no_tls1_1 -connect parameters-optional:25") - assert starttls.rc == 0 - assert '221 2.0.0 Bye' in starttls.stdout - - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1_2 -connect parameters-optional:25") - assert starttls.rc == 0 - assert '221 2.0.0 Bye' in starttls.stdout - - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1_2 -no_tls1_1 -connect parameters-optional:25") - assert starttls.rc == 0 - assert '221 2.0.0 Bye' in starttls.stdout - - # Test TLS protocol versions for submission port (only TLS 1.1 and TLS 1.2 should be enabled). - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -connect parameters-optional:587") + starttls = host.run('echo "QUIT" | openssl s_client -quiet -starttls smtp -connect parameters-optional:25') assert starttls.rc == 0 assert '221 2.0.0 Bye' in starttls.stdout - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1_2 -connect parameters-optional:587") - assert starttls.rc == 0 + tls = host.run('echo "QUIT" | openssl s_client -quiet -starttls smtp -connect parameters-optional:587') + assert tls.rc == 0 assert '221 2.0.0 Bye' in starttls.stdout - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -no_tls1_1 -no_tls1_2 -connect parameters-optional:587") - assert starttls.rc != 0 - assert 'SSL alert number 70' in starttls.stderr - - # Test ciphers for default port (less restrictive). - starttls_cipher = host.run("echo 'QUIT' | openssl s_client -starttls smtp -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-optional:25") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in starttls_cipher.stdout - starttls_cipher = host.run("echo 'QUIT' | openssl s_client -starttls smtp -cipher ECDHE-RSA-AES128-SHA -connect parameters-optional:25") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA" in starttls_cipher.stdout - - # Test ciphers for submission port (at least one weak cipher was configured). - starttls_cipher = host.run("echo 'QUIT' | openssl s_client -starttls smtp -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-optional:587") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in starttls_cipher.stdout +def test_smtp_default_port_tls_version_and_ciphers(host): + """ + Tests TLS configuration for SMTP default port (needs to be less + restrictive for interoperability purposes). + """ - starttls_cipher = host.run("echo 'QUIT' | openssl s_client -starttls smtp -cipher ECDHE-RSA-AES128-SHA -connect parameters-optional:587") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA" in starttls_cipher.stdout + expected_tls_versions = ["TLSv1.0", "TLSv1.1", "TLSv1.2"] + + expected_tls_ciphers = [ + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_128_CCM", + "TLS_DHE_RSA_WITH_AES_128_CCM_8", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_AES_256_CCM", + "TLS_DHE_RSA_WITH_AES_256_CCM_8", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA", + "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA", + "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_DHE_RSA_WITH_SEED_CBC_SHA", + "TLS_DH_anon_WITH_AES_128_CBC_SHA", + "TLS_DH_anon_WITH_AES_128_CBC_SHA256", + "TLS_DH_anon_WITH_AES_128_GCM_SHA256", + "TLS_DH_anon_WITH_AES_256_CBC_SHA", + "TLS_DH_anon_WITH_AES_256_CBC_SHA256", + "TLS_DH_anon_WITH_AES_256_GCM_SHA384", + "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA", + "TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA", + "TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256", + "TLS_DH_anon_WITH_SEED_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDH_anon_WITH_AES_128_CBC_SHA", + "TLS_ECDH_anon_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_CCM", + "TLS_RSA_WITH_AES_128_CCM_8", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_256_CBC_SHA256", + "TLS_RSA_WITH_AES_256_CCM", + "TLS_RSA_WITH_AES_256_CCM_8", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA", + "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256", + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA", + "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256", + "TLS_RSA_WITH_SEED_CBC_SHA", + ] + + # Run the nmap scanner against the server, and fetch the results. + nmap = host.run("nmap -sV --script ssl-enum-ciphers -p 25 localhost -oX /tmp/report.xml") + assert nmap.rc == 0 + report_content = host.file('/tmp/report.xml').content_string + + report_root = ElementTree.fromstring(report_content) + + tls_versions = [] + tls_ciphers = set() + + for child in report_root.findall("./host/ports/port/script/table"): + tls_versions.append(child.attrib['key']) + + for child in report_root.findall(".//table[@key='ciphers']/table/elem[@key='name']"): + tls_ciphers.add(child.text) + + tls_versions.sort() + tls_ciphers = sorted(list(tls_ciphers)) + + assert tls_versions == expected_tls_versions + assert tls_ciphers == expected_tls_ciphers def test_sieve_tls_configuration(host):