Changeset - 52c4a4001c46
[Not reviewed]
0 7 0
Branko Majic (branko) - 4 years ago 2020-10-01 21:08:08
branko@majic.rs
MAR-164: Harden the c2s TLS configuration for the XMPP server role:

- Updated the xmpp_server role.
- Added (optional) xmpp_server_tls_protocol and
xmpp_server_tls_ciphers parameters for specifying the desired
TLS protocol version and ciphers for the c2s connections.
- Updated XMPP server configuration to introduce separate TLS
configuration for the s2s and c2s (legacy included) connections.
- Drop support for Prosody 0.9 since it is not possible to have
separate TLS configuration for c2s and s2s connections.
- Updated role reference documentation.
7 files changed with 206 insertions and 12 deletions:
0 comments (0 inline, 0 general)
docs/rolereference.rst
Show inline comments
 
@@ -845,6 +845,8 @@ Prosody is configured as follows:
 
* Client-to-server communication requires encryption (TLS).
 
* Uses 2048-bit Diffie-Hellman parameters for relevant TLS ciphers for
 
  incoming connections.
 
* Configures TLS versions and ciphers supported by Prosody (for
 
  *c2s*/client connections only).
 
* Authentication is done via LDAP. For setting the LDAP TLS truststore, see
 
  :ref:`LDAP Client <ldap_client>`.
 
* Internal storage is used.
 
@@ -853,12 +855,6 @@ Prosody is configured as follows:
 
* For each domain specified, a dedicated file proxy service will be set-up, with
 
  FQDN set to ``proxy.DOMAIN``.
 

	
 
.. warning::
 
   Since it is not possible to set-up separate TLS configuration for *c2s* and
 
   *s2s* connections in Prosody 0.9.x, no hardening of TLS is performed in order
 
   to improve interoperability. This will be changed in Prosody 0.10.x, at which
 
   point hardening can be revisited.
 

	
 
Prosody expects a specific directory structure in LDAP when doing look-ups:
 

	
 
* Prosody will log-in to LDAP as user
 
@@ -927,8 +923,26 @@ Parameters
 
**xmpp_prosody_package** (string, optional, ``prosody-0.10``)
 
  Name of Prosody package from the Prosody repositories to
 
  install. This makes it possible to easily test the latest Prosody or
 
  to switch to a different nightly builds. It should be noted that
 
  only the default version is getting properly tested.
 
  to switch to a different nightly build. It should be noted that
 
  only the default version is getting properly tested. Prosody
 
  versions lower than ``0.10.x`` are not supported.
 

	
 
**xmpp_server_tls_ciphers** (string, optional ``DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305:!aNULL:!MD5:!EXPORT``)
 
  TLS ciphers to enable on the XMPP server. This should be an
 
  OpenSSL-compatible cipher specification. Value should be compatible
 
  with Prosody's option ``ciphers`` normally defined within the
 
  ``ssl`` section of configuration file (see `official documentation
 
  <https://prosody.im/doc/advanced_ssl_config#ciphers>`_ for details).
 
  Default value allows only TLSv1.2 and strong PFS ciphers with RSA
 
  private keys.
 

	
 
**xmpp_server_tls_protocol** (string, optional, ``tlsv1_2+``)
 
  Protocol version the XMPP server should support for client
 
  connections. The value specified should be compatible with Prosody's
 
  ``protocol`` option normally defined within the ``ssl`` section of
 
  configuration file (see `official documentation
 
  <https://prosody.im/doc/advanced_ssl_config#protocol>`_ for
 
  details).
 

	
 
**xmpp_tls_certificate** (string, mandatory)
 
  X.509 certificate used for TLS for XMPP service. The file will be stored in
roles/xmpp_server/defaults/main.yml
Show inline comments
 
@@ -4,3 +4,12 @@ enable_backup: false
 
xmpp_domains:
 
  - "{{ ansible_domain }}"
 
xmpp_prosody_package: "prosody-0.10"
 
xmpp_server_tls_protocol: "tlsv1_2+"
 
xmpp_server_tls_ciphers: "\
 
DHE-RSA-AES128-GCM-SHA256:\
 
DHE-RSA-AES256-GCM-SHA384:\
 
DHE-RSA-CHACHA20-POLY1305:\
 
ECDHE-RSA-AES128-GCM-SHA256:\
 
ECDHE-RSA-AES256-GCM-SHA384:\
 
ECDHE-RSA-CHACHA20-POLY1305:\
 
!aNULL:!MD5:!EXPORT"
roles/xmpp_server/molecule/default/group_vars/parameters-optional.yml
Show inline comments
 
@@ -9,9 +9,13 @@ xmpp_domains:
 
xmpp_ldap_base_dn: dc=local
 
xmpp_ldap_password: prosodypassword
 
xmpp_ldap_server: ldap-server
 
xmpp_prosody_package: prosody-0.9
 
xmpp_prosody_package: prosody-0.10
 
xmpp_tls_certificate: "{{ lookup('file', 'tests/data/x509/server/{{ inventory_hostname }}_xmpp.cert.pem') }}"
 
xmpp_tls_key: "{{ lookup('file', 'tests/data/x509/server/{{ inventory_hostname }}_xmpp.key.pem') }}"
 
xmpp_server_tls_protocol: "tlsv1+"
 
xmpp_server_tls_ciphers: "DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-GCM-SHA384:\
 
DHE-RSA-AES256-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-GCM-SHA384:\
 
ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:!aNULL:!MD5:!EXPORT"
 

	
 
# common
 
ca_certificates:
roles/xmpp_server/molecule/default/prepare.yml
Show inline comments
 
@@ -58,9 +58,17 @@
 

	
 
    - name: Install tools for testing
 
      apt:
 
        name: gnutls-bin
 
        name:
 
          - gnutls-bin
 
          - nmap
 
        state: present
 

	
 
    - name: Use name provided via CLI when running STARTTLS handshake for XMPP via nmap
 
      replace:
 
        path: "/usr/share/nmap/nselib/sslcert.lua"
 
        regexp: "host\\.name\\)"
 
        replace: "host.targetname)"
 

	
 
- hosts: stretch
 
  become: true
 
  tasks:
roles/xmpp_server/molecule/default/tests/test_mandatory.py
Show inline comments
 
import os
 

	
 
import defusedxml.ElementTree as ElementTree
 

	
 
import pytest
 

	
 
import testinfra.utils.ansible_runner
 

	
 

	
 
@@ -77,3 +81,69 @@ def test_xmpp_server_uses_correct_dh_parameters(host):
 
    used_dhparam = output[output.find(begin_marker):output.find(end_marker) + len(end_marker)]
 

	
 
    assert used_dhparam == expected_dhparam
 

	
 

	
 
def test_tls_connectivity(host):
 
    """
 
    Tests if it is possible to connect to the XMPP server using
 
    STARTTLS/TLS.
 
    """
 

	
 
    starttls = host.run('echo "test" | openssl s_client -quiet -starttls xmpp -xmpphost domain1 -connect localhost:5222')
 
    assert starttls.rc == 0
 
    assert 'jabber:client' in starttls.stdout
 
    assert 'not-well-formed' in starttls.stdout
 

	
 
    tls = host.run('echo "test" | openssl s_client -quiet -connect domain1:5223')
 
    assert tls.rc == 0
 
    assert 'jabber:client' in starttls.stdout
 
    assert 'not-well-formed' in starttls.stdout
 

	
 
    s2s = host.run('echo "test" | openssl s_client -quiet -starttls xmpp-server -xmpphost domain1 -connect localhost:5222')
 
    assert s2s.rc == 0
 
    assert 'jabber:client' in s2s.stdout
 
    assert 'not-well-formed' in s2s.stdout
 

	
 

	
 
@pytest.mark.parametrize("port", [
 
    5222,
 
    5223
 
])
 
def test_xmpp_c2s_tls_version_and_ciphers(host, port):
 
    """
 
    Tests if the correct TLS version and ciphers have been enabled for
 
    XMPP C2S ports.
 
    """
 

	
 
    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 domain1 -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[@id='ssl-enum-ciphers']/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
roles/xmpp_server/molecule/default/tests/test_optional.py
Show inline comments
 
import os
 

	
 
import defusedxml.ElementTree as ElementTree
 

	
 
import pytest
 

	
 
import testinfra.utils.ansible_runner
 

	
 

	
 
@@ -61,7 +65,7 @@ def test_correct_prosody_package_installed(host):
 
    Tests if correct Prosody package has been installed.
 
    """
 

	
 
    assert host.package('prosody-0.9').is_installed
 
    assert host.package('prosody-0.10').is_installed
 

	
 

	
 
def test_xmpp_server_uses_correct_dh_parameters(host):
 
@@ -83,3 +87,72 @@ def test_xmpp_server_uses_correct_dh_parameters(host):
 
    used_dhparam = output[output.find(begin_marker):output.find(end_marker) + len(end_marker)]
 

	
 
    assert used_dhparam == expected_dhparam
 

	
 

	
 
def test_tls_connectivity(host):
 
    """
 
    Tests if it is possible to connect to the XMPP server using
 
    STARTTLS/TLS.
 
    """
 

	
 
    starttls = host.run('echo "test" | openssl s_client -quiet -starttls xmpp -xmpphost domain2 -connect localhost:5222')
 
    assert starttls.rc == 0
 
    assert 'jabber:client' in starttls.stdout
 
    assert 'not-well-formed' in starttls.stdout
 

	
 
    tls = host.run('echo "test" | openssl s_client -quiet -connect domain2:5223')
 
    assert tls.rc == 0
 
    assert 'jabber:client' in starttls.stdout
 
    assert 'not-well-formed' in starttls.stdout
 

	
 
    s2s = host.run('echo "test" | openssl s_client -quiet -starttls xmpp-server -xmpphost domain2 -connect localhost:5222')
 
    assert s2s.rc == 0
 
    assert 'jabber:client' in s2s.stdout
 
    assert 'not-well-formed' in s2s.stdout
 

	
 

	
 
@pytest.mark.parametrize("port", [
 
    5222,
 
    5223
 
])
 
def test_xmpp_c2s_tls_version_and_ciphers(host, port):
 
    """
 
    Tests if the correct TLS version and ciphers have been enabled for
 
    XMPP C2S ports.
 
    """
 

	
 
    expected_tls_versions = ["TLSv1.0", "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 domain2 -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[@id='ssl-enum-ciphers']/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
roles/xmpp_server/templates/prosody.cfg.lua.j2
Show inline comments
 
@@ -41,12 +41,28 @@ allow_registration = false;
 

	
 
-- These are the SSL/TLS-related settings. If you don't want
 
-- to use SSL/TLS, you may comment or remove this
 
ssl = {
 
s2s_ssl = {
 
  key = "/etc/ssl/private/{{ ansible_fqdn }}_xmpp.key";
 
  certificate = "/etc/ssl/certs/{{ ansible_fqdn }}_xmpp.pem";
 
  dhparam = "/etc/ssl/private/{{ ansible_fqdn }}_xmpp.dh.pem";
 
}
 

	
 
c2s_ssl = {
 
  key = "/etc/ssl/private/{{ ansible_fqdn }}_xmpp.key";
 
  certificate = "/etc/ssl/certs/{{ ansible_fqdn }}_xmpp.pem";
 
  dhparam = "/etc/ssl/private/{{ ansible_fqdn }}_xmpp.dh.pem";
 
  protocol = "{{ xmpp_server_tls_protocol }}";
 
  ciphers = "{{ xmpp_server_tls_ciphers }}";
 
}
 

	
 
legacy_ssl_ssl = {
 
  key = "/etc/ssl/private/{{ ansible_fqdn }}_xmpp.key";
 
  certificate = "/etc/ssl/certs/{{ ansible_fqdn }}_xmpp.pem";
 
  dhparam = "/etc/ssl/private/{{ ansible_fqdn }}_xmpp.dh.pem";
 
  protocol = "{{ xmpp_server_tls_protocol }}";
 
  ciphers = "{{ xmpp_server_tls_ciphers }}";
 
}
 

	
 
-- Ports on which to have direct TLS/SSL.
 
legacy_ssl_ports = { 5223 }
 

	
0 comments (0 inline, 0 general)