Changeset - da031f975c67
[Not reviewed]
0 3 4
Branko Majic (branko) - 7 years ago 2017-07-11 22:55:29
branko@majic.rs
MAR-31: Implemented tests and small fixes for xmpp_server role:

- Updated test playbook to deploy simple XMPP non-interactive tool.
- Updated test playbook to include an additional administrator for server
hosting domain2 and domain3.
- Updated test playbook to enable backup on parameters-optional test instance.
- Use expanded syntax for deploying TLS key and certificate in order to prevent
mangling of tab characters.
- Set permissions on deployed Prosody configuration file in order to prevent
LDAP password from being world-readable.
- Implemented tests for backup functionality.
- Implemented tests for client connectivity.
- Implemented a number of tests that ensure XMPP server is deployed correctly.
- Full functionality cannot be tested (like MUC and file proxy) due to partial
lack of CLI-based tools.
7 files changed with 436 insertions and 11 deletions:
0 comments (0 inline, 0 general)
roles/xmpp_server/playbook.yml
Show inline comments
 
@@ -43,6 +43,11 @@
 
        name: mcabber
 
        state: installed
 

	
 
    - name: Install console-based XMPP tool (for non-interactive testing)
 
      apt:
 
        name: sendxmpp
 
        state: installed
 

	
 
    - name: Create dedicated group for testing
 
      group:
 
        name: user
 
@@ -209,6 +214,7 @@
 
    - role: xmpp_server
 
      xmpp_administrators:
 
        - jane.doe@domain2
 
        - mick.doe@domain3
 
      xmpp_domains:
 
        - domain2
 
        - domain3
 
@@ -225,3 +231,23 @@
 
      # common
 
      ca_certificates:
 
        testca: "{{ lookup('file', 'tests/data/x509/ca.cert.pem') }}"
 

	
 
      # backup_client
 
      enable_backup: yes
 
      backup_encryption_key: "{{ lookup('file', 'tests/data/gnupg/parameters-optional.asc') }}"
 
      backup_server: backup-server
 
      backup_server_host_ssh_public_keys:
 
        - "{{ lookup('file', 'tests/data/ssh/server_dsa.pub') }}"
 
        - "{{ lookup('file', 'tests/data/ssh/server_rsa.pub') }}"
 
        - "{{ lookup('file', 'tests/data/ssh/server_ed25519.pub') }}"
 
        - "{{ lookup('file', 'tests/data/ssh/server_ecdsa.pub') }}"
 
      backup_ssh_key: "{{ lookup('file', 'tests/data/ssh/parameters-optional' ) }}"
 

	
 

	
 
- hosts: parameters-optional
 
  tasks:
 

	
 
    - name: Install console-based XMPP tool (for non-interactive testing)
 
      apt:
 
        name: sendxmpp
 
        state: installed
roles/xmpp_server/tasks/main.yml
Show inline comments
 
@@ -24,14 +24,22 @@
 
  user: name=prosody append=yes groups=ssl-cert
 

	
 
- name: Deploy XMPP TLS private key
 
  copy: dest="/etc/ssl/private/{{ ansible_fqdn }}_xmpp.key" content="{{ xmpp_tls_key }}"
 
        mode=0640 owner=root group=prosody
 
  copy:
 
    dest: "/etc/ssl/private/{{ ansible_fqdn }}_xmpp.key"
 
    content: "{{ xmpp_tls_key }}"
 
    mode: 0640
 
    owner: root
 
    group: prosody
 
  notify:
 
    - Restart Prosody
 

	
 
- name: Deploy XMPP TLS certificate
 
  copy: dest="/etc/ssl/certs/{{ ansible_fqdn }}_xmpp.pem" content="{{ xmpp_tls_certificate }}"
 
        mode=0644 owner=root group=root
 
  copy:
 
    dest: "/etc/ssl/certs/{{ ansible_fqdn }}_xmpp.pem"
 
    content: "{{ xmpp_tls_certificate }}"
 
    mode: 0644
 
    owner: root
 
    group: root
 
  notify:
 
    - Restart Prosody
 

	
 
@@ -50,7 +58,12 @@
 
  file: dest=/usr/local/lib/prosody/modules/mod_auth_ldap.lua owner=root group=root mode=0644
 

	
 
- name: Deploy Prosody configuration file
 
  template: src=prosody.cfg.lua.j2 dest=/etc/prosody/prosody.cfg.lua
 
  template:
 
    src: prosody.cfg.lua.j2
 
    dest: /etc/prosody/prosody.cfg.lua
 
    owner: root
 
    group: prosody
 
    mode: 0640
 
  notify:
 
    - Restart Prosody
 

	
roles/xmpp_server/tests/test_backup.py
Show inline comments
 
new file 100644
 
import testinfra.utils.ansible_runner
 

	
 

	
 
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
 
    '.molecule/ansible_inventory').get_hosts('parameters-optional')
 

	
 

	
 
def test_backup(Command, File, Sudo):
 
    """
 
    Tests if Prosody data directory is correctly backed-up.
 
    """
 

	
 
    # Deliver a couple of messages in order to make sure the directory structure
 
    # is created.
 
    send = Command("echo 'Hello' | sendxmpp -t -u jane.doe -p janepassword -j domain2:5222 jane.doe@domain2")
 
    assert send.rc == 0
 

	
 
    send = Command("echo 'Hello' | sendxmpp -t -u mick.doe -p mickpassword -j domain3:5222 mick.doe@domain3")
 
    assert send.rc == 0
 

	
 
    with Sudo():
 

	
 
        # Remove restore directory in order to make sure restore has worked
 
        # correctly.
 
        Command("rm -rf /root/restore")
 

	
 
        backup_run = Command('duply main backup')
 
        assert backup_run.rc == 0
 

	
 
        restore_run = Command('duply main restore /root/restore')
 
        assert restore_run.rc == 0
 

	
 
        for directory_path in ["/root/restore/var/lib/prosody/domain2",
 
                               "/root/restore/var/lib/prosody/domain2/offline",
 
                               "/root/restore/var/lib/prosody/domain3",
 
                               "/root/restore/var/lib/prosody/domain3/offline"]:
 

	
 
            directory = File(directory_path)
 

	
 
            assert directory.is_directory
 
            assert directory.user == "prosody"
 
            assert directory.group == "prosody"
 
            assert directory.mode == 0o750
 

	
 
        for file_path in ["/root/restore/var/lib/prosody/domain2/offline/jane%2edoe.list",
 
                          "/root/restore/var/lib/prosody/domain3/offline/mick%2edoe.list"]:
 

	
 
            f = File(file_path)
 

	
 
            assert f.is_file
 
            assert f.user == 'prosody'
 
            assert f.group == 'prosody'
 
            assert f.mode == 0o640
roles/xmpp_server/tests/test_client.py
Show inline comments
 
new file 100644
 
import testinfra.utils.ansible_runner
 

	
 

	
 
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
 
    '.molecule/ansible_inventory').get_hosts('client1')
 

	
 

	
 
def test_connectivity(Command, Sudo):
 
    """
 
    Tests connectivity to the XMPP server (ports that should be reachable).
 
    """
 

	
 
    with Sudo():
 

	
 
        for server in ["parameters-mandatory",
 
                       "parameters-optional"]:
 
            # c2s plaintext, c2s TLS, file proxy, s2s.
 
            for port in [5222, 5223, 5000, 5269]:
 

	
 
                ping = Command('hping3 -S -p %d -c 1 %s' % (port, server))
 
                assert ping.rc == 0
 

	
 

	
 
def test_tls(Command):
 
    """
 
    Tests if TLS works as expected.
 
    """
 

	
 
    send = Command("echo 'Hello' | sendxmpp -t -u john.doe -p johnpassword -j domain1:5222 john.doe@domain1")
 
    assert send.rc == 0
 

	
 
    send = Command("echo 'Hello' | sendxmpp -e -u john.doe -p johnpassword -j domain1:5223 john.doe@domain1")
 
    assert send.rc == 0
 

	
 
    send = Command("echo 'Hello' | sendxmpp -t -u jane.doe -p janepassword -j domain2:5222 jane.doe@domain2")
 
    assert send.rc == 0
 

	
 
    send = Command("echo 'Hello' | sendxmpp -e -u jane.doe -p janepassword -j domain2:5223 jane.doe@domain2")
 
    assert send.rc == 0
 

	
 

	
 
def test_authentication_requires_tls(Command):
 
    """
 
    Tests if authentication must be done over TLS.
 
    """
 

	
 
    command = Command("echo 'Hello' | sendxmpp -u bogus -p bogus -j domain1:5222 john.doe@domain1 -d")
 
    assert "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'><required/></starttls>" in command.stderr
 

	
 
    command = Command("echo 'Hello' | sendxmpp -u bogus -p bogus -j domain2:5222 jane.doe@domain2 -d")
 
    assert "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'><required/></starttls>" in command.stderr
 

	
 

	
 
def test_authentication(Command):
 
    """
 
    Tests if authentication works correctly.
 
    """
 

	
 
    send = Command("echo 'Hello' | sendxmpp -t -u john.doe -p johnpassword -j domain1:5222 john.doe@domain1")
 
    assert send.rc == 0
 

	
 
    send = Command("echo 'Hello' | sendxmpp -e -u john.doe -p johnpassword -j domain1:5223 john.doe@domain1")
 
    assert send.rc == 0
 

	
 
    send = Command("echo 'Hello' | sendxmpp -t -u jane.doe -p janepassword -j domain2:5222 jane.doe@domain2")
 
    assert send.rc == 0
 

	
 
    send = Command("echo 'Hello' | sendxmpp -e -u mick.doe -p mickpassword -j domain3:5223 mick.doe@domain3")
 
    assert send.rc == 0
 

	
 

	
 
def test_unauthorized_users_rejected(Command):
 
    """
 
    Tests if unauthorized users (present in LDAP, but not member of correct
 
    group) are rejected from accessing the XMPP server.
 
    """
 

	
 
    send = Command("echo 'Hello' | sendxmpp -t -u noxmpp -p noxmpppassword -j domain1:5222 john.doe@domain1")
 
    assert send.rc != 0
 
    assert "Error 'AuthSend': error: not-authorized[?]" in send.stderr
roles/xmpp_server/tests/test_default.py
Show inline comments
 
import testinfra.utils.ansible_runner
 

	
 

	
 
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
 
    '.molecule/ansible_inventory').get_hosts('all')
 
    '.molecule/ansible_inventory').get_hosts(['parameters-mandatory', 'parameters-optional'])
 

	
 

	
 
def test_installed_packages(Package):
 
    """
 
    Tests if all the necessary packages have been installed.
 
    """
 

	
 
    assert Package('python-apt').is_installed
 
    assert Package('lua-sec').is_installed
 
    assert Package('lua-ldap').is_installed
 
    assert Package('prosody').is_installed
 

	
 

	
 
def test_prosody_apt_key(Command):
 
    """
 
    Tests if Prosody repository signing key has been imported.
 
    """
 

	
 
    keys = Command("apt-key list")
 

	
 
    assert "1024D/74D9DBB5" in keys.stdout
 
    assert "4096g/6C8F28BA" in keys.stdout
 

	
 

	
 
def test_prosody_repository(File):
 
    """
 
    Tests if Prosody repository has been added.
 
    """
 

	
 
    repository = File("/etc/apt/sources.list.d/packages_prosody_im_debian.list")
 

	
 
    assert repository.is_file
 
    assert repository.user == 'root'
 
    assert repository.group == 'root'
 
    assert repository.mode == 0o644
 
    assert repository.content == "deb http://packages.prosody.im/debian jessie main"
 

	
 

	
 
def test_prosody_user(User):
 
    """
 
    Tests if Prosody user has been set-up correctly to access TLS material.
 
    """
 

	
 
    assert 'ssl-cert' in User('prosody').groups
 

	
 

	
 
def test_prosody_modules_directory(File):
 
    """
 
    Tests if directory for storing additional Prosody modules is set-up
 
    correctly.
 
    """
 

	
 
    directory = File('/usr/local/lib/prosody/modules')
 

	
 
    assert directory.is_directory
 
    assert directory.user == 'root'
 
    assert directory.group == 'root'
 
    assert directory.mode == 0o755
 

	
 

	
 
def test_prosody_mod_auth_ldap(File):
 
    """
 
    Tests if Prosody module mod_auth_ldap has been deployed correctly.
 
    """
 

	
 
    module = File('/usr/local/lib/prosody/modules/mod_auth_ldap.lua')
 

	
 
    assert module.is_file
 
    assert module.user == 'root'
 
    assert module.group == 'root'
 
    assert module.mode == 0o644
 
    assert 'module:provides("auth", provider);' in module.content
 
    assert 'mod_auth_ldap' in module.content
 

	
 

	
 
def test_prosody_configuration_file(File, Sudo):
 
    """
 
    Tests if Prosody configuration file has correct permissions.
 
    """
 

	
 
    with Sudo():
 

	
 
        config = File('/etc/prosody/prosody.cfg.lua')
 

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

	
 

	
 
def test_services(Service):
 
    """
 
    Tests if services are enabled and running.
 
    """
 

	
 
    service = Service('prosody')
 

	
 
    assert service.is_enabled
 
    assert service.is_running
 

	
 

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

	
 
    with Sudo():
 

	
 
        config = File('/etc/ferm/conf.d/30-xmpp.conf')
 

	
 
def test_hosts_file(File):
 
    f = File('/etc/hosts')
 
        assert config.is_file
 
        assert config.user == 'root'
 
        assert config.group == 'root'
 
        assert config.mode == 0o640
 

	
 
    assert f.exists
 
    assert f.user == 'root'
 
    assert f.group == 'root'
 
# @TODO: Tests which were not implemented due to lack of out-of-box tools:
 
#
 
# - Proxy capability.
 
# - MUC.
 
# - Server administration through XMPP.
roles/xmpp_server/tests/test_mandatory.py
Show inline comments
 
new file 100644
 
import testinfra.utils.ansible_runner
 

	
 

	
 
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
 
    '.molecule/ansible_inventory').get_hosts('parameters-mandatory.domain1')
 

	
 

	
 
def test_prosody_tls_files(File, Sudo):
 
    """
 
    Tests if Prosody TLS private key and certificage have been deployed
 
    correctly.
 
    """
 

	
 
    with Sudo():
 

	
 
        tls_file = File('/etc/ssl/private/parameters-mandatory.domain1_xmpp.key')
 
        assert tls_file.is_file
 
        assert tls_file.user == 'root'
 
        assert tls_file.group == 'prosody'
 
        assert tls_file.mode == 0o640
 
        assert tls_file.content == open("tests/data/x509/parameters-mandatory.domain1_xmpp.key", "r").read().rstrip()
 

	
 
        tls_file = File('/etc/ssl/certs/parameters-mandatory.domain1_xmpp.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 == open("tests/data/x509/parameters-mandatory.domain1_xmpp.pem", "r").read().rstrip()
 

	
 

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

	
 
    config = File('/etc/check_certificate/parameters-mandatory.domain1_xmpp.conf')
 
    assert config.is_file
 
    assert config.user == 'root'
 
    assert config.group == 'root'
 
    assert config.mode == 0o644
 
    assert config.content == "/etc/ssl/certs/parameters-mandatory.domain1_xmpp.pem"
 

	
 

	
 
def test_prosody_configuration_file_content(File, Sudo):
 
    """
 
    Tests if Prosody configuration file has correct content.
 
    """
 

	
 
    with Sudo():
 

	
 
        config = File('/etc/prosody/prosody.cfg.lua')
 

	
 
        assert "admins = { \"john.doe@domain1\",  }" in config.content
 
        assert "key = \"/etc/ssl/private/parameters-mandatory.domain1_xmpp.key\";" in config.content
 
        assert "certificate = \"/etc/ssl/certs/parameters-mandatory.domain1_xmpp.pem\";" in config.content
 
        assert "ldap_server = \"ldap-server\"" in config.content
 
        assert "ldap_rootdn = \"cn=prosody,ou=services,dc=local\"" in config.content
 
        assert "ldap_password = \"prosodypassword\"" in config.content
 
        assert "ldap_filter = \"(&(mail=$user@$host)(memberOf=cn=xmpp,ou=groups,dc=local))\"" in config.content
 
        assert "ldap_base = \"ou=people,dc=local\"" in config.content
 

	
 
        assert """VirtualHost "domain1"
 
Component "conference.domain1" "muc"
 
  restrict_room_creation = "local"
 
Component "proxy.domain1" "proxy65"
 
  proxy65_acl = { "domain1" }""" in config.content
roles/xmpp_server/tests/test_optional.py
Show inline comments
 
new file 100644
 
import testinfra.utils.ansible_runner
 

	
 

	
 
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
 
    '.molecule/ansible_inventory').get_hosts('parameters-optional')
 

	
 

	
 
def test_prosody_tls_files(File, Sudo):
 
    """
 
    Tests if Prosody TLS private key and certificage have been deployed
 
    correctly.
 
    """
 

	
 
    with Sudo():
 

	
 
        tls_file = File('/etc/ssl/private/parameters-optional_xmpp.key')
 
        assert tls_file.is_file
 
        assert tls_file.user == 'root'
 
        assert tls_file.group == 'prosody'
 
        assert tls_file.mode == 0o640
 
        assert tls_file.content == open("tests/data/x509/parameters-optional_xmpp.key.pem", "r").read().rstrip()
 

	
 
        tls_file = File('/etc/ssl/certs/parameters-optional_xmpp.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 == open("tests/data/x509/parameters-optional_xmpp.cert.pem", "r").read().rstrip()
 

	
 

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

	
 
    config = File('/etc/check_certificate/parameters-optional_xmpp.conf')
 
    assert config.is_file
 
    assert config.user == 'root'
 
    assert config.group == 'root'
 
    assert config.mode == 0o644
 
    assert config.content == "/etc/ssl/certs/parameters-optional_xmpp.pem"
 

	
 

	
 
def test_prosody_configuration_file_content(File, Sudo):
 
    """
 
    Tests if Prosody configuration file has correct content.
 
    """
 

	
 
    with Sudo():
 

	
 
        config = File('/etc/prosody/prosody.cfg.lua')
 

	
 
        assert "admins = { \"jane.doe@domain2\", \"mick.doe@domain3\",  }" in config.content
 
        assert "key = \"/etc/ssl/private/parameters-optional_xmpp.key\";" in config.content
 
        assert "certificate = \"/etc/ssl/certs/parameters-optional_xmpp.pem\";" in config.content
 
        assert "ldap_server = \"ldap-server\"" in config.content
 
        assert "ldap_rootdn = \"cn=prosody,ou=services,dc=local\"" in config.content
 
        assert "ldap_password = \"prosodypassword\"" in config.content
 
        assert "ldap_filter = \"(&(mail=$user@$host)(memberOf=cn=xmpp,ou=groups,dc=local))\"" in config.content
 
        assert "ldap_base = \"ou=people,dc=local\"" in config.content
 

	
 
        assert """VirtualHost "domain2"
 
Component "conference.domain2" "muc"
 
  restrict_room_creation = "local"
 
Component "proxy.domain2" "proxy65"
 
  proxy65_acl = { "domain2" }""" in config.content
 

	
 
        assert """VirtualHost "domain3"
 
Component "conference.domain3" "muc"
 
  restrict_room_creation = "local"
 
Component "proxy.domain3" "proxy65"
 
  proxy65_acl = { "domain3" }""" in config.content
0 comments (0 inline, 0 general)