Changeset - 52c4a4001c46
[Not reviewed]
0 7 0
Branko Majic (branko) - 5 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
 
@@ -464,852 +464,866 @@ packages on all servers:
 
  ---
 

	
 
  os_users:
 
    - name: admin
 
      uid: 1000
 
      additional_groups:
 
        - sudo
 
      authorized_keys:
 
        - "{{ lookup('file', '/home/admin/.ssh/id_rsa.pub') }}"
 
      password: '$6$AaJRWtqyX5pk$IP8DUjgY0y2zqMom9BAc.O9qHoQWLFCmEsPRCika6l/Xh87cp2SnlMywH0.r4uEcbHnoicQG46V9VrJ8fxp2d.'
 
    - name: john
 
      uid: 1001
 
      password: '$6$AaJRWtqyX5pk$IP8DUjgY0y2zqMom9BAc.O9qHoQWLFCmEsPRCika6l/Xh87cp2SnlMywH0.r4uEcbHnoicQG46V9VrJ8fxp2d.'
 

	
 
  os_groups:
 
    - name: localusers
 
      gid: 2500
 

	
 
  common_packages:
 
    - emacs23-nox
 
    - screen
 
    - debconf-utils
 

	
 
  ca_certificates:
 
    "truststore": "{{ lookup('file', '../certs/truststore.pem') }}"
 

	
 
  incoming_connection_limit: 2/second
 

	
 
  incoming_connection_limit_burst: 6
 

	
 
  prompt_colour: light_green
 

	
 
  prompt_id: PROD
 

	
 
.. _ldap_client:
 

	
 
LDAP Client
 
-----------
 

	
 
The ``ldap_client`` role can be used for setting-up an OpenLDAP client on
 
destination machine.
 

	
 
The role implements the following:
 

	
 
* Installs OpenLDAP client tools.
 
* Sets-up global configuration file for OpenLDAP clients at /etc/ldap/ldap.conf.
 

	
 

	
 
Parameters
 
~~~~~~~~~~
 

	
 
**ldap_client_config** (list, optional, ``[]``)
 
  A list of configuration options that should be put into the LDAP configuration
 
  file. Each item is a dictionary with the following options defining the
 
  configuration parameter:
 

	
 
  **comment** (string, mandatory)
 
    Comment that will be shown in the file just above the configuration option.
 

	
 
  **option** (string, mandatory)
 
    Name of configuration option.
 

	
 
  **value** (string, mandatory)
 
    Value for configuration option.
 

	
 

	
 
Distribution compatibility
 
~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Role is compatible with the following distributions:
 

	
 
- Debian 9 (Stretch)
 

	
 

	
 
Examples
 
~~~~~~~~
 

	
 
Here is an example configuration for setting some common LDAP client options:
 

	
 
.. code-block:: yaml
 

	
 
  ---
 

	
 
  ldap_client_config:
 
    - comment: Set the base DN
 
      option: BASE
 
      value: dc=example,dc=com
 
    - comment: Set the default URI
 
      option: URI
 
      value: ldap://ldap.example.com/
 
    - comment: Set the truststore for TLS/SSL
 
      option: TLS_CACERT
 
      value: /etc/ssl/certs/example_ca.pem
 
    - commment: Force basic server certificate verification
 
      option: TLS_REQCERT
 
      value: demand
 
    - comment: Disable CRL checks for server certificate
 
      option: TLS_CRLCHECK
 
      value: none
 

	
 

	
 
LDAP Server
 
-----------
 

	
 
The ``ldap_server`` role can be used for setting-up an OpenLDAP server on
 
destination machine.
 

	
 
The role implements the following:
 

	
 
* Deploys LDAP TLS private key and certificate.
 
* Configures TLS versions and ciphers suppported by the server.
 
* Installs OpenLDAP server (package ``slapd``).
 
* Configures OpenLDAP server (base DN - domain, organisation, TLS, SSF, log levels).
 
* Sets-up separate log file for OpenLDAP server at ``/var/log/slapd.log`` (with
 
  log rotation included).
 
* Enables the ``misc`` LDAP schema (from ``/etc/ldap/schema/misc.ldif``). This
 
  is necessary for the mail server role.
 
* Enables the ``memberof`` overlay on top of default database. The overlay is
 
  configured to keep track of membership changes for object class
 
  ``groupOfUniqueNames`` via attribute ``uniqueMember``. Enforcement of
 
  referential integrity is turned on as well (modifications of ``memberof``
 
  attribute will update corresponding group as well.
 
* Creates a basic directory structure used by most of the other roles.
 
* Creates a basic directory structure used by the mail server role.
 
* Creates login entries for services that need to consume LDAP directory data in
 
  some way.
 
* Creates user-supplied groups in LDAP.
 
* Configures permissions.
 
* Creates LDAP entries.
 
* Configures firewall to allow incoming connections to the LDAP server (via both
 
  TCP 389 and 636).
 
* Sets the LDAP server administrator's password.
 

	
 

	
 
LDIF Templates
 
~~~~~~~~~~~~~~
 

	
 
For adding users, use::
 

	
 
  dn: uid=USERNAME,ou=people,BASE_DN
 
  objectClass: inetOrgPerson
 
  objectClass: simpleSecurityObject
 
  uid: USERNAME
 
  userPassword: PASSWORD_FROM_SLAPPASSWD
 
  cn: NAME SURNAME
 
  sn: SURNAME
 
  gn: NAME
 
  displayName: DISPLAYNAME
 
  initials: INITIALS
 
  mail: MAIL
 
  mobile: MOBILE
 

	
 

	
 

	
 
Role dependencies
 
~~~~~~~~~~~~~~~~~
 

	
 
Depends on the following roles:
 

	
 
* **common**
 
* **ldap_client**
 
* **backup_client**
 

	
 

	
 
Backups
 
~~~~~~~
 

	
 
If the backup for this role has been enabled, the following paths are backed-up:
 

	
 
**/srv/backup/slapd.bak**
 
  Dump of the LDAP database. LDAP database dump is created every day at 01:45 in
 
  the morning. This does *not* include the dump of the config database
 
  (``cn=config``).
 

	
 

	
 
Parameters
 
~~~~~~~~~~
 

	
 
**ldap_admin_password** (string, mandatory)
 
  Password for the default administrator account of LDAP server (the
 
  ``cn=admin,DOMAIN`` entry/user).
 

	
 
**ldap_entries** (list, optional, ``[]``)
 
  List of entries that should be kept in the LDAP directory. Each item is a
 
  dictionary describing a single LDAP entry with the following keys:
 

	
 
  **dn** (string, mandatory)
 
    LDAP DN entry.
 

	
 
  **state** (string, optional, ``present``)
 
    Whether the entry should be present or not. Value can be anything
 
    supported by the ``ldap_entry`` module. Keep in mind that state
 
    ``present`` will not update the attributes and their values if the
 
    entry is already present.
 

	
 
  **attributes** (dictionary, mandatory)
 
    Dictionary describing remaining attributes (except ``dn``). The keys in this
 
    dictionary should be the attribute names. The values should be either
 
    strings, for setting a single attribute value, or a list of strings if it is
 
    necessary to set multiple values for the same attribute.
 

	
 
**ldap_permissions** (list, optional, ``see below``)
 
  List of LDAP access rules to apply to base DN served by the LDAP server. The
 
  listed access control rules will *replace* all existing rules, and will be
 
  added in the same order they are listed in. Each item is a string that
 
  constitutes a single access control rule. The format should be the same as
 
  described in `OpenLDAP Administrator's Guide
 
  <http://www.openldap.org/doc/admin24/access-control.html#Access%20Control%20via%20Dynamic%20Configuration>`.
 

	
 
  Default value is:
 

	
 
  .. code-block:: yaml
 

	
 
    - >
 
      to *
 
      by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
 
      by * break
 
    - >
 
      to attrs=userPassword,shadowLastChange
 
      by self write
 
      by anonymous auth
 
      by dn="cn=admin,BASEDN" write
 
      by * none
 
    - >
 
      to dn.base=""
 
      by * read
 
    - >
 
      to *
 
      by self write
 
      by dn="cn=admin,BASEDN" write
 
      by * none
 

	
 
**ldap_server_consumers** (list, optional, ``[]``)
 
  List of items describing additional login entries that should be created for
 
  services that want to be able to log-in into the LDAP server and consume the
 
  data present within. Each item should be a dictionary, with the following keys
 
  avaialable:
 

	
 
  - **name** (name of the service, mandatory, this will be used to construct the
 
    login entry DN in format of ``cn=NAME,ou=services,BASE_DN``)
 
  - **password** (password for the login entry, mandatory)
 
  - **state** (state of the service, optional, defaults to ``present``, this
 
    should be ``present`` or ``absent``, allowing for removal of old services)
 

	
 
**ldap_server_groups** (list, optional, ``[]``)
 
  List of groups that should be created in the LDAP directory. Each item should
 
  be a dictionary containing the following keys:
 

	
 
  - **name** (name of the group, mandatory, this will be used to construct the
 
    group DN in format of ``cn=NAME,ou=groups,BASE_DN``)
 
  - **state** (state of the group, optional, defaults to ``present``, this
 
    should be ``present`` or ``absent``, allowing for removal of old groups)
 

	
 
**ldap_server_domain** (string, optional, ``{{ ansible_domain }}``)
 
  Domain that should be used for constructing the base DN of default user LDAP
 
  database. This should be a sub-domain dedicated to organisation. The base DN
 
  will be constructed by putting all elements of the sub-domain as ``dc``
 
  entries (as per standard Debian convention). I.e. ``example.com`` would get
 
  transformed into ``dc=example,dc=com``.
 

	
 
**ldap_server_organization** (string, optional, ``Private``)
 
  Organization that should be specified in the base DN entry.
 

	
 
**ldap_server_log_level** (string, optional, ``256``)
 
  Log level to use for the server. This should be compatible with OpenLDAP
 
  configuration option ``olcLogLevel``. See `OpenLDAP Administrator's Guide
 
  <http://www.openldap.org/doc/admin24/slapdconf2.html#cn=config>` for value
 
  description and syntax.
 

	
 
**ldap_server_tls_certificate** (string, mandatory)
 
  X.509 certificate used for TLS for LDAP service. The file will be stored in
 
  directory ``/etc/ssl/certs/`` under name ``{{ ansible_fqdn }}_ldap.pem``.
 

	
 
**ldap_server_tls_key** (string, mandatory)
 
  Private key used for TLS for LDAP service. The file will be stored in
 
  directory ``/etc/ssl/private/`` under name ``{{ ansible_fqdn }}_ldap.key``.
 

	
 
**ldap_server_ssf** (number, optional, ``128``)
 
  Minimum *Security Strength Factor* to require from all incoming
 
  connections. This applies for both remote and local connections.
 

	
 
**ldap_tls_ciphers** (string, optional ``NONE:+VERS-TLS1.2:+CTYPE-X509:+COMP-NULL:+SIGN-RSA-SHA256:+SIGN-RSA-SHA384:+SIGN-RSA-SHA512:+DHE-RSA:+ECDHE-RSA:+SHA256:+SHA384:+SHA512:+AEAD:+AES-128-GCM:+AES-256-GCM:+CHACHA20-POLY1305:+CURVE-ALL``)
 

	
 
  .. warning::
 
     Under Debian Stretch, the DHE ciphers are not usable due to a bug
 
     present in OpenLDAP 2.4.44. See
 
     https://bugs.launchpad.net/ubuntu/+source/openldap/+bug/1656979
 
     for details.
 

	
 
  TLS ciphers to enable on the LDAP server. This should be a GnuTLS-compatible
 
  cipher specification that should also include what TLS protocol versions
 
  should be used. Value should be compatible with OpenLDAP server option
 
  ``olcTLSCipherSuite``. Default value allows only TLSv1.2 and strong PFS
 
  ciphers.
 

	
 

	
 
Distribution compatibility
 
~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Role is compatible with the following distributions:
 

	
 
- Debian 9 (Stretch)
 

	
 

	
 
Examples
 
~~~~~~~~
 

	
 
Here is an example configuration for setting-up LDAP server:
 

	
 
.. code-block:: yaml
 

	
 
  ---
 

	
 
  ldap_server_domain: "example.com"
 
  ldap_server_organization: "Example Corporation"
 
  ldap_server_log_level: 256
 
  ldap_server_tls_certificate: "{{ lookup('file', '~/tls/ldap.example.com_ldap.pem') }}"
 
  ldap_server_tls_key: "{{ lookup('file', '~/tls/ldap.example.com_ldap.key') }}"
 
  ldap_server_ssf: 128
 

	
 
  ldap_permissions:
 
    - >
 
      to *
 
      by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
 
      by * break
 
    - >
 
      to attrs=userPassword,shadowLastChange
 
      by self write
 
      by anonymous auth
 
      by dn="cn=admin,dc=example,dc=com" write
 
      by * none
 
    - >
 
      to dn.base=""
 
      by * read
 
    - >
 
      to *
 
      by self write
 
      by dn="cn=admin,dc=example,dc=com" write
 
      by users read
 
      by * none
 

	
 
  ldap_entries:
 
    - dn: ou=people,dc=example,dc=com
 
      attributes:
 
        objectClass: organizationalUnit
 
        ou: people
 
    - dn: ou=groups,dc=example,dc=com
 
      attributes:
 
        objectClass: organizationalUnit
 
        ou: groups
 
    - dn: uid=john,dc=example,dc=com
 
      attributes:
 
        objectClass:
 
          - inetOrgPerson
 
          - simpleSecurityObject
 
        userPassword: somepassword
 
        uid: john
 
        cn: John Doe
 
        sn: Doe
 

	
 

	
 
XMPP Server
 
-----------
 

	
 
The ``xmpp_server`` role can be used for setting-up Prosody, an XMPP server, on
 
destination machine.
 

	
 
The role implements the following:
 

	
 
* Sets-up the Prosody apt repository.
 
* Deploys XMPP TLS private key and certificate.
 
* Installs Prosody.
 
* Configures Prosody.
 
* Configures firewall to allow incoming connections to the XMPP server.
 

	
 
Prosody is configured as follows:
 

	
 
* Modules enabled: roster, saslauth, tls, dialback, posix, private, vcard,
 
  version, uptime, time, ping, pep, register, admin_adhoc, announce, legacyauth.
 
* Self-registration is not allowed.
 
* TLS is configured. Legacy TLS is available on port 5223.
 
* 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.
 
* For each domain specified, a dedicated conference/multi-user chat (MUC)
 
  service is set-up, with FQDN set to ``conference.DOMAIN``.
 
* 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
 
  ``cn=prosody,ou=services,XMPP_LDAP_BASE_DN``.
 
* User entries are read from sub-tree (first-level only)
 
  ``ou=people,XMPP_LDAP_BASE_DN``. Query filter used for finding users is
 
  ``(&(mail=$user@$host)(memberOf=cn=xmpp,ou=groups,XMPP_LDAP_BASE_DN))``. This
 
  allows group-based granting of XMPP service to users.
 

	
 

	
 
LDIF Templates
 
~~~~~~~~~~~~~~
 

	
 
For adding user to a group, use::
 

	
 
  dn: cn=xmpp,ou=groups,BASE_DN
 
  changetype: modify
 
  add: uniqueMember
 
  uniqueMember: uid=USERNAME,ou=people,BASE_DN
 

	
 

	
 
Role dependencies
 
~~~~~~~~~~~~~~~~~
 

	
 
Depends on the following roles:
 

	
 
* **common**
 
* **backup_client**
 

	
 

	
 
Backups
 
~~~~~~~
 

	
 
If the backup for this role has been enabled, the following paths are backed-up:
 

	
 
**/var/lib/prosody/**
 
  Roster information, as well as undelivered (offline) messages for all XMPP
 
  users. Keep in mind that list of available users and their credentials are
 
  stored in the LDAP directory (which is backed-up via LDAP server role).
 

	
 

	
 
Parameters
 
~~~~~~~~~~
 

	
 
**xmpp_administrators** (list, mandatory)
 
  List of Prosody users that should be granted administrator privileges over
 
  Prosody. Each item is a string with value equal to XMPP user ID
 
  (i.e. ``john.doe@example.com``).
 

	
 
**xmpp_domains** (list, optional, ``{{ ansible_domain }}``)
 
  List of domains that are served by this Prosody instance. Each item is a
 
  string specifying a domain.
 

	
 
**xmpp_ldap_base_dn** (string, mandatory)
 
  Base DN on the LDAP server. A specific directory structure is expected under
 
  this entry (as explained above) in order to locate the available domains,
 
  users, aliases etc.
 

	
 
**xmpp_ldap_password** (string, mandatory)
 
  Password used for authenticating to the LDAP server.
 

	
 
**xmpp_ldap_server** (string, mandatory)
 
  Fully qualified domain name, hostname, or IP address of the LDAP server used
 
  for user authentication and listing.
 

	
 
**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
 
  directory ``/etc/ssl/certs/`` under name ``{{ ansible_fqdn }}_xmpp.pem``.
 

	
 
**xmpp_tls_key** (string, mandatory)
 
  Private key used for TLS for XMPP service. The file will be stored in
 
  directory ``/etc/ssl/private/`` under name ``{{ ansible_fqdn }}_xmpp.key``.
 

	
 

	
 
Distribution compatibility
 
~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Role is compatible with the following distributions:
 

	
 
- Debian 9 (Stretch)
 

	
 

	
 
Examples
 
~~~~~~~~
 

	
 
Here is an example configuration for setting-up XMPP server using Prosody:
 

	
 
.. code-block:: yaml
 

	
 
  ---
 

	
 
  xmpp_administrators:
 
    - john.doe@example.com
 
  xmpp_domains:
 
    - example.com
 
  xmpp_ldap_base_dn: dc=example,dc=com
 
  xmpp_ldap_password: xmpp
 
  xmpp_ldap_server: ldap.example.com
 
  # These are default key and certificate that generated during Prosody
 
  # installation. Possibly you want to deploy your own.
 
  xmpp_tls_key: "{{ lookup('file', '/etc/prosody/certs/localhost.key') }}"
 
  xmpp_tls_certificate: "{{ lookup('file', '/etc/prosody/certs/localhost.crt') }}"
 

	
 

	
 
Mail Server
 
-----------
 

	
 
.. warning::
 
   It may happen that the ``clamav-freshclam`` service hasn't finished
 
   downloading the virus database before the ``clamav-daemon`` and
 
   ``clamav-milter`` services are enabled during the initial run. If mail server
 
   is not operational, you may need to wait for a little while for download to
 
   finish, and then restart the ``clamav-daemon`` and ``clamav-milter``
 
   services.
 

	
 
The ``mail_server`` role can be used for setting-up a complete mail server
 
solution, which includes both SMTP and IMAP service, on destination machine.
 

	
 
Postfix is used SMTP, while Dovecot is used for IMAP.
 

	
 
The role implements the following:
 

	
 
* Installs rsync.
 
* Deploys IMAP/SMTP TLS private keys and certificates.
 
* Installs and configures Dovecot, Postfix, ClamAV, and ClamAV Milter.
 
* Purges Exim4 configuration (just in case).
 
* Sets-up aliases for the local recipients.
 
* Installs SWAKS (utility for testing SMTP servers).
 
* Sets-up the necessary directories and files under Postfix chroot.
 
* Configures firewall to allow incoming connections to the mail server. This
 
  includes set-up of redirection from TCP port 26 to TCP port 587 (alternate
 
  submission port), as well as redirection from TCP port 27 to TCP port 25
 
  (alternate SMTP port), useful as workaround for ISP/hotel blocks.
 

	
 
Deployed services are configured as follows:
 

	
 
* Both Postfix and Dovecot look-up available domains, users, and aliases in
 
  LDAP.
 
* Incoming and outgoing mail is scanned with ClamAV (via ClamAV
 
  Milter). Infected mails are rejected.
 
* Mail is stored in directory ``/var/MAIL_USER/DOMAIN/USER``, using ``Maildir``
 
  format.
 
* TLS is required for user log-ins for both SMTP and IMAP.
 
* Uses 2048-bit Diffie-Hellman parameters for relevant TLS ciphers for
 
  incoming connections.
 
* For user submission (SMTP), users must connect and authenticate over TCP
 
  port 587.
 
* Configures TLS versions and ciphers supported by Dovecot.
 
* Configures TLS versions and ciphers supported by Postfix on submission port
 
  (587). TLS configuration on port 25 is kept intact in order to maintain maximum
 
  interoperability with other servers.
 
* RBL's are used for combating spam (if any is specified in configuration, see
 
  below).
 
* Postfix is configured to deliver undeliverable bounces to postmaster. This
 
  helps with detecting misconfigured applications and servers.
 

	
 
Both Postfix and Dovecot expect a specific directory structure in LDAP when
 
doing look-ups:
 

	
 
* Postfix will log-in to LDAP as user
 
  ``cn=postfix,ou=services,MAIL_LDAP_BASE_DN``.
 
* Dovecot will log-in to LDAP as user
 
  ``cn=dovecot,ou=services,MAIL_LDAP_BASE_DN``.
 
* Domain entries need to be available as
 
  ``dc=DOMAIN,ou=domains,ou=mail,ou=services,MAIL_LDAP_BASE_DN``.
 
* Alias entries need to be available as
 
  ``cn=ALIAS,ou=aliases,ou=mail,ou=services,MAIL_LDAP_BASE_DN``.
 
* User entries are read from sub-tree (first-level only)
 
  ``ou=people,MAIL_LDAP_BASE_DN``. Query filter used for finding users is
 
  ``(&(mail=%s)(memberOf=cn=mail,ou=groups,MAIL_LDAP_BASE_DN))``. This allows
 
  group-based granting of mail services to users.
 

	
 

	
 
LDIF Templates
 
~~~~~~~~~~~~~~
 

	
 
For adding domains, use::
 

	
 
  dn: dc=DOMAIN,ou=domains,ou=mail,ou=services,BASE_DN
 
  objectClass: dNSDomain
 
  dc: DOMAIN
 

	
 
For adding aliases, use::
 

	
 
  dn: cn=ALIAS,ou=aliases,ou=mail,ou=services,BASE_DN
 
  objectClass: nisMailAlias
 
  cn: ALIAS
 
  rfc822MailMember: REALEMAIL
 

	
 
For adding user to a group, use::
 

	
 
  dn: cn=mail,ou=groups,BASE_DN
 
  changetype: modify
 
  add: uniqueMember
 
  uniqueMember: uid=USERNAME,ou=people,BASE_DN
 

	
 

	
 
Role dependencies
 
~~~~~~~~~~~~~~~~~
 

	
 
Depends on the following roles:
 

	
 
* **common**
 
* **backup_client**
 

	
 

	
 
Backups
 
~~~~~~~
 

	
 
If the backup for this role has been enabled, the following paths are backed-up:
 

	
 
**/var/{{ mail_user }}**
 
  All data stored by the mail server, including mails and Sieve scripts. Keep in
 
  mind that list of available users and their credentials are stored in the LDAP
 
  directory (which is backed-up via LDAP server role).
 

	
 

	
 
Parameters
 
~~~~~~~~~~
 

	
 
**mail_ldap_base_dn** (string, mandatory)
 
  Base DN on the LDAP server. A specific directory structure is expected under
 
  this entry (as explained above) in order to locate the available domains,
 
  users, aliases etc.
 

	
 
**mail_ldap_url** (string, mandatory)
 
  LDAP URL that should be used for connecting to the LDAP server for doing
 
  domain/user look-ups.
 

	
 
**mail_ldap_tls_truststore** (string, mandatory)
 
  X.509 certificate chain used for issuing certificate for the LDAP service. The
 
  file will be stored in locations ``/etc/ssl/certs/mail_ldap_tls_truststore.pem``
 
  and ``/var/spool/postfix/etc/ssl/certs/mail_ldap_tls_truststore.pem``.
 

	
 
**mail_ldap_postfix_password** (string, mandatory)
 
  Password for authenticating the Postfix LDAP user.
 

	
 
**mail_ldap_dovecot_password** (string, mandatory)
 
  Password for authenticating the Dovecot LDAP user.
 

	
 
**mail_message_size_limit** (integer, optional, ``10240000``)
 
  Maximum size of message in bytes that the SMTP server should accept
 
  for incoming mails. If the mail message size exceeds the listed
 
  value, it will be rejected by the server. The size is also
 
  advertised as part of SMTP server capabilities (in response to the
 
  ``ehlo`` SMTP command).
 

	
 
**mail_server_tls_protocols** (list, optional, ``[ "TLSv1.2" ]``)
 
  List of TLS protocols the mail server should support. Each value specified
 
  should be compatible with Postfix configuration option
 
  ``smtpd_tls_mandatory_protocols`` and Dovecot configuration option
 
  ``ssl_protocols``.
 

	
 
**mail_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 mail server (for IMAP and SMTP submission). This
 
  should be an OpenSSL-compatible cipher specification. Value should be
 
  compatible with Postfix configuration option ``tls_high_cipherlist`` and
 
  Dovecot configuration option ``ssl_cipher_list``. Default value allows only
 
  TLSv1.2 and strong PFS ciphers.
 

	
 
**mail_user** (string, optional, ``vmail``)
 
  Name of the user that owns all the mail files.
 

	
 
**mail_user_uid** (integer, optional, ``whatever OS picks``)
 
  UID of the user that owns all the mail files.
 

	
 
**mail_user_gid** (integer, optional, ``whatever OS picks``)
 
  GID of the user that owns all the mail files.
 

	
 
**imap_max_user_connections_per_ip** (integer, optional, ``10``)
 
  Maximum number of IMAP connections from a single IP for a single user. Default
 
  value can be considered rather low, since two devices (computer and phone)
 
  will easily reach it.
 

	
 
**imap_tls_certificate** (string, mandatory)
 
  X.509 certificate used for TLS for IMAP service. The file will be stored in
 
  directory ``/etc/ssl/certs/`` under name ``{{ ansible_fqdn }}_imap.pem``.
 

	
 
**imap_tls_key** (string, mandatory)
 
  Private key used for TLS for IMAP service. The file will be stored in
 
  directory ``/etc/ssl/private/`` under name ``{{ ansible_fqdn }}_imap.key``.
 

	
 
**local_mail_aliases** (dictionary, optional, ``{}``)
 
  Dictionary defining the local aliases. Aliases defined this way will either be
 
  appended to default aliases on the server, or replace the existing entries (if
 
  the alias/recipient is already present). Keys in the dictionary are the local
 
  recipients/aliases, while the value provided should be a space-separated list
 
  of mail addresses (or local users) where the mails should be forwarded.
 

	
 
**smtp_tls_certificate** (string, mandatory)
 
  X.509 certificate used for TLS for SMTP service. The file will be stored in
 
  directory ``/etc/ssl/certs/`` under name ``{{ ansible_fqdn }}_smtp.pem``.
 

	
 
**smtp_tls_key** (string, mandatory)
 
  Private key used for TLS for SMTP service. The file will be stored in
 
  directory ``/etc/ssl/private/`` under name ``{{ ansible_fqdn }}_smtp.key``.
 

	
 
**imap_folder_separator** (string, optional, ``/``)
 
  Character used for separating the IMAP folders when clients are requesting
 
  listing from the server. Usually either slash(``/``) or dot(``.``).
 

	
 
**smtp_rbl** (list, optional, ``[]``)
 
  List of RBLs to use for detecting servers which send out spam. Each item is a
 
  string resembling the RBL domain.
 

	
 
**mail_postmaster** (string, optional, ``postmaster@{{ ansible_domain}}``)
 
  Mail address to use for the postmaster account in Dovecot.
 

	
 
**smtp_allow_relay_from** (list, optional, [])
 
  List of networks from which mail relaying is allowed even without
 
  authentication. Each item in the list is a string defining a network. The
 
  format must be compatible with Postfix ``mynetworks`` setting (for example:
 
  ``192.168.1.0/24``, ``myhost.example.com`` etc).
 

	
 

	
 
Distribution compatibility
 
~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Role is compatible with the following distributions:
 

	
 
- Debian 9 (Stretch)
 

	
 

	
 
Examples
 
~~~~~~~~
 

	
 
Here is an example configuration for setting-up XMPP server using Prosody:
 

	
 
.. code-block:: yaml
 

	
 
  ---
 

	
 
  mail_ldap_url: ldap://ldap.example.com/
 
  mail_ldap_tls_truststore: /etc/ssl/certs/truststore.pem
 
  mail_ldap_base_dn: dc=example,dc=com
 
  mail_ldap_postfix_password: postfix
 
  mail_ldap_dovecot_password: dovecot
 

	
 
  mail_user: vmail
 
  mail_user_uid: 5000
 
  mail_user_gid: 5000
 

	
 
  # All mails sent to local user root will be forwarded to external account as
 
  # well.
 
  local_mail_aliases:
 
    root: "root john.doe@example.com"
 

	
 
  imap_tls_certificate: "{{ lookup('file', '~/tls/mail.example.com_imap.pem') }}"
 
  imap_tls_key: "{{ lookup('file', '~/tls/mail.example.com_imap.key') }}"
 
  smtp_tls_certificate: "{{ lookup('file', '~/tls/mail.example.com_smtp.pem') }}"
 
  smtp_tls_key: "{{ lookup('file', '~/tls/mail.example.com_smtp.key') }}"
 
  imap_folder_separator: /
 
  smtp_rbl:
 
    - bl.spamcop.net
 
    - zen.spamhaus.org
 
  mail_postmaster: postmaster@example.com
 

	
 
  smtp_allow_relay_from:
 
    - ldap.example.com
 
    - xmpp.example.com
 

	
 
  imap_max_user_connections_per_ip: 50
 

	
 

	
 
Mail Forwarder
 
--------------
 

	
 
The ``mail_forwarder`` role can be used for setting-up a local SMTP server for
 
sending out mails and receiving mails for local users. The SMTP server is
 
provided by Postfix.
 

	
 
SMTP service on server set-up this way is not meant to be exposed to the
 
Internet directly, and should receive delivery failures from the relay server
 
instead.
 

	
 
The role implements the following:
 

	
 
* Installs and configures Postfix.
 
* Purges Exim4 configuration (just in case).
 
* Sets-up aliases for the local recipients.
 
* Installs SWAKS (utility for testing SMTP servers).
 
* Configures firewall to accept SMTP connections from SMTP relay (if one has
 
  been configured). This allows for delivery of bounced e-mails.
 

	
 
Postfix is configured as follows:
 

	
 
* Local destinations are set-up.
 
* A relay host is set.
 
* TLS is enforced for relaying mails, with configurable truststore for server
 
  certificate verification if SMTP relay is used. If SMTP relay is not used
 
  (configured), no certificate verification is done.
 
* Uses 2048-bit Diffie-Hellman parameters for relevant TLS ciphers for
 
  incoming connections.
 

	
 

	
 
Role dependencies
 
~~~~~~~~~~~~~~~~~
 

	
 
Depends on the following roles:
 

	
 
* **common**
 

	
 

	
 
Parameters
 
~~~~~~~~~~
 

	
 
**local_mail_aliases** (dictionary, optional, ``[]``)
 
  Dictionary defining the local aliases. Aliases defined this way will either be
 
  appended to default aliases on the server, or replace the existing entries (if
 
  the alias/recipient is already present). Keys in the dictionary are the local
 
  recipients/aliases, while the value provided should be a space-separated list
 
  of mail addresses (or local users) where the mails should be forwarded.
 

	
 
**mail_message_size_limit** (integer, optional, ``10240000``)
 
  Maximum size of message in bytes that the SMTP server should accept
 
  for incoming mails. If the mail message size exceeds the listed
 
  value, it will be rejected by the server. The size is also
 
  advertised as part of SMTP server capabilities (in response to the
 
  ``ehlo`` SMTP command). Changing the value is primarily useful when
 
  SMTP from relay is allowed (via the ``smtp_from_relay_allowed``
 
  parameter), since incoming SMTP communication is otherwise not
 
  allowed at all.
 

	
 
**smtp_from_relay_allowed** (boolean, optional, ``True``)
 
  Specify if SMTP traffic from SMTP relay should be allowed or not (for bounced
 
  messages, for example). This parameter should be set to ``False`` on systems
 
  behind NAT or on systems that may not have constant network connectivity (such
 
  as laptops) to avoid firewall failures since SMTP relay name needs to be
 
  resolvable.
 

	
 
**smtp_relay_host** (string, optional, ``None``)
 
  SMTP server via which the mails are sent out for non-local recipients.
 

	
 
**smtp_relay_host_port** (integer, optional, ``None``)
 
  Port to use when connecting to the SMTP relay host.
 

	
 
**smtp_relay_truststore** (string, mandatory)
 
  X.509 certificate chain used for issuing certificate for the SMTP relay
 
  service. The file will be stored in location
 
  ``/etc/ssl/certs/smtp_relay_truststore.pem``
 

	
 

	
 
Distribution compatibility
 
~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
Role is compatible with the following distributions:
 

	
 
- Debian 9 (Stretch)
roles/xmpp_server/defaults/main.yml
Show inline comments
 
---
 

	
 
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
 
---
 

	
 
xmpp_administrators:
 
  - jane.doe@domain2
 
  - mick.doe@domain3
 
xmpp_domains:
 
  - domain2
 
  - domain3
 
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:
 
  testca: "{{ lookup('file', 'tests/data/x509/ca/level1.cert.pem') }}"
 

	
 
# backup_client
 
enable_backup: true
 
backup_client_username: "bak-parameters-optional-{{ ansible_distribution_release }}"
 
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_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' ) }}"
roles/xmpp_server/molecule/default/prepare.yml
Show inline comments
 
---
 

	
 
- name: Set-up fixtures
 
  hosts: localhost
 
  connection: local
 
  gather_facts: false
 
  tasks:
 

	
 
    - name: Initialise CA hierarchy
 
      command: "gimmecert init"
 
      args:
 
        creates: ".gimmecert/ca/level1.cert.pem"
 
        chdir: "tests/data/"
 

	
 
    - name: Generate server private keys and certificates
 
      command:
 
      args:
 
        chdir: "tests/data/"
 
        creates: ".gimmecert/server/{{ item.name }}.cert.pem"
 
        argv: "{{ ['gimmecert', 'server', item.name] + item.fqdn }}"
 
      with_items:
 
        - name: ldap-server_ldap
 
          fqdn:
 
            - ldap-server
 
        - name: parameters-mandatory-stretch64.domain1_xmpp
 
          fqdn:
 
            - parameters-mandatory
 
            - domain1
 
        - name: parameters-optional-stretch64_xmpp
 
          fqdn:
 
            - parameters-optional
 
            - domain2
 
            - domain3
 

	
 
    - name: Set-up link to generated X.509 material
 
      file:
 
        src: ".gimmecert"
 
        dest: "tests/data/x509"
 
        state: link
 

	
 
- name: Prepare
 
  hosts: all
 
  gather_facts: false
 
  tasks:
 
    - name: Install python for Ansible
 
      raw: test -e /usr/bin/python3 || (apt -y update && apt install -y python3-minimal)
 
      become: true
 
      changed_when: false
 

	
 
- hosts: all
 
  become: true
 
  tasks:
 

	
 
    - name: Update all caches to avoid errors due to missing remote archives
 
      apt:
 
        update_cache: true
 
      changed_when: false
 

	
 
    - 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:
 

	
 
    - name: Set-up the hosts file
 
      lineinfile:
 
        path: /etc/hosts
 
        regexp: "^{{ item.key }}"
 
        line: "{{ item.key }} {{ item.value }}"
 
        owner: root
 
        group: root
 
        mode: 0644
 
        state: present
 
      with_dict:
 
        10.31.127.10: "ldap-server backup-server"
 
        10.31.127.21: "client-stretch"
 
        10.31.127.32: "parameters-mandatory domain1 proxy.domain1 conference.domain1"
 
        10.31.127.33: "parameters-optional domain2 proxy.domain2 conference.domain2 domain3 proxy.domain3 conference.domain3"
 

	
 
- hosts: clients
 
  become: true
 
  tasks:
 

	
 
    - name: Install tool for testing TCP connectivity
 
      apt:
 
        name: hping3
 
        state: present
 

	
 
    - name: Deploy CA certificate
 
      copy:
 
        src: tests/data/x509/ca/level1.cert.pem
 
        dest: /usr/local/share/ca-certificates/testca.crt
 
        owner: root
 
        group: root
 
        mode: 0644
 
      notify:
 
        - Update CA certificate cache
 

	
 
    - name: Install console-based XMPP client (for interactive testing)
 
      apt:
 
        name: mcabber
 
        state: present
 

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

	
 
    - name: Create dedicated group for testing
 
      group:
 
        name: user
 
        state: present
 

	
 
    - name: Create dedicated user for testing
 
      user:
 
        name: user
 
        group: user
 
        shell: /bin/bash
 

	
 
    - name: Deploy mcabber configuration files
 
      template:
 
        src: tests/data/mcabber.cfg.j2
 
        dest: "~user/{{ item.jid }}.cfg"
 
        owner: user
 
        group: user
 
        mode: 0600
 
      with_items:
 
        - jid: john.doe@domain1
 
          password: johnpassword
 
          server: domain1
 
          security: tls
 
          nickname: john.doe
 
        - jid: jane.doe@domain2
 
          password: janepassword
 
          server: domain2
 
          security: ssl
 
          nickname: jane.doe
 
        - jid: mick.doe@domain3
 
          password: mickpassword
 
          server: domain3
 
          security: tls
 
          nickname: mick.doe
 
        - jid: noxmpp@domain1
 
          password: noxmpppassword
 
          server: domain1
 
          security: tls
 
          nickname: noxmpp
 

	
 
  handlers:
 

	
 
    - name: Update CA certificate cache
 
      command: /usr/sbin/update-ca-certificates --fresh
 

	
 
- hosts: ldap-server
 
  become: true
 
  roles:
 
    - ldap_server
 
    - backup_server
 

	
 
- hosts: ldap-server
 
  become: true
 
  tasks:
 

	
 
    - name: Create LDAP accounts for testing
 
      ldap_entry:
 
        dn: "{{ item.dn }}"
 
        objectClass: "{{ item.objectClass }}"
 
        attributes: "{{ item.attributes }}"
 
      with_items:
 
        - dn: uid=john,ou=people,dc=local
 
          objectClass:
 
            - inetOrgPerson
 
            - simpleSecurityObject
 
          attributes:
 
            userPassword: johnpassword
 
            uid: john
 
            cn: John Doe
 
            sn: Doe
 
            mail: john.doe@domain1
 

	
 
        - dn: uid=jane,ou=people,dc=local
 
          objectClass:
 
            - inetOrgPerson
 
            - simpleSecurityObject
 
          attributes:
 
            userPassword: janepassword
 
            uid: jane
 
            cn: Jane Doe
 
            sn: Doe
 
            mail: jane.doe@domain2
 

	
 
        - dn: uid=mick,ou=people,dc=local
 
          objectClass:
 
            - inetOrgPerson
 
            - simpleSecurityObject
 
          attributes:
 
            userPassword: mickpassword
 
            uid: mick
 
            cn: Mick Doe
 
            sn: Doe
 
            mail: mick.doe@domain3
 

	
 
        - dn: uid=noxmpp,ou=people,dc=local
 
          objectClass:
 
            - inetOrgPerson
 
            - simpleSecurityObject
 
          attributes:
 
            userPassword: noxmpppassword
 
            uid: noxmpp
 
            cn: No XMPP
 
            sn: XMPP
 
            mail: noxmpp@domain1
 

	
 
    - name: Add test accounts to correct group
 
      ldap_attr:
 
        dn: "cn=xmpp,ou=groups,dc=local"
 
        name: uniqueMember
 
        state: exact
 
        values:
 
          - uid=john,ou=people,dc=local
 
          - uid=jane,ou=people,dc=local
 
          - uid=mick,ou=people,dc=local
 

	
 
- hosts: parameters-optional
 
  become: true
 
  tasks:
 

	
 
    - name: Install console-based XMPP tool (for non-interactive testing)
 
      apt:
 
        name: sendxmpp
 
        state: present
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
 

	
 

	
 
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.domain1_xmpp.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.domain1_xmpp.pem" % hostname
 

	
 

	
 
def test_prosody_configuration_file_content(host):
 
    """
 
    Tests if Prosody configuration file has correct content.
 
    """
 

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

	
 
    with host.sudo():
 

	
 
        config = host.file('/etc/prosody/prosody.cfg.lua')
 

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

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

	
 

	
 
def test_correct_prosody_package_installed(host):
 
    """
 
    Tests if correct Prosody package has been installed.
 
    """
 

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

	
 

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

	
 
    fqdn = host.run('hostname -f').stdout.strip()
 

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

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

	
 
    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_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
 

	
 

	
 
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_xmpp.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_xmpp.pem" % hostname
 

	
 

	
 
def test_prosody_configuration_file_content(host):
 
    """
 
    Tests if Prosody configuration file has correct content.
 
    """
 

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

	
 
    with host.sudo():
 

	
 
        config = host.file('/etc/prosody/prosody.cfg.lua')
 

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

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

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

	
 

	
 
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):
 
    """
 
    Tests if the HTTP server uses the generated Diffie-Hellman parameter.
 
    """
 

	
 
    fqdn = host.run('hostname -f').stdout.strip()
 

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

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

	
 
    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_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
 
-- Additional paths to search for modules.
 
plugin_paths = { "/usr/local/lib/prosody/modules/" }
 

	
 
-- List of server administrators.
 
admins = { {% for admin in xmpp_administrators %}"{{ admin }}", {% endfor %} }
 

	
 
-- List of modules to load on startup.
 
modules_enabled = {
 

	
 
  -- Generally required
 
    "roster"; -- Allow users to have a roster. Recommended ;)
 
    "saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
 
    "tls"; -- Add support for secure TLS on c2s/s2s connections
 
    "dialback"; -- s2s dialback support
 
    "disco"; -- Service discovery
 
    "posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
 

	
 
  -- Not essential, but recommended
 
    "private"; -- Private XML storage (for room bookmarks, etc.)
 
    "vcard"; -- Allow users to set vCards
 

	
 
  -- Nice to have
 
    "version"; -- Replies to server version requests
 
    "uptime"; -- Report how long server has been running
 
    "time"; -- Let others know the time here on this server
 
    "ping"; -- Replies to XMPP pings with pongs
 
    "pep"; -- Enables users to publish their mood, activity, playing music and more
 
    "register"; -- Allow users to register on this server using a client and change passwords
 

	
 
  -- Admin interfaces
 
    "admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
 

	
 
  -- Other specific functionality
 
    "announce"; -- Send announcement to all online users
 
    "legacyauth"; -- Allow legacy authentication and SSL
 
};
 

	
 
-- Disable account creation by default, for security
 
-- For more information see http://prosody.im/doc/creating_accounts
 
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 }
 

	
 
-- Force clients to use encrypted connection.
 
c2s_require_encryption = true
 

	
 
-- Disable certificate validation for server-to-server connections.
 
s2s_secure_auth = false
 

	
 
-- Path to Prosody's PID file.
 
pidfile = "/run/prosody/prosody.pid"
 

	
 
-- Authentication backend.
 
authentication = "ldap"
 
ldap_server = "{{ xmpp_ldap_server }}"
 
ldap_rootdn = "cn=prosody,ou=services,{{ xmpp_ldap_base_dn }}"
 
ldap_password = "{{ xmpp_ldap_password }}"
 
ldap_filter = "(&(mail=$user@$host)(memberOf=cn=xmpp,ou=groups,{{xmpp_ldap_base_dn}}))"
 
ldap_scope = "onelevel"
 
ldap_tls = true
 
ldap_base = "ou=people,{{ xmpp_ldap_base_dn }}"
 

	
 
-- Storage backend.
 
storage = "internal"
 

	
 
-- Logging configuration.
 
log = {
 
  info = "/var/log/prosody/prosody.log"; -- Change 'info' to 'debug' for verbose logging
 
  error = "/var/log/prosody/prosody.err";
 
  "*syslog";
 
}
 

	
 
-- Domains which should be handled by Prosody, with dedicated MUC and file
 
-- proxying components.
 
{% for domain in xmpp_domains -%}
 
VirtualHost "{{ domain }}"
 
Component "conference.{{ domain }}" "muc"
 
  restrict_room_creation = "local"
 
Component "proxy.{{ domain }}" "proxy65"
 
  proxy65_acl = { "{{ domain }}" }
 
{% endfor -%}
0 comments (0 inline, 0 general)