Changeset - 9254a5c167c6
[Not reviewed]
0 3 0
Branko Majic (branko) - 5 years ago 2020-09-23 19:54:18
branko@majic.rs
MAR-158: Deduplicate SMTP/IMAP TLS connectivity tests in mail_server role.
3 files changed with 35 insertions and 58 deletions:
0 comments (0 inline, 0 general)
roles/mail_server/molecule/default/tests/test_default.py
Show inline comments
 
@@ -66,384 +66,419 @@ def test_clamav_milter_configuration(host):
 
def test_clamav_milter(host):
 
    """
 
    Tests if ClamAV milter is blocking viruses.
 
    """
 

	
 
    server_did_not_accept_mail = 26
 

	
 
    eicar = 'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*'
 

	
 
    send_mail = host.run("swaks --to john.doe@domain1 --server localhost --attach '%s'" % eicar)
 

	
 
    assert send_mail.rc == server_did_not_accept_mail
 
    assert 'Your message has been rejected due to a possible virus' in send_mail.stdout
 

	
 

	
 
def test_postfix_chroot_directories(host):
 
    """
 
    Tests if Postfix chroot directories have been set-up with correct
 
    permissions.
 
    """
 

	
 
    directory = host.file('/var/spool/postfix/var')
 
    assert directory.is_directory
 
    assert directory.user == 'root'
 
    assert directory.group == 'root'
 
    assert directory.mode == 0o755
 

	
 
    directory = host.file('/var/spool/postfix/var/run')
 
    assert directory.is_directory
 
    assert directory.user == 'root'
 
    assert directory.group == 'root'
 
    assert directory.mode == 0o755
 

	
 
    directory = host.file('/var/spool/postfix/var/run/clamav')
 
    assert directory.is_directory
 
    assert directory.user == 'clamav'
 
    assert directory.group == 'clamav'
 
    assert directory.mode == 0o755
 

	
 

	
 
def test_ldap_tls_truststore_file(host):
 
    """
 
    Tests if the LDAP TLS truststore file has been deployed correctly.
 
    """
 

	
 
    tls_file = host.file('/etc/ssl/certs/mail_ldap_tls_truststore.pem')
 
    assert tls_file.is_file
 
    assert tls_file.user == 'root'
 
    assert tls_file.group == 'root'
 
    assert tls_file.mode == 0o644
 
    assert tls_file.content_string == open("tests/data/x509/ca/chain-full.cert.pem", "r").read().rstrip()
 

	
 
    tls_file = host.file('/var/spool/postfix/etc/ssl/certs/mail_ldap_tls_truststore.pem')
 
    assert tls_file.is_file
 
    assert tls_file.user == 'root'
 
    assert tls_file.group == 'root'
 
    assert tls_file.mode == 0o644
 
    assert tls_file.content_string == open("tests/data/x509/ca/chain-full.cert.pem", "r").read().rstrip()
 

	
 

	
 
def test_mailname_file(host):
 
    """
 
    Tests the system mail name file permissions.
 
    """
 

	
 
    mailname = host.file('/etc/mailname')
 

	
 
    assert mailname.is_file
 
    assert mailname.user == 'root'
 
    assert mailname.group == 'root'
 
    assert mailname.mode == 0o644
 

	
 

	
 
def test_postfix_ldap_configuration_files(host):
 
    """
 
    Tests if Postfix LDAP configuration files have been deployed correctly.
 
    """
 

	
 
    for config_file_path in ['/etc/postfix/ldap-virtual-alias-maps.cf',
 
                             '/etc/postfix/ldap-virtual-mailbox-domains.cf',
 
                             '/etc/postfix/ldap-virtual-mailbox-maps.cf']:
 

	
 
        config = host.file(config_file_path)
 
        assert config.is_file
 
        assert config.user == 'root'
 
        assert config.group == 'postfix'
 
        assert config.mode == 0o640
 

	
 

	
 
def test_postfix_ldap_configuration(host):
 
    """
 
    Tests if LDAP configuration can be used to fetch correct query results.
 
    """
 

	
 
    with host.sudo():
 

	
 
        # Test for valid domains.
 
        command = host.run("postmap -q domain1 ldap:/etc/postfix/ldap-virtual-mailbox-domains.cf")
 
        assert command.rc == 0
 
        assert command.stdout == "domain1\n"
 

	
 
        command = host.run("postmap -q domain2 ldap:/etc/postfix/ldap-virtual-mailbox-domains.cf")
 
        assert command.rc == 0
 
        assert command.stdout == "domain2\n"
 

	
 
        # Test for invalid domains.
 
        command = host.run("postmap -q domain3 ldap:/etc/postfix/ldap-virtual-mailbox-domains.cf")
 
        assert command.rc == 1
 
        assert command.stdout == ""
 

	
 
        # Test for valid mail addresses.
 
        command = host.run("postmap -q 'john.doe@domain1' ldap:/etc/postfix/ldap-virtual-mailbox-maps.cf")
 
        assert command.rc == 0
 
        assert command.stdout == 'john.doe@domain1\n'
 

	
 
        command = host.run("postmap -q 'jane.doe@domain2' ldap:/etc/postfix/ldap-virtual-mailbox-maps.cf")
 
        assert command.rc == 0
 
        assert command.stdout == 'jane.doe@domain2\n'
 

	
 
        # Test for invalid mail addresses.
 
        command = host.run("postmap -q 'jane.doe@domain1' ldap:/etc/postfix/ldap-virtual-mailbox-maps.cf")
 
        assert command.rc == 1
 
        assert command.stdout == ''
 

	
 
        command = host.run("postmap -q 'john.doe@domain2' ldap:/etc/postfix/ldap-virtual-mailbox-maps.cf")
 
        assert command.rc == 1
 
        assert command.stdout == ''
 

	
 
        # Test for valid mail address that's not allowed by LDAP group membership.
 
        command = host.run("postmap -q 'nomail@domain1' ldap:/etc/postfix/ldap-virtual-mailbox-maps.cf")
 
        assert command.rc == 1
 
        assert command.stdout == ''
 

	
 
        # Test for valid mail aliases.
 
        command = host.run("postmap -q postmaster@domain1 ldap:/etc/postfix/ldap-virtual-alias-maps.cf")
 
        assert command.rc == 0
 
        assert command.stdout == "john.doe@domain1\n"
 

	
 
        command = host.run("postmap -q webmaster@domain2 ldap:/etc/postfix/ldap-virtual-alias-maps.cf")
 
        assert command.rc == 0
 
        assert command.stdout == "jane.doe@domain2\n"
 

	
 
        # Test for invalid mail aliases.
 
        command = host.run("postmap -q postmaster@domain2 ldap:/etc/postfix/ldap-virtual-alias-maps.cf")
 
        assert command.rc == 1
 
        assert command.stdout == ""
 

	
 
        command = host.run("postmap -q webmaster@domain1 ldap:/etc/postfix/ldap-virtual-alias-maps.cf")
 
        assert command.rc == 1
 
        assert command.stdout == ""
 

	
 

	
 
def test_postfix_main_cf_file(host):
 
    """
 
    Tests Postfix main configuration file permissions.
 
    """
 

	
 
    config = host.file('/etc/postfix/main.cf')
 
    assert config.is_file
 
    assert config.user == 'root'
 
    assert config.group == 'root'
 
    assert config.mode == 0o644
 

	
 

	
 
def test_postfix_delivery_to_dovecot(host):
 
    """
 
    Tests if mail received by Postfix is properly delivered to Dovecot.
 
    """
 

	
 
    hostname = host.run('hostname').stdout.strip()
 

	
 
    message_id = str(uuid.uuid4())
 

	
 
    # Virtual account.
 
    send = host.run('swaks --header %s --suppress-data --to john.doe@domain1 --server %s', "Message-Id: <%s>" % message_id, hostname)
 
    assert send.rc == 0
 

	
 
    with host.sudo():
 
        mail_log = host.file('/var/log/mail.log')
 
        pattern = r"dovecot: lda\(john.doe@domain1\): msgid=<%s>: saved mail to INBOX" % message_id
 
        assert re.search(pattern, mail_log.content_string) is not None
 

	
 

	
 
def test_dovecot_system_authentication_is_disabled(host):
 
    """
 
    Tests if Dovecot system-based authentication has been disabled.
 
    """
 

	
 
    config = host.file("/etc/dovecot/conf.d/10-auth.conf")
 

	
 
    assert "!include auth-system.conf.ext" not in config.content_string
 

	
 

	
 
def test_dovecot_overrides_configuration_file(host):
 
    """
 
    Tests if Dovecot configuration file with overrides has been deployed and has
 
    correct permissions.
 
    """
 

	
 
    config = host.file("/etc/dovecot/conf.d/99-local.conf")
 

	
 
    assert config.is_file
 
    assert config.user == 'root'
 
    assert config.group == 'root'
 
    assert config.mode == 0o644
 

	
 

	
 
def test_dovecot_imap_ldap_configuration(host):
 
    """
 
    Tests if Dovecot LDAP configuration is correct.
 
    """
 

	
 
    with host.sudo():
 

	
 
        user_does_not_exist = 67
 

	
 
        # Test for valid mail addresses.
 
        command = host.run("doveadm user john.doe@domain1")
 
        assert command.rc == 0
 

	
 
        command = host.run("doveadm user jane.doe@domain2")
 
        assert command.rc == 0
 

	
 
        # Test for invalid mail addresses.
 
        command = host.run("doveadm user john.doe@domain2")
 
        assert command.rc == user_does_not_exist
 

	
 
        command = host.run("doveadm user jane.doe@domain1")
 
        assert command.rc == user_does_not_exist
 

	
 
        # Test for mail addresses present in LDAP, but entry not in mail group.
 
        command = host.run("doveadm user nomail@domain1")
 
        assert command.rc == user_does_not_exist
 

	
 

	
 
def test_postfix_master_file(host):
 
    """
 
    Tests permissions for Postfix master.cf configuration file.
 
    """
 

	
 
    config = host.file('/etc/postfix/master.cf')
 

	
 
    assert config.is_file
 
    assert config.user == 'root'
 
    assert config.group == 'root'
 
    assert config.mode == 0o644
 

	
 

	
 
def test_services(host):
 
    """
 
    Tests if all the mail-related servieces are up and running.
 
    """
 

	
 
    for service_name in ["clamav-daemon",
 
                         "clamav-freshclam",
 
                         "clamav-milter",
 
                         "postfix",
 
                         "dovecot"]:
 

	
 
        service = host.service(service_name)
 
        assert service.is_running
 
        assert service.is_enabled
 

	
 

	
 
def test_clamav_database_presence(host):
 
    """
 
    Tests if ClamAV database is present.
 
    """
 

	
 
    for database_file in ["/var/lib/clamav/bytecode",
 
                          "/var/lib/clamav/daily",
 
                          "/var/lib/clamav/main"]:
 

	
 
        database_cvd = host.file(database_file + '.cvd')
 
        database_cld = host.file(database_file + '.cld')
 

	
 
        assert database_cvd.is_file or database_cld.is_file
 

	
 

	
 
def test_firewall_configuration_file(host):
 
    """
 
    Tests if firewall configuration file has been deployed correctly.
 
    """
 

	
 
    with host.sudo():
 

	
 
        config = host.file('/etc/ferm/conf.d/20-mail.conf')
 

	
 
        assert config.is_file
 
        assert config.user == 'root'
 
        assert config.group == 'root'
 
        assert config.mode == 0o640
 

	
 

	
 
def test_smtp_server_dh_parameter_file(host):
 
    """
 
    Tests if the Diffie-Hellman parameter file has been generated
 
    correctly.
 
    """
 

	
 
    hostname = host.run('hostname').stdout.strip()
 
    dhparam_file_path = '/etc/ssl/private/%s_smtp.dh.pem' % hostname
 

	
 
    with host.sudo():
 
        dhparam_file = host.file(dhparam_file_path)
 
        assert dhparam_file.is_file
 
        assert dhparam_file.user == 'root'
 
        assert dhparam_file.group == 'root'
 
        assert dhparam_file.mode == 0o640
 

	
 
        dhparam_info = host.run("openssl dhparam -noout -text -in %s", dhparam_file_path)
 

	
 
        assert "DH Parameters: (2048 bit)" in dhparam_info.stdout
 

	
 

	
 
def test_smtp_server_uses_correct_dh_parameters(host):
 
    """
 
    Tests if the SMTP server uses the generated Diffie-Hellman parameter.
 
    """
 

	
 
    hostname = host.run('hostname').stdout.strip()
 

	
 
    with host.sudo():
 
        expected_dhparam = host.file('/etc/ssl/private/%s_smtp.dh.pem' % hostname).content_string.rstrip()
 

	
 
    connection = host.run("gnutls-cli --no-ca-verification --starttls-proto=smtp --port 25 "
 
                          "--priority 'NONE:+VERS-TLS1.2:+CTYPE-X509:+COMP-NULL:+SIGN-RSA-SHA384:+DHE-RSA:+SHA384:+AEAD:+AES-256-GCM' --verbose localhost")
 

	
 
    output = connection.stdout
 
    begin_marker = "-----BEGIN DH PARAMETERS-----"
 
    end_marker = "-----END DH PARAMETERS-----"
 
    used_dhparam = output[output.find(begin_marker):output.find(end_marker) + len(end_marker)]
 

	
 
    assert used_dhparam == expected_dhparam
 

	
 

	
 
def test_imap_server_uses_correct_dh_parameters(host):
 
    """
 
    Tests if the IMAP server uses correct Diffie-Hellman parameters.
 
    """
 

	
 
    connection = host.run("gnutls-cli --no-ca-verification --starttls-proto=imap --port 143 "
 
                          "--priority 'NONE:+VERS-TLS1.2:+CTYPE-X509:+COMP-NULL:+SIGN-RSA-SHA384:+DHE-RSA:+SHA384:+AEAD:+AES-256-GCM' localhost")
 

	
 
    assert " - Using prime: 2048 bits" in connection.stdout
 

	
 

	
 
def test_imap_and_smtp_tls_files(host):
 
    """
 
    Tests if IMAP and SMTP TLS private keys and certificates have been
 
    deployed correctly.
 
    """
 

	
 
    hostname = host.run('hostname').stdout.strip()
 

	
 
    with host.sudo():
 

	
 
        tls_file = host.file('/etc/ssl/private/%s_smtp.key' % hostname)
 
        assert tls_file.is_file
 
        assert tls_file.user == 'root'
 
        assert tls_file.group == 'root'
 
        assert tls_file.mode == 0o640
 
        assert tls_file.content_string == open("tests/data/x509/server/%s_smtp.key.pem" % hostname, "r").read().rstrip()
 

	
 
        tls_file = host.file('/etc/ssl/certs/%s_smtp.pem' % hostname)
 
        assert tls_file.is_file
 
        assert tls_file.user == 'root'
 
        assert tls_file.group == 'root'
 
        assert tls_file.mode == 0o644
 
        assert tls_file.content_string == open("tests/data/x509/server/%s_smtp.cert.pem" % hostname, "r").read().rstrip()
 

	
 
        tls_file = host.file('/etc/ssl/private/%s_imap.key' % hostname)
 
        assert tls_file.is_file
 
        assert tls_file.user == 'root'
 
        assert tls_file.group == 'root'
 
        assert tls_file.mode == 0o640
 
        assert tls_file.content_string == open("tests/data/x509/server/%s_imap.key.pem" % hostname, "r").read().rstrip()
 

	
 
        tls_file = host.file('/etc/ssl/certs/%s_imap.pem' % hostname)
 
        assert tls_file.is_file
 
        assert tls_file.user == 'root'
 
        assert tls_file.group == 'root'
 
        assert tls_file.mode == 0o644
 
        assert tls_file.content_string == open("tests/data/x509/server/%s_imap.cert.pem" % hostname, "r").read().rstrip()
 

	
 

	
 
def test_imap_tls_connectivity(host):
 
    """
 
    Tests connectivity over STARTTLS/TLS towards IMAP server.
 
    """
 

	
 
    hostname = host.run('hostname').stdout.strip()
 
    fqdn = hostname[:hostname.rfind("-")]
 

	
 
    starttls = host.run('echo "a0001 LOGOUT" | openssl s_client -quiet -starttls imap -connect %s:143', fqdn)
 
    assert starttls.rc == 0
 
    assert '* BYE Logging out' in starttls.stdout
 

	
 
    tls = host.run('echo "a0001 LOGOUT" | openssl s_client -quiet -connect %s:993', fqdn)
 
    assert tls.rc == 0
 
    assert '* BYE Logging out' in starttls.stdout
 

	
 

	
 
def test_smtp_tls_connectivity(host):
 
    """
 
    Tests connectivity over default/submission port towards SMTP
 
    server.
 
    """
 

	
 
    hostname = host.run('hostname').stdout.strip()
 
    fqdn = hostname[:hostname.rfind("-")]
 

	
 
    starttls = host.run('echo "QUIT" | openssl s_client -quiet -starttls smtp -connect %s:25', fqdn)
 
    assert starttls.rc == 0
 
    assert '221 2.0.0 Bye' in starttls.stdout
 

	
 
    tls = host.run('echo "QUIT" | openssl s_client -quiet -starttls smtp -connect %s:587', fqdn)
 
    assert tls.rc == 0
 
    assert '221 2.0.0 Bye' in starttls.stdout
roles/mail_server/molecule/default/tests/test_mandatory.py
Show inline comments
 
import os
 

	
 
import defusedxml.ElementTree as ElementTree
 

	
 
import pytest
 

	
 
import testinfra.utils.ansible_runner
 

	
 

	
 
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
 
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('parameters-mandatory')
 

	
 

	
 
def test_certificate_validity_check_configuration(host):
 
    """
 
    Tests if certificate validity check configuration file has been deployed
 
    correctly.
 
    """
 

	
 
    hostname = host.run('hostname').stdout.strip()
 

	
 
    config = host.file('/etc/check_certificate/%s_smtp.conf' % hostname)
 
    assert config.is_file
 
    assert config.user == 'root'
 
    assert config.group == 'root'
 
    assert config.mode == 0o644
 
    assert config.content_string == "/etc/ssl/certs/%s_smtp.pem" % hostname
 

	
 
    config = host.file('/etc/check_certificate/%s_imap.conf' % hostname)
 
    assert config.is_file
 
    assert config.user == 'root'
 
    assert config.group == 'root'
 
    assert config.mode == 0o644
 
    assert config.content_string == "/etc/ssl/certs/%s_imap.pem" % hostname
 

	
 

	
 
def test_mailname_file_content(host):
 
    """
 
    Tests the system mail name file content.
 
    """
 

	
 
    mailname = host.file('/etc/mailname')
 
    hostname = host.run('hostname').stdout.strip()
 

	
 
    assert mailname.content_string == hostname
 

	
 

	
 
def test_postfix_main_cf_file_content(host):
 
    """
 
    Tests if the Postfix main configuration file content is correct.
 
    """
 

	
 
    hostname = host.run('hostname').stdout.strip()
 

	
 
    config = host.file('/etc/postfix/main.cf')
 
    config_lines = config.content_string.split("\n")
 

	
 
    assert "myhostname = %s" % hostname in config_lines
 
    assert "mydestination = %s, %s, localhost.localdomain, localhost" % (hostname, hostname) in config_lines
 
    assert "mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128" in config_lines
 
    assert "smtpd_tls_cert_file = /etc/ssl/certs/%s_smtp.pem" % hostname in config_lines
 
    assert "smtpd_tls_key_file = /etc/ssl/private/%s_smtp.key" % hostname in config_lines
 
    assert "reject_rbl" not in config_lines
 
    assert "smtp_host_lookup = dns, native" in config_lines
 

	
 

	
 
def test_dovecot_mailbox_directories(host):
 
    """
 
    Tests if mailbox directories are created correctly.
 
    """
 

	
 
    # Deliver two mails in order to make sure the directory structure is
 
    # created.
 
    send = host.run('swaks --suppress-data --to john.doe@domain1 --server localhost')
 
    assert send.rc == 0
 
    send = host.run('swaks --suppress-data --to jane.doe@domain2 --server localhost')
 
    assert send.rc == 0
 

	
 
    with host.sudo():
 

	
 
        for directory_path in ["/var/vmail/domain1",
 
                               "/var/vmail/domain1/john.doe",
 
                               "/var/vmail/domain1/john.doe/Maildir",
 
                               "/var/vmail/domain2",
 
                               "/var/vmail/domain2/jane.doe",
 
                               "/var/vmail/domain2/jane.doe/Maildir"]:
 

	
 
            directory = host.file(directory_path)
 

	
 
            assert directory.is_directory
 
            assert directory.user == "vmail"
 
            assert directory.group == "vmail"
 
            assert directory.mode == 0o700
 

	
 

	
 
def test_mail_owner(host):
 
    """
 
    Tests creation of mail owner group and user.
 
    """
 

	
 
    group = host.group("vmail")
 
    assert group.exists
 
    assert group.gid == 1002
 

	
 
    user = host.user("vmail")
 
    assert user.exists
 
    assert user.uid == 1002
 
    assert user.home == "/var/vmail"
 
    assert user.group == "vmail"
 
    assert user.groups == ["vmail"]
 

	
 

	
 
def test_imap_tls_connectivity(host):
 
    """
 
    Tests connectivity over STARTTLS/TLS towards IMAP server.
 
    """
 

	
 
    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
 

	
 
    tls = host.run('echo "a0001 LOGOUT" | openssl s_client -quiet -connect parameters-mandatory: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.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",
 
    ]
 

	
 
    # 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
 

	
 
    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_dovecot_postmaster(host):
 
    """
 
    Tests if Dovecot postmaster has been correctly configured.
 
    """
 

	
 
    with host.sudo():
 

	
 
        config = host.run("doveadm config")
 
        assert config.rc == 0
 
        assert "  postmaster_address = postmaster@" in config.stdout
 

	
 

	
 
def test_imap_max_user_connections_per_ip(host):
 
    """
 
    Tests if Dovecot per-user connection limit has been set-up correctly.
 
    """
 

	
 
    with host.sudo():
 

	
 
        config = host.run("doveadm config")
 

	
 
        assert config.rc == 0
 
        assert "  mail_max_userip_connections = 10" in config.stdout
 

	
 

	
 
def test_smtp_tls_connectivity(host):
 
    """
 
    Tests connectivity over default/submission port towards SMTP
 
    server.
 
    """
 

	
 
    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
 

	
 
    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
 

	
 

	
 
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).
 
    """
 

	
 
    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):
 
    """
 
    Tests TLS configuration for SIEVE in Dovecot
 
    """
 

	
 
    # @TODO: Currently not possible to test since openssl s_client does not
 
    # support STARTTLS for Sieve.
 
    pass
 

	
 

	
 
def test_mail_message_size_limit(host):
 
    """
 
    Tests if the mail message size limit advertised by the SMTP server
 
    is correct.
 
    """
 

	
 
    capabilities = host.run("(echo 'ehlo localhost' && sleep 2) | telnet localhost 25")
 
    begin = capabilities.stdout.find("250-SIZE")
 
    end = capabilities.stdout.find("\n", begin)
 
    mail_message_size_limit = capabilities.stdout[begin:end]
 

	
 
    assert mail_message_size_limit == "250-SIZE 10240000"
roles/mail_server/molecule/default/tests/test_optional.py
Show inline comments
 
import os
 
import re
 
import time
 
import uuid
 

	
 
import defusedxml.ElementTree as ElementTree
 

	
 
import pytest
 

	
 
import testinfra.utils.ansible_runner
 

	
 

	
 
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
 
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('parameters-optional')
 

	
 

	
 
def test_certificate_validity_check_configuration(host):
 
    """
 
    Tests if certificate validity check configuration file has been deployed
 
    correctly.
 
    """
 

	
 
    hostname = host.run('hostname').stdout.strip()
 

	
 
    config = host.file('/etc/check_certificate/%s_smtp.conf' % hostname)
 
    assert config.is_file
 
    assert config.user == 'root'
 
    assert config.group == 'root'
 
    assert config.mode == 0o644
 
    assert config.content_string == "/etc/ssl/certs/%s_smtp.pem" % hostname
 

	
 
    config = host.file('/etc/check_certificate/%s_imap.conf' % hostname)
 
    assert config.is_file
 
    assert config.user == 'root'
 
    assert config.group == 'root'
 
    assert config.mode == 0o644
 
    assert config.content_string == "/etc/ssl/certs/%s_imap.pem" % hostname
 

	
 

	
 
def test_mailname_file_content(host):
 
    """
 
    Tests the system mail name file content.
 
    """
 

	
 
    mailname = host.file('/etc/mailname')
 
    hostname = host.run('hostname').stdout.strip()
 

	
 
    assert mailname.content_string == hostname
 

	
 

	
 
def test_postfix_main_cf_file_content(host):
 
    """
 
    Tests if the Postfix main configuration file content is correct.
 
    """
 

	
 
    allow_relay_from_ip = "10.31.127.22"
 

	
 
    hostname = host.run('hostname').stdout.strip()
 

	
 
    config = host.file('/etc/postfix/main.cf')
 
    config_lines = config.content_string.split("\n")
 

	
 
    assert "myhostname = %s" % hostname in config_lines
 
    assert "mydestination = %s, %s, localhost.localdomain, localhost" % (hostname, hostname) in config_lines
 
    assert "mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 %s" % allow_relay_from_ip in config_lines
 
    assert "smtpd_tls_cert_file = /etc/ssl/certs/%s_smtp.pem" % hostname in config_lines
 
    assert "smtpd_tls_key_file = /etc/ssl/private/%s_smtp.key" % hostname in config_lines
 
    assert "  reject_rbl bl.spamcop.net" in config_lines
 
    assert "  reject_rbl zen.spamhaus.org" in config_lines
 
    assert "smtp_host_lookup = dns, native" in config_lines
 

	
 

	
 
def test_local_aliases(host):
 
    """
 
    Tests if local aliases are configured correctly.
 
    """
 

	
 
    message_id = str(uuid.uuid4())
 

	
 
    send = host.run('swaks --header %s --suppress-data --to root@localhost', "Message-Id: <%s>" % message_id)
 
    time.sleep(1)
 
    assert send.rc == 0
 

	
 
    with host.sudo():
 
        mail_log = host.file('/var/log/mail.log')
 
        pattern = r"dovecot: lda\(john.doe@domain1\): msgid=<%s>: saved mail to INBOX" % message_id
 
        assert re.search(pattern, mail_log.content_string) is not None
 

	
 

	
 
def test_dovecot_mailbox_directories(host):
 
    """
 
    Tests if mailbox directories are created correctly.
 
    """
 

	
 
    # Deliver two mails in order to make sure the directory structure is
 
    # created.
 
    send = host.run('swaks --suppress-data --to john.doe@domain1 --server localhost')
 
    assert send.rc == 0
 
    send = host.run('swaks --suppress-data --to jane.doe@domain2 --server localhost')
 
    assert send.rc == 0
 

	
 
    with host.sudo():
 

	
 
        for directory_path in ["/var/virtmail/domain1",
 
                               "/var/virtmail/domain1/john.doe",
 
                               "/var/virtmail/domain1/john.doe/Maildir",
 
                               "/var/virtmail/domain2",
 
                               "/var/virtmail/domain2/jane.doe",
 
                               "/var/virtmail/domain2/jane.doe/Maildir"]:
 

	
 
            directory = host.file(directory_path)
 

	
 
            assert directory.is_directory
 
            assert directory.user == "virtmail"
 
            assert directory.group == "virtmail"
 
            assert directory.mode == 0o700
 

	
 

	
 
def test_mail_owner(host):
 
    """
 
    Tests creation of mail owner group and user.
 
    """
 

	
 
    group = host.group("virtmail")
 
    assert group.exists
 
    assert group.gid == 5000
 

	
 
    user = host.user("virtmail")
 
    assert user.exists
 
    assert user.uid == 5000
 
    assert user.home == "/var/virtmail"
 
    assert user.group == "virtmail"
 
    assert user.groups == ["virtmail"]
 

	
 

	
 
def test_imap_tls_connectivity(host):
 
    """
 
    Tests connectivity over STARTTLS/TLS towards IMAP server.
 
    """
 

	
 
    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
 

	
 
    tls = host.run('echo "a0001 LOGOUT" | openssl s_client -quiet -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",
 
    ]
 

	
 
    # 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
 

	
 
    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_dovecot_postmaster(host):
 
    """
 
    Tests if Dovecot postmaster has been correctly configured.
 
    """
 

	
 
    with host.sudo():
 

	
 
        config = host.run("doveadm config")
 

	
 
        assert config.rc == 0
 
        assert "  postmaster_address = webmaster@parameters-optional" in config.stdout
 

	
 

	
 
def test_imap_max_user_connections_per_ip(host):
 
    """
 
    Tests if Dovecot per-user connection limit has been set-up correctly.
 
    """
 

	
 
    with host.sudo():
 

	
 
        config = host.run("doveadm config")
 

	
 
        assert config.rc == 0
 
        assert "  mail_max_userip_connections = 2" in config.stdout
 

	
 

	
 
def test_smtp_tls_connectivity(host):
 
    """
 
    Tests connectivity over default/submission port towards SMTP
 
    server.
 
    """
 

	
 
    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
 

	
 
    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
 

	
 

	
 
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).
 
    """
 

	
 
    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):
 
    """
 
    Tests TLS configuration for SIEVE in Dovecot
 
    """
 

	
 
    # @TODO: Currently not possible to test since openssl s_client does not
 
    # support STARTTLS for Sieve.
 
    pass
 

	
 

	
 
def test_mail_message_size_limit(host):
 
    """
 
    Tests if the mail message size limit advertised by the SMTP server
 
    is correct.
 
    """
 

	
 
    capabilities = host.run("(echo 'ehlo localhost' && sleep 2) | telnet localhost 25")
 
    begin = capabilities.stdout.find("250-SIZE")
 
    end = capabilities.stdout.find("\n", begin)
 
    mail_message_size_limit = capabilities.stdout[begin:end]
 

	
 
    assert mail_message_size_limit == "250-SIZE 20480001"
0 comments (0 inline, 0 general)