diff --git a/roles/mail_server/molecule/default/tests/test_mandatory.py b/roles/mail_server/molecule/default/tests/test_mandatory.py index 0937e7de8afb93f56aa52f7b0f43e096116b5b43..5f93602e03456878893608d414cf053f557aeb78 100644 --- a/roles/mail_server/molecule/default/tests/test_mandatory.py +++ b/roles/mail_server/molecule/default/tests/test_mandatory.py @@ -1,5 +1,9 @@ import os +import defusedxml.ElementTree as ElementTree + +import pytest + import testinfra.utils.ansible_runner @@ -106,12 +110,11 @@ def test_mail_owner(host): assert user.groups == ["vmail"] -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-mandatory:143') assert starttls.rc == 0 assert '* BYE Logging out' in starttls.stdout @@ -120,34 +123,50 @@ def test_imap_tls_configuration(host): assert tls.rc == 0 assert '* BYE Logging out' in starttls.stdout - # Test TLS protocol versions. - starttls_old_tls_versions_disabled = host.run("echo 'a0001 LOGOUT' | openssl s_client -quiet -starttls imap -no_tls1_2 -connect parameters-mandatory:143") - assert starttls_old_tls_versions_disabled.rc != 0 - assert "write:errno=104" in starttls_old_tls_versions_disabled.stderr or 'SSL alert number 70' in starttls_old_tls_versions_disabled.stderr - tls_old_tls_versions_disabled = host.run("echo 'a0001 LOGOUT' | openssl s_client -quiet -no_tls1_2 -connect parameters-mandatory:993") - assert tls_old_tls_versions_disabled.rc != 0 - assert "write:errno=104" in tls_old_tls_versions_disabled.stderr or 'SSL alert number 70' in tls_old_tls_versions_disabled.stderr +@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.2"] + + expected_tls_ciphers = [ + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + ] - # 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-mandatory:143") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in starttls_cipher.stdout + # 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_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-mandatory:993") - assert tls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA256" in tls_cipher.stdout + report_root = ElementTree.fromstring(report_content) - # Test weaker TLS cipher are disabled. - starttls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -starttls imap -cipher ECDHE-RSA-AES128-SHA -connect parameters-mandatory:143") - assert starttls_cipher.rc != 0 - assert "CONNECTED" in starttls_cipher.stdout - assert "ECDHE-RSA-AES128-SHA" not in starttls_cipher.stdout + tls_versions = [] + tls_ciphers = set() - tls_cipher = host.run("echo 'a0001 LOGOUT' | openssl s_client -cipher ECDHE-RSA-AES128-SHA -connect parameters-mandatory:993") - assert tls_cipher.rc != 0 - assert "CONNECTED" in tls_cipher.stdout - assert "ECDHE-RSA-AES128-SHA" not in tls_cipher.stdout + 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_dovecot_postmaster(host): @@ -175,51 +194,106 @@ def test_imap_max_user_connections_per_ip(host): assert " mail_max_userip_connections = 10" 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-mandatory:25") + starttls = host.run('echo "QUIT" | openssl s_client -quiet -starttls smtp -connect parameters-mandatory: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-mandatory: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-mandatory:25") - assert starttls.rc == 0 - assert '221 2.0.0 Bye' in starttls.stdout - - # Test TLS protocol versions for submission port (only TLS 1.2 should be enabled). - starttls = host.run("echo 'QUIT' | openssl s_client -quiet -starttls smtp -connect parameters-mandatory:587") - assert starttls.rc == 0 + tls = host.run('echo "QUIT" | openssl s_client -quiet -starttls smtp -connect parameters-mandatory: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_2 -connect parameters-mandatory:587") - assert starttls.rc != 0 - assert 'write:errno=104' in starttls.stderr or '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-mandatory: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-mandatory:25") - assert starttls_cipher.rc == 0 - assert "ECDHE-RSA-AES128-SHA" in starttls_cipher.stdout - # Test ciphers for submission port (weak ciphers not available). - starttls_cipher = host.run("echo 'QUIT' | openssl s_client -starttls smtp -cipher ECDHE-RSA-AES128-SHA256 -connect parameters-mandatory: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-mandatory:587") - assert starttls_cipher.rc != 0 - assert "CONNECTED" in starttls_cipher.stdout - assert "ECDHE-RSA-AES128-SHA" not 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):