Files @ 24120c68c6b4
Branch filter:

Location: majic-ansible-roles/roles/mail_server/molecule/default/tests/test_client2.py

branko
MAR-196: Simplify allowed TLS protocol configuration for mail_server role:

- Accept minimum version allowed instead of arbitrary list.
- Fixes deprecation warnings in Dovecot logs (ssl_protocols ->
ssl_min_protocol transition).
import os
import re

import pytest

import testinfra.utils.ansible_runner


testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('client-relay-forbidden')


def test_open_relay(host):
    """
    Tests if mail server behaves as open relay.
    """

    no_recipients_accepted = 24

    send = host.run('swaks --suppress-data --to root@client1 --server parameters-mandatory')
    assert send.rc == no_recipients_accepted
    assert "Relay access denied" in send.stdout

    send = host.run('swaks --suppress-data --to root@client1 --server parameters-optional')
    assert send.rc == no_recipients_accepted
    assert "Relay access denied" in send.stdout

    send = host.run('swaks --port 27 --suppress-data --to root@client1 --server parameters-mandatory')
    assert send.rc == no_recipients_accepted
    assert "Relay access denied" in send.stdout

    send = host.run('swaks --port 27 --suppress-data --to root@client1 --server parameters-optional')
    assert send.rc == no_recipients_accepted
    assert "Relay access denied" in send.stdout


def test_mail_delivery(host):
    """
    Tests if mails can be delivered to valid accounts. Has to be run on client
    with no unauthenticated relay permissions.
    """

    no_recipients_accepted = 24

    # Valid accounts.
    send = host.run('swaks --suppress-data --to john.doe@domain1 --server parameters-mandatory')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout

    send = host.run('swaks --suppress-data --to john.doe@domain1 --server parameters-optional')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout

    send = host.run('swaks --suppress-data --to jane.doe@domain2 --server parameters-mandatory')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout

    send = host.run('swaks --suppress-data --to jane.doe@domain2 --server parameters-optional')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout

    # Invalid accounts.
    send = host.run('swaks --suppress-data --to john.doe@domain2 --server parameters-mandatory')
    assert send.rc == no_recipients_accepted
    assert "Recipient address rejected: User unknown in virtual mailbox table" in send.stdout

    send = host.run('swaks --suppress-data --to john.doe@domain2 --server parameters-optional')
    assert send.rc == no_recipients_accepted
    assert "Recipient address rejected: User unknown in virtual mailbox table" in send.stdout

    send = host.run('swaks --suppress-data --to jane.doe@domain1 --server parameters-mandatory')
    assert send.rc == no_recipients_accepted
    assert "Recipient address rejected: User unknown in virtual mailbox table" in send.stdout

    send = host.run('swaks --suppress-data --to jane.doe@domain1 --server parameters-optional')
    assert send.rc == no_recipients_accepted
    assert "Recipient address rejected: User unknown in virtual mailbox table" in send.stdout

    # Test for valid mail address that's not allowed by LDAP group membership.
    send = host.run('swaks --suppress-data --to nomail@domain1 --server parameters-mandatory')
    assert send.rc == no_recipients_accepted
    assert "Recipient address rejected: User unknown in virtual mailbox table" in send.stdout

    send = host.run('swaks --suppress-data --to nomail@domain1 --server parameters-optional')
    assert send.rc == no_recipients_accepted
    assert "Recipient address rejected: User unknown in virtual mailbox table" in send.stdout

    # Valid aliases.
    send = host.run('swaks --suppress-data --to postmaster@domain1 --server parameters-mandatory')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout

    send = host.run('swaks --suppress-data --to postmaster@domain1 --server parameters-optional')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout

    send = host.run('swaks --suppress-data --to webmaster@domain2 --server parameters-mandatory')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout

    send = host.run('swaks --suppress-data --to webmaster@domain2 --server parameters-optional')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout

    # Invalid aliases.
    send = host.run('swaks --suppress-data --to postmaster@domain2 --server parameters-mandatory')
    assert send.rc == no_recipients_accepted
    assert "Recipient address rejected: User unknown in virtual mailbox table" in send.stdout

    send = host.run('swaks --suppress-data --to postmaster@domain2 --server parameters-optional')
    assert send.rc == no_recipients_accepted
    assert "Recipient address rejected: User unknown in virtual mailbox table" in send.stdout

    send = host.run('swaks --suppress-data --to webmaster@domain1 --server parameters-mandatory')
    assert send.rc == no_recipients_accepted
    assert "Recipient address rejected: User unknown in virtual mailbox table" in send.stdout

    send = host.run('swaks --suppress-data --to webmaster@domain1 --server parameters-optional')
    assert send.rc == no_recipients_accepted
    assert "Recipient address rejected: User unknown in virtual mailbox table" in send.stdout


def test_smtp_authentication(host):
    """
    Tests if SMTP authentication works via TLS and allows sending mails to
    anywhere.
    """

    send = host.run('swaks -tls --port 587 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-mandatory')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout

    send = host.run('swaks -tls --port 587 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-optional')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout


def test_smtp_authentication_requires_tls(host):
    """
    Tests if SMTP authentication requires TLS.
    """

    auth_error = 28

    send = host.run('swaks --port 587 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-mandatory')
    assert send.rc == auth_error
    assert "Host did not advertise authentication" in send.stderr

    send = host.run('swaks --port 587 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-optional')
    assert send.rc == auth_error
    assert "Host did not advertise authentication" in send.stderr


def test_smtp_authentication_requires_submission_port(host):
    """
    Tests if SMTP authentication cannot be done on regular SMTP port.
    """

    auth_error = 28

    send = host.run('swaks --port 25 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-mandatory')
    assert send.rc == auth_error
    assert "Host did not advertise authentication" in send.stderr

    send = host.run('swaks -tls --port 25 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-mandatory')
    assert send.rc == auth_error
    assert "Host did not advertise authentication" in send.stderr

    send = host.run('swaks --port 25 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-optional')
    assert send.rc == auth_error
    assert "Host did not advertise authentication" in send.stderr

    send = host.run('swaks -tls --port 25 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-optional')
    assert send.rc == auth_error
    assert "Host did not advertise authentication" in send.stderr


def test_dovecot_inbox_separator(host):
    """
    Tests if inbox separator has been configured correctly.
    """

    pattern_slash_separator = re.compile(r'WARNING:imap-cli:Ignoring "LIST" response part : \([^)]*\) "/" INBOX')
    pattern_dot_separator = re.compile(r'WARNING:imap-cli:Ignoring "LIST" response part : \([^)]*\) "\." INBOX')

    status = host.run("imapcli status -c ~/imapcli-parameters-mandatory-john_doe.conf")
    assert pattern_slash_separator.search(status.stdout) is not None

    status = host.run("imapcli status -c ~/imapcli-parameters-optional-john_doe.conf")
    assert pattern_dot_separator.search(status.stdout) is not None


@pytest.mark.parametrize("server", [
    "parameters-mandatory",
    "parameters-optional"
])
def test_imap_authentication_requires_tls(host, server):
    """
    Tests if IMAP authentication requires TLS.
    """

    capabilities_command = "a0001 CAPABILITY\na0002 LOGOUT"

    # No TLS.
    command = host.run("echo %s | nc %s 143", capabilities_command, server)
    assert command.rc == 0
    assert "LOGINDISABLED" in command.stdout

    # STARTTLS.
    command = host.run("echo %s | openssl s_client -quiet -connect %s:143 -starttls imap", capabilities_command, server)
    assert command.rc == 0
    assert "LOGINDISABLED" not in command.stdout

    # TLS.
    command = host.run("echo %s | openssl s_client -quiet -connect %s:993", capabilities_command, server)
    assert command.rc == 0
    assert "LOGINDISABLED" not in command.stdout


def test_sieve_authentication_requires_tls(host):
    """
    Tests if SIEVE authentication requires TLS.
    """

    # No TLS.
    command = host.run("echo 'LOGOUT' | nc parameters-mandatory 4190")
    assert command.rc == 0
    assert "PLAIN LOGIN" not in command.stdout

    command = host.run("echo 'LOGOUT' | nc parameters-optional 4190")
    assert command.rc == 0
    assert "PLAIN LOGIN" not in command.stdout

    # STARTTLS
    # @TODO: In case of failed login (authentication rejected),
    #        sieve-connect will return error code 255. However,
    #        internally Testinfra treats error code as if it is a
    #        signal from the SSH (over which the command is being run)
    #        that the SSH itself has failed, and throws a runtime
    #        exception. The use of || //bin/false is a workaround for
    #        now. It would be a good idea to get in contact with
    #        Testinfra maintainer and raise an issue around
    #        this. There are more similar uses of sieve-connect in the
    #        test files, don't forget to update those as well if some
    #        kind of better solution pops-up.
    command = host.run("echo 'johnpassword' | sieve-connect -u john.doe@domain1 --password 0 --server parameters-mandatory --port 4190 --list || /bin/false")
    assert command.rc == 0

    command = host.run("echo 'johnpassword' | sieve-connect -u john.doe@domain1 --password 0 --server parameters-optional --port 4190 --list || /bin/false")
    assert command.rc == 0


@pytest.mark.parametrize("server", [
    "parameters-mandatory",
    "parameters-optional"
])
@pytest.mark.parametrize("port", [
    25,
    26,
    27,
    587,
    143,
    993,
    4190
])
def test_connectivity(host, server, port):
    """
    Tests connectivity to the mail server (ports that should be reachable).
    """

    with host.sudo():
        ping = host.run('hping3 -S -p %s -c 1 %s', str(port), server)
        assert ping.rc == 0


def test_port_forwarding(host):
    """
    Tests if port forwarding is set-up correctly for additional SMTP and
    submission ports.
    """

    # Regular SMTP.
    send = host.run('swaks -tls --port 27 --to john.doe@domain1 --server parameters-mandatory')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout

    send = host.run('swaks -tls --port 27 --to john.doe@domain1 --server parameters-optional')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout

    # Submission port.
    send = host.run('swaks -tls --port 26 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-mandatory')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout

    send = host.run('swaks -tls --port 26 --auth-user john.doe@domain1 --auth-password johnpassword --to root@client1 --server parameters-optional')
    assert send.rc == 0
    assert "Ok: queued as" in send.stdout


def test_dovecot_sieve(host):
    """
    Tests if Sieve service is available.
    """

    # Test valid users.
    command = host.run('echo johnpassword | sieve-connect --list -s parameters-mandatory -p 4190 -u john.doe@domain1 --password 0 || /bin/false')
    assert command.rc == 0

    command = host.run('echo janepassword | sieve-connect --list -s parameters-optional -p 4190 -u jane.doe@domain2 --password 0 || /bin/false')
    assert command.rc == 0

    # Test invalid users.
    command = host.run('echo johnpassword | sieve-connect --list -s parameters-mandatory -p 4190 -u john.doe@domain2 --password 0 || /bin/false')
    assert command.rc != 0
    assert "Authentication refused by server" in command.stderr

    command = host.run('echo janepassword | sieve-connect --list -s parameters-optional -p 4190 -u jane.doe@domain1 --password 0 || /bin/false')
    assert command.rc != 0
    assert "Authentication refused by server" in command.stderr