Changeset - 51c92f71fa0a
[Not reviewed]
0 8 0
Branko Majic (branko) - 5 years ago 2020-11-16 16:32:14
branko@majic.rs
MAR-170: Always enforce use of HTTPS in the web_server role:

- Dropped the default_enforce_https parameter.
- Updated tests.
- Updated release notes.
8 files changed with 31 insertions and 53 deletions:
0 comments (0 inline, 0 general)
docs/releasenotes.rst
Show inline comments
 
Release notes
 
=============
 

	
 

	
 
NEXT RELEASE
 
------------
 

	
 
Upgrade to Ansible 2.9.x, dropping support for Debian 8 Jessie,
 
upgrade to Python 3.x, dropping support for Python 2.7.
 

	
 
Breaking changes:
 

	
 
* Switched to Ansible 2.9.x, removing support for older versions. All
 
  documentation has been updated.
 
* Switched to using Python 3 on both controller and managed server
 
  side. Python 2.7 can no longer be used for this purpose. Support for
 
  WSGI applications running on Python 2.7 remains.
 

	
 
* All roles
 

	
 
  * Support for Debian 8 Jessie has been dropped.
 
  * Common parameters ``tls_private_key_dir`` and
 
    ``tls_certificate_dir`` are no longer used.
 
  * TLS private key and certificate parameters are now mandatory.
 

	
 
* ``bootstrap`` role
 

	
 
  * Parameter ``ansible_key`` is now mandatory.
 

	
 
* ``common`` role``
 

	
 
  * Minimum version of ``pip-tools`` in the ``pip_check_requirements``
 
    and ``pip_check_requirements_py3`` is now 5.3.0. This change was
 
    required in order to fix the deprecation warnings being sent out
 
    when the ``pip_check_requirements_upgrades.sh`` script is run.
 

	
 
* ``ldap_server`` role
 

	
 
  * Parameter ``ldap_server_domain`` is now mandatory.
 

	
 
  * Updated default set of TLS ciphers used by server
 
    (``ldap_tls_ciphers`` parameter). All CBC ciphers have been
 
    dropped. This could introduce incompatibility with older clients
 
    trying to connect to the LDAP server.
 

	
 
* ``mail_forwarder`` role
 

	
 
  * Use 2048-bit Diffie-Hellman parameters for relevant TLS
 
    ciphers. This could introduce incompatibility with older
 
    clients/servers trying to connect to the SMTP server.
 

	
 
* ``mail_server`` role
 

	
 
  * Use 2048-bit Diffie-Hellman parameters for relevant TLS
 
    ciphers. This could introduce incompatibility with older
 
    clients/servers trying to connect to the SMTP/IMAP server.
 

	
 
  * Updated default set of TLS ciphers used by IMAP/SMTP servers
 
    (``mail_server_tls_ciphers`` parameter). All CBC ciphers have been
 
    dropped. This could introduce incompatibility with older clients
 
    trying to connect to the IMAP/SMTP server.
 

	
 
* ``preseed`` role
 

	
 
  * Parameter ``ansible_key`` is now mandatory.
 

	
 
  * Parameter ``preseed_directory`` is now mandatory.
 

	
 
* ``web_server`` role
 

	
 
  * Use 2048-bit Diffie-Hellman parameters for relevant TLS
 
    ciphers. This could introduce incompatibility with older clients
 
    trying to connect to the web server.
 

	
 
  * Updated default set of TLS ciphers used by the server
 
    (``web_server_tls_ciphers`` parameter). All CBC ciphers have been
 
    dropped. This could introduce incompatibility with older clients
 
    trying to connect to the server.
 

	
 
  * Parameter ``default_enforce_https`` has been deprecated and
 
    removed. HTTPS is now mandatory in all cases.
 

	
 
* ``wsgi_website`` role
 

	
 
  * Parameters ``gunicorn_version`` and ``futures_version`` have been
 
    deprecated and removed. Existing roles should be updated to
 
    utilise the ``wsgi_requirements`` parameter instead.
 

	
 
  * Added parameter ``wsgi_requirements_in`` for listing top-level
 
    packages for performing pip requirements upgrade checks for
 
    Gunicorn requirements (listed via existing ``wsgi_requirements``
 
    parameter).
 

	
 
* ``xmpp_server`` role
 

	
 
  * Parameter ``xmpp_domains`` is now mandatory.
 

	
 
  * Use 2048-bit Diffie-Hellman parameters for relevant TLS
 
    ciphers. This could introduce incompatibility with older
 
    clients/servers trying to connect to the XMPP server.
 

	
 
  * TLS hardening is now applied to the *c2s* (client) connections on
 
    both the standard (``5222``) and legacy (``5223``) ports. Protocol
 
    version and ciphers are configurable via new
 
    ``xmpp_server_tls_protocol`` and ``xmpp_server_tls_ciphers``
 
    parameters with defaults enforcing TLSv1.2+ and PFS (perfect
 
    forward secrecy) ciphers.
 

	
 
  * Support for older Prosody versions (``0.9.x``) has been
 
    dropped. Only Prosody ``0.10.x`` is supported at the moment (due
 
    to missing Lua LDAP bindings in Debian 9 Stretch).
 

	
 
Bug fixes:
 

	
 
* ``common`` role
 

	
 
  * Run apticron at least once during initial installation to avoid
 
    accidental locking later on during the same playbook run.
 

	
 
* ``wsgi_website`` role
 

	
 
  * Deploy the requirement files used for upgrade checks to correct
 
    location when using Python 3. Previously the files would get
 
    deployed to directory dedicated to Python 2 version, which means
 
    the checks would be performed using Python 2 instead of Python 3.
 

	
 
New features/improvements:
 

	
 
* Tests have been updated to work with latest Molecule/Testinfra as
 
  part of the Ansible upgrade process.
 
* X.509 artefacts used during testing are now generated on the fly
 
  using `Gimmecert <https://gimmecert.readthedocs.io/>`_.
 

	
 
* ``mail_forwader`` role
 

	
 
  * The role now supports specifying the maximum mail message size
 
    limit for the SMTP server to accept via
 
    ``mail_message_size_limit`` role parameter.
 

	
 
* ``mail_server`` role
 

	
 
  * The role now supports specifying the maximum mail message size
 
    limit for the SMTP server to accept via
 
    ``mail_message_size_limit`` role parameter.
 

	
 
Deprecations:
 

	
 
* ``backup_server`` and ``backup_client`` role
 

	
 
  * Officially dropped support for DSA keys (this was mainly remnant
 
    from Debian 8 Jessie support, on Debian 9 Stretch and upwards the
 
    DSA keys were not supported at all).
 

	
 

	
 
4.0.0
 
-----
 

	
 
A couple of smaller bug-fixes, and introduction of (minor) breaking
 
change related to handling of pip requirements upgrade checks in the
 
``common`` role (see below).
 

	
 
Breaking changes:
 

	
 
* ``common`` role:
 

	
 
  * Added separate parameter (``pip_check_requirements_py3``) for
 
    specifying dedicated Python 3 virtual environment package
 
    requirements used for package upgrade checks on (other
 
    user-provided) Python 3 virtual environments. If the existing
 
    ``pip_check_requirements`` parameter has been overridden, the new
 
    parameter will most likely need to be overridden in your site
 
    configuration as well. Take note that the new requirements will
 
    differ between Debian Jessie and Debian Stretch due to differnece
 
    in Python 3 minor version releases.
 

	
 
Bug fixes:
 

	
 
* ``backup_client`` role
 

	
 
  * Avoid errors related to lack of ``tty`` when invoking the GnuPG
 
    utility by using the ``--no-tty`` option.
 

	
 
* ``common`` role
 

	
 
  * Fixed problem with pip requirements upgrades checks outputting
 
    package list to stderr, causing the cron job to report outdated
 
    packages to administrator even though nothing is outdated (cron
 
    job treats anything output to stderr as worthy of notification).
 

	
 

	
 
3.1.0
 
-----
 

	
 
Minor improvements and fixes.
 

	
 
Breaking changes:
 

	
 
* ``common`` role:
 

	
 
   * Default values for the ``pip_check_requirements`` have changed to
 
     include ``pip`` and ``setuptools`` (and a couple more). It might
 
     be necessary to update any customised values of this parameter to
 
     match the default set of packages.
 

	
 
Bug fixes:
 

	
 
* ``common`` role
 

	
 
  * Use Python 3 in Python virtual environment used for checking if
 
    upgrades are available for Python requirements files. Fixes
 
    possibly incorrect package resolution due to wrong version of
 
    Python (for packages that have different dependencies based on
 
    Python version).
 

	
 
New features/improvements:
 

	
 
* ``common`` role
 

	
 
  * Changed how the packages are installed in Python virtual
 
    environments used for performing pip requirements upgrade checks,
 
    making the process more reliable. The packages in those dedicated
 
    environments are now fully pinned, including system packages such
 
    as ``setuptools`` and ``pip`` itself.
 

	
 
  * Changed the pip requirements checks to now take into account
 
    unsafe packages as well (such as ``setuptools`` and ``pip``) if
 
    listed.
 

	
 

	
 
3.0.0
 
-----
 

	
 
Upgrade to Ansible 2.7.x and full support for Debian 9 (Stretch).
 

	
 
Breaking changes:
 

	
 
* Switched to Ansible 2.7.x, removing support for older versions. All
 
  documentation has been updated.
 

	
 
* All roles
 

	
 
  * In order to avoid collision with reserved name, the variable for
 
    running the handlers explicitly has been changed from ``handlers``
 
    to ``run_handlers``. E.g. to run all handlers a command similar to
 
    ``ansible-playbook -t handlers -e run_handlers=true
 
    playbooks/site.yml`` should be used.
 

	
 
* ``ldap_server`` role
 

	
 
  * Custom LDAP module ``m_ldap_entry`` has been removed. Role uses
 
    the official ``ldap_entry`` and ``ldap_attr`` modules.
 

	
 
  * The ``ldap_entries`` parameter now supports only the states
 
    supported by ``ldap_entry`` module (e.g. ``append`` is not
 
    supported any longer - since it came with custom LDAP module).
 

	
 
* ``xmpp_server`` role
 

	
 
  * Installs Prosody nightly builds (default version 0.10) instead of
 
    latest stable. Change made to improve reproducability, and to
 
    avoid future breakages after new major/minor releases of
 
    Prosody. Prosody package name can be specified through the
 
    ``xmpp_prosody_package`` variable.
 

	
 
    This should most likely not break anything, but is included as
 
    possibly breaking change nevertheless since it is a big change in
 
    how Prosody is installed.
 

	
 
    This change had to be done because LDAP integration broke with
 
    Prosody 0.11 due to missing LDAP bindings for Lua 5.2. See
 
    `MAR-137: XMPP server LDAP integration not working for Prosody
 
    0.11.x
 
    <https://projects.majic.rs/majic-ansible-roles/issues/MAR-137>`_
 
    for more details.
 

	
 
New features/improvements:
 

	
 
* Tests have been updated to work with latest Molecule/Testinfra as
 
  part of the Ansible upgrade process.
 

	
 
* All roles
 

	
 
  * Full support for Debian 9 (Stretch) has been added to all roles.
 

	
 
* ``common`` role
 

	
 
  * The ``pip`` requirements upgrade checks are now performed once per
 
    day instead of once per hour.
 
  * The ``pip`` requirements upgrade checks now do not output warning
 
    in case deployed ``.in`` file does not have a matching ``.txt``
 
    file.
 
  * Certificate expiration check is less verbose. No mails are sent
 
    out any longer in case no certificates have been configured for
 
    checking, nor in cases where all certificates have passed the
 
    check. E.g. mails are sent out only in case some of the configured
 
    certificates will expire within next 30 days.
 

	
 
* ``wsgi_website`` role
 

	
 
  * Support for specifying Python version for Python virtual
 
    environment.
 

	
 

	
 
2.0.0
 
-----
 

	
 
Upgrade to Ansible 2.3.x, minor bug fixes and updates needed for the upgrade.
 

	
 
Breaking changes:
 

	
 
* Switched to Ansible 2.3.x, removing support for Ansible 1.9.x. All
 
  documentation has been updated.
 

	
 
* Due to switch to Ansible 2.x which is more restrictive when deploying code on
 
  remote server, it is now necessary to use one of the methods listed in
 
  `Ansible documentation
 
  <https://docs.ansible.com/ansible/latest/become.html#becoming-an-unprivileged-user>`_
 
  if connecting to remote server as user other than ``root``. Easiest fix is to
 
  enable ``pipelining``. Tests have been already updated to take advantage of
 
  this.
 

	
 
* ``ldap_server`` role
 

	
 
   * Renamed ``ldap_entry`` module to ``m_ldap_entry`` to avoid collision with
 
     official module.
 
   * Renamed ``ldap_permissions`` module to ``m_ldap_permissions`` to be
 
     consistent and to avoid potential future collisions with official module
 
     names.
 

	
 
* ``wsgi_website`` role
 

	
 
   * Removed handler with parametrised name used for restarting the web
 
     service. Dependent roles should instead define their own handlers from now
 
     on.
 

	
 
New features/improvements:
 

	
 
* Updated documentation to refer to Debian Jessie documentation where necessary.
 

	
 
* ``ldap_server`` role
 

	
 
  * Updated tests to be more resilient to ordering changes.
 

	
 
* ``mail_forwarder`` role
 

	
 
  * Updated tests to be more resilient to ordering changes and time races.
 

	
 
* ``backup_client`` role
 

	
 
  * Switched to using the ``file`` module when cleaning-up GnuPG backup keyring,
 
    which should make it more robust..
 

	
 
Bug-fixes:
 

	
 
* Updated pip requirements and its input file to include ``python-vagrant``
 
  (needed for Molecule tests).
 

	
 
* Updated handling of key ID extraction for OpenPGP keys in order for it to work
 
  with Ansible 2.x.
 

	
 
* Updated usage instructions in order to be able to install The Bug Genie via
 
  script, and added missing instructions for creating one of the directories for
 
  the demo wiki role (``handlers`` directory).
 

	
 
* All roles
 

	
 
  * Updated test playbooks to avoid idempotence test failures due to apt cache
 
    updates.
 

	
 
* ``backup_client`` role
 

	
 
  * Fixed scenario in which backup keys could not be properly replaced on Debian
 
    Stretch machines (due to more up-to-date version of GnuPG compared to Debian
 
    Jessie).
 

	
 
* ``common`` role
 

	
 
  * Fixed handling of complex version specifications when installing packages
 
    via pip. Needed for Ansible 2.x.
 

	
 
* ``wsgi_website`` role
 

	
 
  * Fixed handling of complex version specifications when installing packages
 
    via pip. Needed for Ansible 2.x.
 
  * Fixed erroneous calculation of adminstrator username in internal
 
    defaults parameter.
 

	
 

	
 
1.7.0
 
-----
 

	
 
Minor improvements for mail-related roles, internal refactoring of task syntax,
 
and improvements of tests.
 

	
 
New features/improvements:
 

	
 
* Documentation
 

	
 
  * Added new sub-section in development section describing some of the
 
    conventions used while developing the roles.
 

	
 
* All roles
 

	
 
  * Switched to using expanded syntax in all roles and cleaned-up the tasks a
 
    bit (mainly internal change).
 
  * Minor cleanups within tests and tasks to accomodate the syntax changes
 
    (mainly internal change).
 

	
 
* ``mail_forwarder`` role
 

	
 
  * Added parameter ``smtp_from_relay_allowed`` that controls if managed machine
 
    should accept incoming SMTP connections from the relay server. Useful for
 
    NAT'ed or laptop machines.
 
  * Added parameter ``smtp_relay_host_port`` that controls what port is used for
 
    connecting to the specified SMTP relay. Useful for machines behind
 
    restrictive ISPs.
 

	
 
* ``mail_server`` role
 

	
 
  * Introduced firewall rules to redirect from TCP port 27 to TCP port 25,
 
    useful for machines behind restrictive ISPs.
 

	
 

	
 
1.6.0
 
-----
 

	
 
Implemented full test suite with a plethora of smaller bug-fixes, and some minor
 
(internal) improvements.
 

	
 
Breaking changes:
 

	
 
* All roles
 

	
 
  * Previously a number of roles would modify permissions on the ``/srv``
 
    directory. This has now been fixed in order to prevent weird backup failures
 
    etc. Manual intervention is necessary on existing servers to fix the issue
 
    by changing the mode to ``0755`` (this is the usual default upon the OS
 
    installation).
 

	
 
* ``mail_server`` role
 

	
 
  * Since Postfix will now fall-back to using ``/etc/hosts`` if it cannot
 
    resovle a domain via DNS, some special care may be needed in case you have
 
    some unusual entries in ``/etc/hosts``. Normally this should not be an
 
    issue, though.
 

	
 
* ``backup_client`` role
 

	
 
  * Up to this point, if you had more than one additional encryption key
 
    specified in configuration, only the first one was taken into account. This
 
    is a major issue since it would render backups up to this point decryptable
 
    only with one of the keys. It is highly suggested to perform a full backup
 
    after upgrading to new version of Majic Ansible Roles via command::
 

	
 
      sudo duply main full
 

	
 
    This will ensure the most recent backup is decryptable with all additional
 
    keys!
 

	
 
New features/improvements:
 

	
 
* Added new documentation chapter dedicated to development.
 
* Added ``requirements.txt`` that can be used for installing the required
 
  packages in virtual environment (useful for development).
 
* Small fixes (mostly typos and such) throughout the documentation.
 

	
 
* All roles
 

	
 
  * Implemented tests using Molecule. See documentation for instructions on how
 
    to run tests.
 
  * Small internal refactorings to make things simpler and easier to maintain.
 

	
 
* ``common`` role
 

	
 
  * Added missing documentation for parameters ``pipreqcheck_uid`` and
 
    ``pipreqcheck_gid``.
 

	
 
* ``mail_server`` role
 

	
 
  * Updated Postfix configuration to fall-back to using ``/etc/hosts`` if DNS
 
    lookup fails. This allows for more flexibility when testing and deploying if
 
    proper DNS is not available.
 

	
 
Bug-fixes:
 

	
 
* All roles
 

	
 
  * Fixed how TLS key and certificate material is deployed in order to avoid
 
    mangling of tabs.
 
  * Fixed how file modes are specified in tasks to ensure correct permissions
 
    are applied.
 
  * Fixed missing ``become`` keyword in tasks that use ``become_user`` for
 
    consistent execution.
 

	
 
* ``backup_client`` role
 

	
 
  * Fixed configuration (and documentation) for specifying the backup server
 
    URI - previous implementation included too many forward slashes which could
 
    cause failures in case of custom SSH server being used for backup.
 
  * Fixed configuration of additional encryption keys to include all keys listed
 
    instead of just the first one.
 
  * Fixed issue with ``backup_server_port`` parameter being completely ignored
 
    in the configuration.
 
  * Fixed issue with missing ``/etc/duply/main/include`` configuration file in
 
    case no backup patterns are deployed.
 

	
 
* ``backup_server`` role
 

	
 
  * Fixed deployment of backup server SSH keys in order to avoid unusable
 
    ``ed25519`` keys.
 

	
 
* ``common`` role
 

	
 
  * Fixated version of ``pip`` installed for performing Pyhton requirements
 
    package upgrade checks.
 
  * Fixed incorrect documentation for parameter ``additional_groups``.
 
  * Fixed ownership setting for firewall configuration file.
 
  * Fixed script used for performing checks on pip requirementes files for
 
    availalbe package upgrades. False positives due to different sorting will
 
    not be reported anymore, and the script will actually make sure to check if
 
    upgrades are available (which was not the case before due ot missing
 
    paramter to pip-compile).
 

	
 
* ``ldap_server`` role
 

	
 
  * Fixed invalid configuration of LDAP server package via
 
    ``debconf-set-selections`` (wrong option was used for
 
    ``shared/organization``).
 
  * Fixed role documentation example for parameter ``ldap_entries`` (was using
 
    obsolete syntax of ``ldap_entry`` module).
 
  * When making changes to the LDAP server configuration, make sure to use Unix
 
    socket. This way the role does not depend on correct LDAP client
 
    configuration.
 

	
 
* ``mail_server`` role
 

	
 
  * Fixed Postfix main configuraiton file permissions set-up to be explicit.
 
  * Fixed issue where Postfix server is not restarted when the truststore (used
 
    for verifying the LDAP server certificate) is changed.
 
  * Fixed issue with Postfix configuration where the parameter ``mail_user`` was
 
    ignored when making deliveries to Dovecot (old implementation used fixed
 
    value of ``vmail`` instead of parameter).
 

	
 
* ``php_website`` role
 

	
 
  * Fixed Nginx configuration file to use correct parameter (``enforce_https``
 
    instead of ``default_enforce_https``) when configuring HSTS. Previously it
 
    was possible to set the parameter to ``no``, and still end-up with HSTS
 
    headers being set-up.
 

	
 
* ``wsgi_website`` role
 

	
 
  * Fixed Nginx configuration file to use correct parameter (``enforce_https``
 
    instead of ``default_enforce_https``) when configuring HSTS. Previously it
 
    was possible to set the parameter to ``no``, and still end-up with HSTS
 
    headers being set-up.
 

	
 
* ``xmpp_server`` role
 

	
 
  * Fixed invalid default value for paramerer ``xmpp_domains`` - it should be a
 
    list and not a simple string. Previously this would result in invalid domain
 
    set-up in Prosody configuration file.
 
  * Fixed issue with permissions not being set on Prosody configuration file,
 
    making it world-readable (the configuration file contains passwords).
 

	
 

	
 
1.5.1
 
-----
 

	
 
Small bug-fix release for misbehaving package upgrade checks.
 

	
 
Bug-fixes:
 

	
 
* ``common`` role
 

	
 
  * Fixed script used for performing checks on pip requirementes files for
 
    availalbe package upgrades. False positives due to different sorting will
 
    not be reported anymore, and the script will actually make sure to check if
 
    upgrades are available (which was not the case before due ot missing
 
    paramter to pip-compile).
 

	
 

	
 
1.5.0
 
-----
 

	
 
Minor bug-fixes, package upgrade checks, and better support for next Debian
 
stable release (Stretch).
 

	
 
New features/improvements:
 

	
 
* ``backup_client`` role
 

	
 
  * Implemented support for next Debian stable release (*Debian Stretch*). This
 
    was needed due to changes in duplicity parameters and their syntax.
 

	
 
* ``common`` role
 

	
 
  * Added parameter for configuring common backup patterns. Allows for better
 
    control over ``/root`` and ``/home`` directories. Backup of remaining
 
    directories is still hard-coded.
 
  * Added support for checking if package upgrades are available. Covers system
 
    packages out-of-the-box, and provides ability to perform checks on pip
 
    requirements files.
 
  * Added generic support for checking certificate expiration dates. Relevant
 
    roles need to deploy special configuration files to trigger the checks.
 

	
 
* ``ldap_server`` role
 

	
 
  * Updated role to perform certificate expiration date check on LDAP server
 
    certificate.
 

	
 
* ``mail_server`` role
 

	
 
  * Updated role to perform certificate expiration date check on all mail server
 
    certificates.
 

	
 
* ``php_website`` role
 

	
 
  * Updated role to perform certificate expiration date check on website server
 
    certificate.
 

	
 
* ``xmpp_server`` role
 

	
 
  * Updated role to perform certificate expiration date check on XMPP server
 
    certificate.
 

	
 
* ``web_server`` role
 

	
 
  * Updated role to perform certificate expiration date check on default web
 
    server certificate.
 

	
 
* ``wsgi_website`` role
 

	
 
  * Added alternative way to specify Gunicorn version to install in virtual
 
    environment (via separate parameter). If this parameter is in use, package
 
    upgrade checks will be done as well (against auto-assembled pip requirements
 
    file). See role reference documentation for details.
 
  * Updated role to perform certificate expiration date check on website server
 
    certificate.
 

	
 
Bug-fixes:
 

	
 
* ``mail_server`` role
 

	
 
  * Fixed incorrect mail name (FQDN) used for mails originating from the server.
 

	
 
* ``web_server`` role
 

	
 
  * Fixed configuration of available TLS versions on the Nginx web server.
 

	
 
Documentation:
 

	
 
* Added release procedures and related information.
 
* Added information about Debian release compatibility to role reference.
 

	
 

	
 
1.4.0
 
-----
 

	
 
Minor fixes and features allowing for more fine-tuning of installations.
 

	
 
New features/improvements:
 

	
 
* ``ldap_server`` role
 

	
 
  * TLS versions and ciphers supported by server are now configurable.
 

	
 
* ``mail_server`` role
 

	
 
  * TLS versions and ciphers supported by SMTP and IMAP server are now
 
    configurable.
 
  * Number of allowed concurent IMAP connections for a single user from a single
 
    IP address is now configurable.
 

	
 
* ``web_server`` role
 

	
 
  * TLS versions and ciphers supported by server are now configurable.
 

	
 

	
 
1.3.0
 
-----
 

	
 
IPv6 support in firewall rules, small bug fixes and improvements.
 

	
 
New features/improvements:
 

	
 
* All roles that deploy firewall rules
 

	
 
  * Set-up IPv6 firewall rules in addition to IPv4.
 

	
 
* ``common`` role
 

	
 
  * Crontabs, operating system user passwords (``/etc/shadow``), and local user
 
    mails are now included in the backup.
 

	
 
Bug-fixes:
 

	
 
* ``wsgi_website`` role
 

	
 
  * Do not traverse static locations that have not been explicitly
 
    configured. Fixes issue where static location ends-up being served by Nginx
 
    instea of WSGI application.
 

	
 

	
 
1.2.0
 
-----
 

	
 
Minor fixes and features.
 

	
 
New features:
 

	
 
* ``wsgi_website`` role
 

	
 
  * Added support for providing custom proxy headers to pass on to Gunicorn
 
    server.
 

	
 
Bug-fixes:
 

	
 
* ``php_website`` role
 

	
 
  * Make sure the environment indicator is always shown on top by increasing its
 
    ``z-index`` value.
 

	
 
* ``wsgi_website`` role
 

	
 
  * Make sure the environment indicator is always shown on top by increasing its
 
    ``z-index`` value.
 

	
 

	
 
1.1.0
 
-----
 

	
 
Minor bug fixes, enchancements, and features.
 

	
 
New features/improvements:
 

	
 
* ``common`` role
 

	
 
  * Added support for having user-defined ``/etc/profile.d`` style scripts (in
 
    ``~/.profile.d/``.
 
  * Disables Emacs ``electric-indent-mode`` globally if Emacs is installed.
 
  * Deploys symbolic link for ``mysql_config`` if package
 
    ``libmariadb-client-lgpl-dev-compat`` is installed (workaround for
 
    `Debian Bug 766996
 
    <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=766996>`_)
 
  * Updates CA cache immediatelly so that roles depending on cache being
 
    up-to-date do not throw validation errors.
 

	
 
* ``mail_server`` role
 

	
 
  * Added support for specifying local aliases.
 
  * Undeliverable bounces are now delivered to postmaster.
 

	
 
* ``php_website`` role
 

	
 
  * Added support for specifying custom ``php-fpm`` pool configuration options.
 
  * Added support for having ribon/strip at bottom to identify website
 
    environment. Useful for testing/staging environments.
 
  * Deploys symbolic link for ``mysql_config`` if package
 
    ``libmariadb-client-lgpl-dev-compat`` is installed (workaround for
 
    `Debian Bug 766996
 
    <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=766996>`_)
 
  * Forwards mails delivered to application or application administrator users
 
    to local ``root`` account (can be configured to deliver mails elsewhere).
 
  * Sets ``HSTS`` policy if TLS is enforced.
 
  * *Umask* for the operating system which runs the website is set to ``0007``.
 
  * When administrator user is created for the first time, its home directory is
 
    populated from ``/etc/skel``. This makes prompts etc look more uniform
 
    across the system.
 

	
 
* ``wsgi_website`` role
 

	
 
  * Added support for having ribon/strip at bottom to identify website
 
    environment. Useful for testing/staging environments.
 
  * Added support for specifying environment variables that should be set when
 
    running the service, or when administering the installation (using
 
    application administrator operating system user).
 
  * Deploys symbolic link for ``mysql_config`` if package
 
    ``libmariadb-client-lgpl-dev-compat`` is installed (workaround for
 
    `Debian Bug 766996
 
    <https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=766996>`_)
 
  * Forwards mails delivered to application or application administrator users
 
    to local ``root`` account (can be configured to deliver mails elsewhere).
 
  * Sets ``HSTS`` policy if TLS is enforced.
 
  * *Umask* for the operating system which runs the website is set to ``0007``.
 
  * When administrator user is created for the first time, its home directory is
 
    populated from ``/etc/skel``. This makes prompts etc look more uniform
 
    across the system.
 

	
 
Bug-fixes:
 

	
 
* ``database_server`` role
 

	
 
  * Applies UTF-8 configuration immediatelly. This should fix issues during
 
    inital server set-up for roles that need to create database using UTF-8
 
    character set.
 

	
 
* ``wsgi_website`` role
 

	
 
  * Fixed virtualenv wrapper shell script to use proper escaping around
 
    arguments.
 
  * Website service is now restarted in case of package changes (system or
 
    virtual environment).
 

	
 
* ``mail_forwarder`` role
 

	
 
  * Allows incoming SMTP connections from the SMTP relay server (if
 
    configured). This way the SMTP relay can deliver bounces.
 

	
 

	
 
1.0.1
 
-----
 

	
 
Minimal bugfix update to improve interoperability.
 

	
 
Changes:
 

	
 
* ``xmpp_server`` role no longer restricts TLS to version 1.2 and ciphers to PFS
 
  ciphers. Should solve ``s2s`` communication issues with old XMPP servers.
 

	
 

	
 
1.0.0
 
-----
 

	
 
Initial release of Majic Ansible Roles.
 

	
 
New roles:
 

	
 
* ``backup``, reusable role for specifying files to back-up.
 
* ``backup_client``, base role for setting-up backup client on a server
 
  (Duplicity).
 
* ``backup_server``, sets-up a backup server.
 
* ``bootstrap``, sets-up server for Ansible management (bootstrapping it for
 
  subsequent Ansible runs).
 
* ``common``, basic set-up of server, some hardening, creation of admin accounts
 
  etc.
 
* ``database``, reusable role for creating MariaDB database and user for
 
  accessing the database.
 
* ``database_server``, sets-up database server (MariaDB).
 
* ``ldap_client``, sets-up LDAP client tools and configuration (OpenLDAP).
 
* ``ldap_server``, sets-up and manages basic entries in an LDAP server
 
  (OpenLDAP).
 
* ``mail_forwarder``, sets-up local SMTP server that forwards mail to the main
 
  mail server (Postfix).
 
* ``mail_server``, sets-up a mail server with SMTP and IMAP services (Postfix,
 
  Dovecot).
 
* ``php_website``, reusable role for creating PHP-based websites. Provides basic
 
  building block for PHP applications (Nginx).
 
* ``preseed``, small role for generating Debian preseed files for automated OS
 
  installation.
docs/rolereference.rst
Show inline comments
 
@@ -609,1554 +609,1559 @@ For adding users, use::
 
  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, mandatory)
 
  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). E.g. ``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``.
 

	
 
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, mandatory)
 
  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 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)
 

	
 

	
 
Examples
 
~~~~~~~~
 

	
 
Here is an example configuration for setting-up the mail forwarder:
 

	
 
.. code-block:: yaml
 

	
 
  ---
 

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

	
 
  smtp_relay_host: mail.example.com
 

	
 
  smtp_relay_host_port: 27
 

	
 
  smtp_from_relay_allowed: False
 

	
 
  smtp_relay_truststore: /etc/ssl/certs/example_ca_chain.pem
 

	
 

	
 
Web Server
 
----------
 

	
 
The ``web_server`` role can be used for setting-up a web server on destination
 
machine.
 

	
 
The role is supposed to be very lightweight, providing a basis for deployment of
 
web applications.
 

	
 
The role implements the following:
 

	
 
* Installs and configures nginx with a single, default vhost with a small static
 
  index page.
 
* Deploys the HTTPS TLS private key and certificate (for default vhost).
 
* Configures TLS versions and ciphers supported by Nginx.
 
* Uses 2048-bit Diffie-Hellman parameters for relevant TLS ciphers for
 
  incoming connections.
 
* Configures firewall to allow incoming connections to the web server.
 
* Installs and configures virtualenv and virtualenvwrapper as a common base for
 
  Python apps.
 
* Installs and configures PHP FPM as a common base for PHP apps.
 

	
 
The web server is configured as follows:
 

	
 
* No plaintext HTTP is allowed, HTTPS is mandatory. Clients connecting
 
  via plaintext HTTP are redirected to HTTPS.
 
* Clients are served with ``Strict-Transport-Security`` header with
 
  value of ``max-age=31536000; includeSubDomains``. This forces
 
  compliant clients to always connect using HTTPS to the web server
 
  when accessing its default domain, as well as any subdomains served
 
  by this web server or any other. The (client-side) cached header
 
  value expires after one year.
 

	
 

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

	
 
Depends on the following roles:
 

	
 
* **common**
 

	
 

	
 
Parameters
 
~~~~~~~~~~
 

	
 
**default_enforce_https** (boolean, optional, ``True``)
 
  Specify if HTTPS should be enforced for the default virtual host or not. If
 
  enforced, clients connecting via plaintext will be redirected to HTTPS, and
 
  clients will be served with ``Strict-Transport-Security`` header with value of
 
  ``max-age=31536000; includeSubDomains``.
 

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

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

	
 
**web_default_title** (string, optional, ``Welcome``)
 
  Title for the default web page shown to users (if no other vhosts were matched).
 

	
 
**web_default_message** (string, optional, ``You are attempting to access the web server using a wrong name or an IP address. Please check your URL.``)
 
  Message for the default web page shown to users (if no other vhosts were
 
  matched).
 

	
 
**web_server_tls_protocols** (list, optional, ``[ "TLSv1.2" ]``)
 
  List of TLS protocols the web server should support. Each value specified
 
  should be compatible with Nginx configuration option ``ssl_protocols``.
 

	
 
**web_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 web server. This should be an OpenSSL-compatible
 
  cipher specification. Value should be compatible with Nginx configuration
 
  option ``ssl_ciphers``. Default value allows only TLSv1.2 and strong PFS
 
  ciphers with RSA private keys.
 

	
 

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

	
 
Role is compatible with the following distributions:
 

	
 
- Debian 9 (Stretch)
 

	
 

	
 
Examples
 
~~~~~~~~
 

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

	
 
.. code-block:: yaml
 

	
 
  ---
 

	
 
  default_https_tls_key: "{{ lookup('file', inventory_dir + '/tls/web.example.com_https.key') }}"
 
  default_https_tls_certificate: "{{ lookup('file', inventory_dir + '/tls/web.example.com_https.pem') }}"
 

	
 
  web_default_title: "Welcome to Example Inc."
 
  web_default_message: "You are attempting to access the web server using a wrong name or an IP address. Please check your URL."
 

	
 

	
 
PHP Website
 
-----------
 

	
 
The ``php_website`` role can be used for setting-up a website powered by PHP on
 
destination machine.
 

	
 
This role is normally not supposed to be used directly, but should instead serve
 
as the basis for writing website-specific roles. Therefore the role is written
 
in quite generic way, allowing the integrator to write his/her own logic for
 
deploying the necessary PHP applications, while still reusing a common base and
 
reducing the workload.
 

	
 
The role implements the following:
 

	
 
* Creates a dedicated user/group for running the PHP scripts.
 
* Creates a dedicated administrator user for maintaining the website.
 
* Creates a base directory where the website-specific code and data should be
 
  stored at.
 
* Adds nginx to website's group, so nginx could read the necessary files.
 
* Adds website administrator to website's group, so administrator could manage
 
  the code and data.
 
* Installs additional packages required for running the role (as configured).
 
* Deploys the HTTPS TLS private key and certificate (for website vhost).
 
* Configures PHP FPM and nginx to serve the website.
 

	
 
The role is implemented with the following layout/logic in mind:
 

	
 
* Website users are named after the ``FQDN`` (fully qualified domain name) of
 
  website, in format of ``web-ESCAPEDFQDN``, where ``ESCAPEDFQDN`` is equal to
 
  ``FQDN`` where dots have been replaced by underscores (for example,
 
  ``web-cloud_example_com``).
 
* Website users are set-up via GECOS field to have their umask set to ``0007``
 
  (in combination with ``pam_umask``).
 
* Administrator users are named after the ``FQDN`` (fully qualified domain name)
 
  of website, in format of ``admin-ESCAPEDFQDN``, where ``ESCAPEDFQDN`` is equal
 
  to ``FQDN`` where dots have been replaced by underscores (for example,
 
  ``admin-cloud_example_com``).
 
* All websites reside within a dedicated sub-directory in ``/var/www``. The
 
  sub-directory name is equal to the ``FQDN`` used for accessing the
 
  website. Owner of the directory is set to be the application administrator,
 
  while group is set to be the website group. Additionally, ``SGID`` bit is set
 
  on the directory. This allows admin, with correct umask, to create necessary
 
  files and directories that should be readable (and eventually writeable) by
 
  the website user (running the PHP scripts) without having to become root.
 
* All files placed in the website directory should be either created there
 
  directly, or copied to the directory in order to make sure the ``SGID`` gets
 
  honored. **Do not move the files, the permissions will not be set correctly.**
 
* Within the website directory, nginx/php5-fpm will expect to find the relevant
 
  files within the htdocs sub-directory (this can be symlink too).
 
* nginx communicates with PHP FPM over a dedicated Unix socket for each website.
 

	
 

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

	
 
Depends on the following roles:
 

	
 
* **common**
 
* **web_server**
 

	
 

	
 
Parameters
 
~~~~~~~~~~
 

	
 
**additional_fpm_config** (dict, optional, ``{}``)
 
  Additional PHP FPM configuration options that should be included for PHP
 
  website's pool. Keys are parameter names, values are associated values. Don't
 
  forget to include quotes in the value itself if expected value type is string.
 

	
 
**additional_nginx_config** (list, optional, ``[]``)
 
  List providing additional Nginx configuration options to include. This can be
 
  useful for specifying things like error pages. Options are applied inside of a
 
  **server** context of Nginx configuration file.
 

	
 
  Each item is a dictionary with the following options describing the extra
 
  configuration option:
 

	
 
  **comment** (string, mandatory)
 
    Comment describing the configuration option.
 

	
 
  **value** (string, mandatory)
 
    Configuration option.
 

	
 
**admin_uid** (integer, optional, ``whatever OS picks``)
 
  UID of the dedicated website administrator user. The user will be member of
 
  website group.
 

	
 
**deny_files_regex** (list, optional, ``[]``)
 
  List of regular expressions for matching files/locations to which the web
 
  server should deny access. This is useful to block access to any sensitive
 
  files that should not be served directly by the web server. The format must be
 
  compatible with regular expressions used by ``nginx`` for ``location ~``
 
  syntax.
 

	
 
**enforce_https** (boolean, optional, ``True``)
 
  Specify if HTTPS should be enforced for the website or not. If enforced,
 
  clients connecting via plaintext will be redirected to HTTPS, and clients will
 
  be served with ``Strict-Transport-Security`` header with value of
 
  ``max-age=31536000; includeSubDomains``.
 

	
 
**environment_indicator** (dictionary, optional, ``null``)
 
  Specify configuration for including environment indicator on all HTML
 
  pages. Indicator is a simple strip at bottom of a page with custom background
 
  colour, text colour, and text.
 

	
 
  Specifying environment indicator is useful for avoiding mistakes when testing
 
  by having better visibility what environment you are in
 
  (production/staging/test).
 

	
 
  The following keys need to be specified:
 

	
 
  **background_colour** (string, mandatory)
 
    Background colour to use for the strip at bottom. This should be value
 
    compatible with CSS ``background-color`` attribute.
 

	
 
  **text_colour** (string, mandatory
 
    Text colour to use for the strip at bottom. This should be value compatible
 
    with CSS ``color`` attribute.
 

	
 
  **text** (string, mandatory)
 
    Text to show in show in the strip at bottom.
 

	
 
**fqdn** (string, mandatory)
 
  Fully-qualified domain name where the website is reachable. This value is used
 
  for calculating the user/group name for dedicated website user, as well as
 
  home directory of the website user (where data/code should be stored at).
 

	
 
**index** (string, optional, ``index.php``)
 
  Space-separated list of files which should be treated as index files by the
 
  web server. The web server will attempt opening these index files, in
 
  succession, until the first match, or until it runs out of matches, when a
 
  client requests an URI pointing to directory.
 

	
 
**https_tls_certificate** (string, mandatory)
 
  X.509 certificate used for TLS for HTTPS service. The file will be stored in
 
  directory ``/etc/ssl/certs/`` under name ``{{ fqdn }}_https.pem``.
 

	
 
**https_tls_key** (string, optional, mandatory)
 
  Private key used for TLS for HTTPS service. The file will be stored in
 
  directory ``/etc/ssl/private/`` under name ``{{ fqdn }}_https.key``.
 

	
 
**php_file_regex** (string, optional, ``\.php$``)
 
  Regular expression used for determining which file should be interepted via
 
  PHP.
 

	
 
**php_rewrite_urls** (list, optional, ``[]``)
 
  A list of rewrite rules that are applied to incoming requests. These rewrite
 
  rules are specifically targetted at prettying-up the URLs used by the PHP
 
  scripts. Each element of the list should be a string value compatible with the
 
  format of ``nginx`` option ``rewrite``. The keyword ``rewrite`` itself should
 
  be omitted, as well as trailing semi-colon (``;``).
 

	
 
**rewrites** (list, optional, ``[]``)
 
  A list of rewrite rules that are applied to incoming requests. Each element of
 
  the list should be a string value compatible with the format of ``nginx``
 
  option ``rewrite``. The keyword ``rewrite`` itself should be omitted, as well
 
  as trailing semi-colon (``;``).
 

	
 
**packages** (list, optional, ``[]``)
 
  A list of additional packages to install for this particular PHP
 
  appliction. This is usually going to be different PHP extensions.
 

	
 
**uid** (integer, optional, ``whatever OS picks``)
 
  UID/GID (they are set-up to be the same) of the dedicated website
 
  user/group.
 

	
 
**website_mail_recipients** (string, optional, ``root``)
 
  Space-separated list of e-mails or local users to which the mails, sent to
 
  either the website admin or website user, should be forwarded to. Forwarding
 
  is configured via ``~/.forward`` configuration file.
 

	
 

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

	
 
Role is compatible with the following distributions:
 

	
 
- Debian 9 (Stretch)
 

	
 

	
 
Examples
 
~~~~~~~~
 

	
 
Here is an example configuration for setting-up two (base) PHP websites (for
 
running *ownCloud* and *The Bug Genie* applications):
 

	
 
.. code-block:: yaml
 

	
 
    - role: php_website
 
      fqdn: cloud.example.com
 
      uid: 2001
 
      php_file_regex: \.php($|/)
 
      rewrites:
 
        - ^/\.well-known/host-meta /public.php?service=host-meta
 
        - ^/\.well-known/host-meta\.json /public.php?service=host-meta-json
 
        - ^/\.well-known/carddav /remote.php/carddav/ redirect
 
        - ^/\.well-known/caldav /remote.php/caldav/ redirect
 
        - ^/apps/calendar/caldav\.php /remote.php/caldav/
 
        - ^/apps/contacts/carddav\.php /remote.php/carddav/
 
        - ^/remote/(.*) /remote.php
 
      deny_files_regex:
 
        - ^(\.|autotest|occ|issue|indie|db_|console|build/|tests/|config/|lib/|3rdparty/|templates/).*
 
      packages:
 
        # For ownCloud
 
        - php5-gd
 
        - php5-json
 
        - php5-mysql
 
        - php5-curl
 
      https_tls_key: "{{ lookup('file', inventory_dir + '/tls/cloud.example.com_https.key') }}"
 
      https_tls_certificate: "{{ lookup('file', inventory_dir + '/tls/cloud.example.com_https.pem') }}"
 
      additional_nginx_config:
 
        - comment: Use custom page for forbidden files.
 
          value: error_page 403 /core/templates/403.php;
 
        - comment: Use custom page for non-existing locations/files.
 
          value: error_page 404 /core/templates/404.php;
 
      additional_fpm_config:
 
        "env[PATH]": "\"/usr/local/bin:/usr/bin:/bin\""
 
      website_mail_recipients: "root john.doe@example.com"
 
      environment_indicator:
 
        background_colour: "green"
 
        text_colour: "black"
 
        text: "TEST ENVIRONMENT"
 
    - role: php_website
 
      deny_files_regex:
 
        - ^\..*
 
      php_rewrite_urls:
 
        - ^(.*) /index.php?url=$1
 
      fqdn: tbg.example.com
 
      uid: 2007
 
      https_tls_key: "{{ lookup('file', inventory_dir + '/tls/tbg.example.com_https.key') }}"
 
      https_tls_certificate: "{{ lookup('file', inventory_dir + '/tls/tbg.example.com_https.pem') }}"
 

	
 

	
 
WSGI Website
 
------------
 

	
 
The ``wsgi_website`` role can be used for setting-up a website powered by Python
 
on destination machine. The website needs to use the WSGI specification for
 
making the Python web application(s) available.
 

	
 
This role is normally not supposed to be used directly, but should instead serve
 
as the basis for writing website-specific roles. Therefore the role is written
 
in quite generic way, allowing the integrator to write his/her own logic for
 
deploying the necessary Python applications/packages, while still reusing a
 
common base and reducing the workload.
 

	
 
The role implements the following:
 

	
 
* Creates a dedicated user/group for running the WSGI application.
 
* Creates a dedicated administrator user for maintaining the website.
 
* Creates a base directory where the website-specific code and data should be
 
  stored at.
 
* Adds nginx to website's group, so nginx could read the necessary files.
 
* Adds website administrator to website's group, so administrator could manage
 
  the code and data.
 
* Installs additional packages required for running the role (as configured).
 
* Sets-up a dedicated Python virtual environment for website. Python
 
  version can be specified (default is Python 2).
 
* Install ``futures`` package in Python virtual environment (required for
 
  Gunicorn in combination with Python 2.7).
 
* Install Gunicorn in Python virtual environment.
 
* Installs additional packages required for running the role in Python virtual
 
  environment (as configured).
 
* Configures systemd to run the website code (using Gunicorn)
 
* Deploys the HTTPS TLS private key and certificate (for website vhost).
 
* Configures nginx to serve the website (static files served directly, requests
 
  passed on to Gunicorn).
 

	
 
The role is implemented with the following layout/logic in mind:
 

	
 
* Website users are named after the ``FQDN`` (fully qualified domain name) of
 
  website, in format of ``web-ESCAPEDFQDN``, where ``ESCAPEDFQDN`` is equal to
 
  ``FQDN`` where dots have been replaced by underscores (for example,
 
  ``web-wiki_example_com``).
 
* Website users are set-up via GECOS field to have their umask set to ``0007``
 
  (in combination with ``pam_umask``).
 
* Administrator users are named after the ``FQDN`` (fully qualified domain name)
 
  of website, in format of ``admin-ESCAPEDFQDN``, where ``ESCAPEDFQDN`` is equal
 
  to ``FQDN`` where dots have been replaced by underscores (for example,
 
  ``admin-cloud_example_com``).
 
* All websites reside within a dedicated sub-directory in ``/var/www``. The
 
  sub-directory name is equal to the ``FQDN`` used for accessing the
 
  website. Owner of the directory is set to be the application administrator,
 
  while group is set to be the website group. Additionally, ``SGID`` bit is set
 
  on the directory. This allows admin, with correct umask, to create necessary
 
  files and directories that should be readable (and eventually writeable) by
 
  the website user (running the WSGI application) without having to become root.
 
* All files placed in the website directory should be either created there
 
  directly, or copied to the directory in order to make sure the ``SGID`` gets
 
  honored. **Do not move the files, the permissions will not be set correctly.**
 
* Within the website directory, Python virtual environment can be found within
 
  the ``virtualenv`` sub-directory. Switching to administrator user via login
 
  shell will automatically activate the virtual environment.
 
* Within the website directory, nginx will expect to find the static files
 
  within the ``htdocs`` sub-directory (this can be symlink too). Locations/aliases
 
  can be configured for static file serving.
 
* Within the website directory, systemd service will expect to find the website
 
  code within the ``code`` sub-directory (this can be symlink too).
 
* nginx communicates with WSGI server over a dedicated Unix socket for each
 
  website.
 

	
 

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

	
 
Depends on the following roles:
 

	
 
* **common**
 
* **web_server**
 

	
 

	
 
Parameters
 
~~~~~~~~~~
 

	
 
**additional_nginx_config** (list, optional, ``[]``)
 
  List providing additional Nginx configuration options to include. This can be
 
  useful for specifying things like error pages. Options are applied inside of a
 
  **server** context of Nginx configuration file.
 

	
 
  Each item is a dictionary with the following options describing the extra
 
  configuration option:
 

	
 
  **comment** (string, mandatory)
 
    Comment describing the configuration option.
 

	
 
  **value** (string, mandatory)
 
    Configuration option.
 

	
 
**admin_uid** (integer, optional, ``whatever OS picks``)
 
  UID of the dedicated website administrator user. The user will be member of
 
  website group.
 

	
 
**enforce_https** (boolean, optional, ``True``)
 
  Specify if HTTPS should be enforced for the website or not. If enforced,
 
  clients connecting via plaintext will be redirected to HTTPS, and clients will
 
  be served with ``Strict-Transport-Security`` header with value of
 
  ``max-age=31536000; includeSubDomains``.
 

	
 
**environment_indicator** (dictionary, optional, ``null``)
 
  Specify configuration for including environment indicator on all HTML
 
  pages. Indicator is a simple strip at bottom of a page with custom background
 
  colour, text colour, and text.
 

	
 
  Specifying environment indicator is useful for avoiding mistakes when testing
 
  by having better visibility what environment you are in
 
  (production/staging/test).
 

	
 
  The following keys need to be specified:
 

	
 
  **background_colour** (string, mandatory)
 
    Background colour to use for the strip at bottom. This should be value
 
    compatible with CSS ``background-color`` attribute.
 

	
 
  **text_colour** (string, mandatory
 
    Text colour to use for the strip at bottom. This should be value compatible
 
    with CSS ``color`` attribute.
 

	
 
  **text** (string, mandatory)
 
    Text to show in show in the strip at bottom.
 

	
 
**environment_variables** (dict, optional, ``{}``)
 
  Specify additional environment variables that should be set for running the
 
  service. Environment variables will be set in both the systemd service and for
 
  the application's administrator user (when logged in as one).
 

	
 
**fqdn** (string, mandatory)
 
  Fully-qualified domain name where the website is reachable. This value is used
 
  for calculating the user/group name for dedicated website user, as well as
 
  home directory of the website user (where data/code should be stored at).
 

	
 
**https_tls_certificate** (string, mandatory)
 
  X.509 certificate used for TLS for HTTPS service. The file will be stored in
 
  directory ``/etc/ssl/certs/`` under name ``{{ fqdn }}_https.pem``.
 

	
 
**https_tls_key** (string, mandatory)
 
  Private key used for TLS for HTTPS service. The file will be stored in
 
  directory ``/etc/ssl/private/`` under name ``{{ fqdn }}_https.key``.
 

	
 
**packages** (list, optional, ``[]``)
 
  A list of additional packages to install for this particular WSGI
 
  website. This is usually going to be development libraries for building Python
 
  packages.
 

	
 
**proxy_headers** (dictionary, optional, ``{}``)
 
  Additional headers to set when proxying request to Gunicorn. Keys are header
 
  names, values are header values. Both should be compatible with Nginx
 
  ``proxy_set_header``. If you need to provide an empty value, use quotes (don't
 
  forget to surround them by another set of quotes for YAML syntax, for example
 
  ``"\"\""`` or ``'""'``).
 

	
 
**python_version** (string, optional, ``2``)
 
  Python version to use when setting-up the Python virtual environment.
 

	
 
**rewrites** (list, optional, ``[]``)
 
  A list of rewrite rules that are applied to incoming requests. Each element of
 
  the list should be a string value compatible with the format of ``nginx``
 
  option ``rewrite``. The keyword ``rewrite`` itself should be omitted, as well
 
  as trailing semi-colon (``;``).
 

	
 
**static_locations** (list, optional, ``[]``)
 
  List of locations that should be treated as static-only, and not processed by
 
  the WSGI application at all. This is normally used for designating serving of
 
  static/media files by Nginx (for example, in case of Django projects for
 
  ``/static/`` and ``/media/``).
 

	
 
**uid** (integer, optional, ``whatever OS picks``)
 
  UID/GID (they are set-up to be the same) of the dedicated website
 
  user/group.
 

	
 
**use_paste** (boolean, optional, ``False``)
 
  Tell Gunicorn to assume that the passed-in ``wsgi_application`` value is a
 
  filename of a Python Paste ``ini`` file instead of WSGI application.
 

	
 
**virtuaelnv_packages** (list, optional, ``[]``)
 
  A list of additional packages to install for this particular WSGI appliction
 
  in its virtual environment using ``pip``.
 

	
 
**website_mail_recipients** (string, optional, ``root``)
 
  Space-separated list of e-mails or local users to which the mails, sent to
 
  either the website admin or website user, should be forwarded to. Forwarding
 
  is configured via ``~/.forward`` configuration file.
 

	
 
**wsgi_application** (string, mandatory)
 
  WSGI application that should be started by Gunicorn. The format should be
 
  conformant to what the ``gunicorn`` command-line tool accepts. If the
 
  ``use_paste`` option is enabled, the value should be equal to filename of the
 
  Python Paste ini file, located in the ``code`` sub-directory. It should be
 
  noted that in either case the value should be specsified relative to the
 
  ``code`` sub-directory. I.e. don't use full paths.
 

	
 
**wsgi_requirements** (list, optional, ``[ futures==3.3.0, gunicorn==19.10.0 ]``)
 
  Complete list of pip requirements used for deploying Gunicorn. If
 
  specified, this list will be used to create requirements file and
 
  install Gunicorn and its dependencies from that one. This allows to
 
  have pinned packages for both Gunicorn, futures, and their
 
  dependencies. The ``futures`` package is required by Gunicorn when
 
  using Python 2.7.
 

	
 
  It should be noted that this installation method is meant primarily in case of
 
  roles that want to take advantage of upgrade checks for pip requirements
 
  files, and that employ `pip-tools <https://github.com/jazzband/pip-tools>`_.
 

	
 
  In addition to change of installation method, when this parameter is specified
 
  the role will deploy necessary files for running the pip requirements upgrade
 
  check (see the ``common`` role for description). For this a directory is
 
  created under ``/etc/pip_check_requirements_upgrades/FQDN``. The same
 
  directory should be used by dependant roles to deploy their own ``.in`` and
 
  ``.txt`` files. Make sure the file ownership is set to ``root:pipreqcheck``.
 

	
 
  Should you need to utilise the requirements file in some manner (other than
 
  checking for its upgrades), it will be also stored (and made accessible to
 
  application user/admin)) in application's home directory under the name
 
  ``.wsgi_requirements.txt``.
 

	
 
  To create complete requirements list, it is recommended to use `pip-tools
 
  <https://github.com/jazzband/pip-tools>`_ (the ``pip-compile`` utility) with
 
  ``gunicorn`` and ``futures`` in the ``.in.`` file.
 

	
 
**wsgi_requirements_in** (list, optional, ``[ futures, gunicorn ]``)
 
  List of top level packages to use when performing the pip
 
  requirements upgrade checks for the Gunicorn requirements (listed
 
  via ``wsgi_requirements`` parameter). For Python 3-based websites,
 
  it should be sufficient to list only ``gunicorn`` (``futures`` is
 
  required for Python 2).
 

	
 

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

	
 
Role is compatible with the following distributions:
 

	
 
- Debian 9 (Stretch)
 

	
 

	
 
Examples
 
~~~~~~~~
 

	
 
Here is an example configuration for setting-up a (base) WSGI website (for
 
running a bare Django project):
 

	
 
.. code-block:: yaml
 

	
 
    # Sample for a Django installation.
 
    - role: wsgi_website
 
      fqdn: django.example.com
 
      static_locations:
 
        - /static
 
        - /media
 
      uid: 2004
 
      virtualenv_packages:
 
        - django
 
      wsgi_application: django_example_com.wsgi:application
 
      environment_variables:
 
        DJANGO_SETTINGS_MODULE: "django_example_com.settings.production"
 
      https_tls_key: "{{ lookup('file', inventory_dir + '/tls/wsgi.example.com_https.key') }}"
 
      https_tls_certificate: "{{ lookup('file', inventory_dir + '/tls/wsgi.example.com_https.pem') }}"
 
      additional_nginx_config:
 
        - comment: Use custom page for forbidden files.
 
          value: error_page 403 /static/403.html;
 
        - comment: Use custom page for non-existing locations/files.
 
          value: error_page 404 /static/404.html;
 
      website_mail_recipients: "root john.doe@example.com"
 
      environment_indicator:
 
        background_colour: "green"
 
        text_colour: "black"
 
        text: "TEST ENVIRONMENT"
 
      proxy_headers:
 
        Accept-Encoding: '""'
 

	
 
    # Use wsgi_requirements to deploy Gunicorn.
 
    - role: wsgi_website
 
      fqdn: wsgi.example.com
 
      wsgi_application: wsgi:main
 
      wsgi_requirements:
 
        - gunicorn==19.7.1
 
	- futures==3.1.1
 

	
 

	
 
Database Server
 
---------------
 

	
 
The ``database_server`` role can be used for setting-up a MariaDB database
 
server on destination machine.
 

	
 
The role implements the following:
 

	
 
* Installs MariaDB server and client.
 
* Configures MariaDB server and client to use *UTF-8* encoding by default.
 
* Sets password for the database root user.
 

	
 
.. note::
 
   On Debian Stretch it is possible to login into the database through
 
   Unix socket as ``root`` operating system account as well - relying
 
   on Unix socket user authentication without providing password. This
 
   is the default set-up of MariaDB server on Debian Stretch
 

	
 
* Deploys MariaDB client configuration in location ``/root/.my.cnf`` that
 
  contains username and password for the root database user.
 

	
 

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

	
 
Depends on the following roles:
 

	
 
* **common**
 

	
 

	
 
Parameters
 
~~~~~~~~~~
 

	
 
**db_root_password** (string, mandatory)
 
  Password for the *root* database user.
 

	
 

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

	
 
Role is compatible with the following distributions:
 

	
 
- Debian 9 (Stretch)
 

	
 

	
 
Examples
 
~~~~~~~~
 

	
 
Here is an example configuration for setting-up the database server:
 

	
 
.. code-block:: yaml
 

	
 
   ---
 

	
 
   db_root_password: root
 

	
 

	
 
Database
 
--------
 

	
 
The ``database`` role can be used for creating a MariaDB database and
 
accompanying user on destination machine.
 

	
 
The role implements the following:
 

	
 
* Creates MariaDB database.
 
* Creates a dedicated user capable of performing any operation on the created
 
  database. Username is set to be same as the name of database.
 
* Sets-up pre-backup task that creates database dump in location
 
  ``/srv/backup/mariadb/{{ db_name }}.sql``.
 

	
 

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

	
 
Depends on the following roles:
 

	
 
* **database_server**
 
* **backup_client**
 

	
 

	
 
Backups
 
~~~~~~~
 

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

	
 
**/srv/backup/maraidb/{{ db_name }}.sql**
 
  Dump of the database. Database dump is created every day at 01:45 in the
 
  morning.
 

	
 

	
 
Parameters
 
~~~~~~~~~~
 

	
 
**db_name** (string, mandatory)
 
  Name of the database that should be created.
 

	
 
**db_password** (string, mandatory)
 
  Password for the database user.
 

	
 

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

	
 
Role is compatible with the following distributions:
 

	
 
- Debian 9 (Stretch)
 

	
 

	
 
Examples
 
~~~~~~~~
 

	
 
Here is an example configuration for creating a single database (for some
 
website):
 

	
 
.. code-block:: yaml
 

	
 
  - role: database
 
    db_name: phpinfo_example_com
 
    db_password: phpinfo_example_com
 

	
 

	
 
Backup Server
 
-------------
 

	
 
The ``backup_server`` role can be used for setting-up a server to act as backup
 
storage for the backup clients. Storage is made available to the clients
 
exclusively via SFTP on a dedicated port and dedicated OpenSSH server
 
instance. This instance is specifically configured and tailored for this
 
purpose.
 

	
 
The role is primarily aimed for use with `Duplicity
 
<http://duplicity.nongnu.org/>`_, but should be also usable for generic SFTP
 
uploads.
 

	
 
The role implements the following:
 

	
 
* Installs backup software (Duplicity, Duply).
 
* Creates a dedicated directory structure for backups with the following structure:
 

	
 
  * ``/srv/backups/`` - main directory under which all the backups reside.
 
  * ``/srv/backups/SERVER_NAME/`` - home directory for the backup user, name
 
    after the server. Backup users are confined to their respective home
 
    directory via chroot. Backup users can't write to their own home directory,
 
    though.
 
  * ``/srv/backups/SERVER_NAME/duplicity/`` - directory where the Duplicity
 
    backups are stored at. This directory is writable by the respective backup
 
    user.
 
  * ``SERVER_NAME/.ssh/`` - directory where authorized keys are stored. Backup
 
    user is not allowed to make modifications to this directory and files
 
    contained within (i.e. backup users can't add more keys to the
 
    ``authorized_keys`` file).
 
* Creates dedicated operating system users for backup clients. These users will
 
  be made members of the ``backup`` group as well (as an additional group).
 
* Sets-up ``authorized_keys`` for the backup clients.
 
* Makes sure the backup users can't log-in via regular OpenSSH server instance.
 
* Sets-up dedicated OpenSSH server instances to be used exclusively by backup
 
  clients. The instance listens on TCP port ``2222``.
 
* Updates firewall to allow incoming TCP connections to port
 
  ``2222``. Connections are allowed only from the configured IP addresses
 
  associated with backup clients.
 

	
 

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

	
 
Depends on the following roles:
 

	
 
* **common**
 

	
 

	
 
Parameters
 
~~~~~~~~~~
 

	
 
**backup_clients** (list, optional)
 
  List of backup clients that are connecting to the backup server. This is
 
  usually done on a per-server basis. Each item in the list is a dictionary
 
  describing the backup client. The following keys are available:
 

	
 
  **server** (string, mandatory)
 
    Name of the server that is backed up. It is highly recommended to use
 
    server's FQDN for this purpose. The dedicated operating system user created
 
    will have the name of format ``bak-ESCAPED_SERVER_NAME``, where
 
    ``ESCAPED_SERVER_NAME`` is calculated by taking the passed-in server name
 
    and replacing all dots (``.``) with undescores (``_``). For example,
 
    ``web.example.com`` will be turned into ``bak-web_example_com``.
 

	
 
  **uid** (integer, optional, ``whatever OS picks``)
 
    Uid for the operating system user. User's default group will have a GID
 
    identical to the user's UID if specified. Otherwise user's default group
 
    will have OS-determined GID.
 

	
 
  **ip** (IPv4 address, mandatory)
 
    IPv4 address from which the backup client server is connecting to the backup
 
    server. Used for introducing stricter firewall rules.
 

	
 
  **public_key** (string, mandatory)
 
    SSH public key used by backup client to connect to the backup server.
 

	
 
**backup_host_ssh_private_keys** (dictionary, mandatory)
roles/web_server/defaults/main.yml
Show inline comments
 
---
 

	
 
default_enforce_https: true
 
web_default_title: "Welcome"
 
web_default_message: "You are attempting to access the web server using a wrong name or an IP address. Please check your URL."
 
web_server_tls_protocols:
 
  - "TLSv1.2"
 
web_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"
 

	
 
# Internal parameters
 
php_fpm_package_name: "php-fpm"
 
php_fpm_service_name: "php7.0-fpm"
 
php_base_config_dir: "/etc/php/7.0"
roles/web_server/molecule/default/group_vars/parameters-optional.yml
Show inline comments
 
---
 

	
 
default_enforce_https: false
 
default_https_tls_certificate: "{{ lookup('file', 'tests/data/x509/server/{{ inventory_hostname }}_https.cert.pem') }}"
 
default_https_tls_key: "{{ lookup('file', 'tests/data/x509/server/{{ inventory_hostname }}_https.key.pem') }}"
 
web_default_title: "Optional Welcome"
 
web_default_message: "Welcome to parameters-optional, default virtual host."
 
web_server_tls_protocols:
 
  - TLSv1.1
 
  - TLSv1.2
 
web_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') }}"
roles/web_server/molecule/default/tests/test_default.py
Show inline comments
 
import os
 

	
 
import testinfra.utils.ansible_runner
 

	
 

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

	
 

	
 
def test_installed_packages(host, php_info):
 
    """
 
    Tests if the required packages have been installed.
 
    """
 

	
 
    assert host.package('nginx').is_installed
 
    assert host.package('python-setuptools').is_installed
 
    assert host.package('python3-setuptools').is_installed
 
    assert host.package('virtualenv').is_installed
 
    assert host.package('virtualenvwrapper').is_installed
 
    assert host.package(php_info.fpm_package).is_installed
 

	
 

	
 
def test_nginx_user(host):
 
    """
 
    Tests if Nginx user has been set-up correctly to traverse TLS directories.
 
    """
 

	
 
    assert 'ssl-cert' in host.user('www-data').groups
 

	
 

	
 
def test_default_tls_configuration_removed(host):
 
    """
 
    Tests if TLS configuration has been removed from the main (default)
 
    configuration file.
 
    """
 

	
 
    assert 'ssl_protocols' not in host.file('/etc/nginx/nginx.conf').content_string
 

	
 

	
 
def test_nginx_configuration_verification_script(host):
 
    """
 
    Tests if script used for verifying Nginx configuration is deployed
 
    correctly.
 
    """
 

	
 
    script = host.file('/usr/local/bin/nginx_verify_site.sh')
 

	
 
    assert script.is_file
 
    assert script.user == 'root'
 
    assert script.group == 'root'
 
    assert script.mode == 0o755
 

	
 

	
 
def test_tls_configuration_file(host):
 
    """
 
    Tests permissions of TLS configuration file.
 
    """
 

	
 
    config = host.file('/etc/nginx/conf.d/tls.conf')
 

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

	
 

	
 
def test_default_vhost_file(host):
 
    """
 
    Tests permissions of default vhost configuration file.
 
    """
 

	
 
    config = host.file('/etc/nginx/sites-available/default')
 

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

	
 

	
 
def test_default_website_enabled(host):
 
    """
 
    Tests if default website has been enabled.
 
    """
 

	
 
    config = host.file('/etc/nginx/sites-enabled/default')
 

	
 
    assert config.is_symlink
 
    assert config.linked_to == '/etc/nginx/sites-available/default'
 

	
 

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

	
 
    with host.sudo():
 

	
 
        config = host.file('/etc/ferm/conf.d/30-web.conf')
 

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

	
 

	
 
def test_default_debian_index_removed(host):
 
    """
 
    Tests if default HTML pages provided by debian are removed.
 
    """
 

	
 
    with host.sudo():
 
        assert not host.file('/var/www/html').exists
 

	
 

	
 
def test_default_vhost_root_directory(host):
 
    """
 
    Tests if the default vhost root directory exists.
 
    """
 

	
 
    directory = host.file('/var/www/default')
 

	
 
    assert directory.is_directory
 
    assert directory.user == 'root'
 
    assert directory.group == 'www-data'
 
    assert directory.mode == 0o750
 

	
 

	
 
def test_default_vhost_index_page_file(host):
 
    """
 
    Tests permissions of default vhost index page.
 
    """
 

	
 
    with host.sudo():
 

	
 
        page = host.file('/var/www/default/index.html')
 

	
 
        assert page.is_file
 
        assert page.user == 'root'
 
        assert page.group == 'www-data'
 
        assert page.mode == 0o640
 

	
 

	
 
def test_services(host, php_info):
 
    """
 
    Tests if services are enabled at boot and running.
 
    """
 

	
 
    service = host.service('nginx')
 
    assert service.is_enabled
 
    assert service.is_running
 

	
 
    service = host.service(php_info.fpm_service)
 
    assert service.is_enabled
 
    assert service.is_running
 

	
 

	
 
def test_sockets(host):
 
    """
 
    Tests if web server is listening on correct ports.
 
    """
 

	
 
    assert host.socket("tcp://80").is_listening
 
    assert host.socket("tcp://443").is_listening
 

	
 

	
 
@pytest.mark.parametrize("application_type, tmpfiles_d_path",
 
                         [("wsgi", "/etc/tmpfiles.d/wsgi.conf"),
 
                          ("php", "/etc/tmpfiles.d/php7.0-fpm.conf")])
 
def test_socket_directories(host, application_type, tmpfiles_d_path):
 
    """
 
    Tests if directories containing sockets for WSGI and PHP apps are created
 
    correctly.
 
    """
 

	
 
    socket_directory = "/run/%s" % application_type
 
    tmpfiles_d_content = "d /run/%s/ 0750 root www-data - -" % application_type
 

	
 
    directory = host.file(socket_directory)
 
    assert directory.is_directory
 
    assert directory.user == 'root'
 
    assert directory.group == 'www-data'
 
    assert directory.mode == 0o750
 

	
 
    config = host.file(tmpfiles_d_path)
 
    assert config.is_file
 
    assert config.user == 'root'
 
    assert config.group == 'root'
 
    assert config.mode == 0o644
 
    assert config.content_string == tmpfiles_d_content
 

	
 

	
 
def test_php_fpm_service_overrides(host, php_info):
 
    """
 
    Tests if overrides for PHP-FPM service are deployed correctly.
 
    """
 

	
 
    directory = host.file('/etc/systemd/system/%s.service.d' % php_info.fpm_service)
 
    assert directory.is_directory
 
    assert directory.user == 'root'
 
    assert directory.group == 'root'
 
    assert directory.mode == 0o755
 

	
 
    config = host.file('/etc/systemd/system/%s.service.d/umask.conf' % php_info.fpm_service)
 
    assert config.is_file
 
    assert config.user == 'root'
 
    assert config.group == 'root'
 
    assert config.mode == 0o644
 

	
 

	
 
def test_php_timezone_configuration(host, php_info):
 
    """
 
    Tests if PHP timezone configuration has been set correctly.
 
    """
 

	
 
    server_timezone = host.file("/etc/timezone").content_string.strip()
 

	
 
    config = host.file('%s/cli/conf.d/30-timezone.ini' % php_info.base_config_dir)
 
    assert config.is_file
 
    assert config.user == 'root'
 
    assert config.group == 'root'
 
    assert config.mode == 0o644
 

	
 
    config = host.file('%s/fpm/conf.d/30-timezone.ini' % php_info.base_config_dir)
 
    assert config.is_file
 
    assert config.user == 'root'
 
    assert config.group == 'root'
 
    assert config.mode == 0o644
 

	
 
    timezone = host.run("php --php-ini %s -r %s", "%s/cli/php.ini" % php_info.base_config_dir, "echo ini_get('date.timezone');")
 
    assert timezone.rc == 0
 
    assert timezone.stdout == server_timezone
 

	
 
    timezone = host.run("php --php-ini %s -r %s", "%s/fpm/php.ini" % php_info.base_config_dir, "echo ini_get('date.timezone');")
 
    assert timezone.rc == 0
 
    assert timezone.stdout == server_timezone
 

	
 

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

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

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

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

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

	
 

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

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

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

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

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

	
 
    assert used_dhparam == expected_dhparam
 

	
 

	
 
def test_nginx_tls_files(host):
 
    """
 
    Tests if TLS private key and certificate have been deployed correctly.
 
    """
 

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

	
 
    with host.sudo():
 

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

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

	
 

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

	
 

	
 
def test_tls_enabled(host):
 
    """
 
    Tests if TLS has been enabled.
 
    """
 

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

	
 
    tls = host.run('wget -q -O - https://%s/', fqdn)
 
    assert tls.rc == 0
 

	
 

	
 
def test_https_enforcement(host):
 
    """
 
    Tests if HTTPS is being enforced.
 
    """
 

	
 
    https_enforcement = host.run('curl -I http://parameters-mandatory/')
 

	
 
    assert https_enforcement.rc == 0
 
    assert 'HTTP/1.1 301 Moved Permanently' in https_enforcement.stdout
 
    assert 'Location: https://parameters-mandatory/' in https_enforcement.stdout
 

	
 
    https_enforcement = host.run('curl -I https://parameters-mandatory/')
 

	
 
    assert https_enforcement.rc == 0
 
    assert 'Strict-Transport-Security: max-age=31536000; includeSubDomains' in https_enforcement.stdout
roles/web_server/molecule/default/tests/test_mandatory.py
Show inline comments
 
import os
 

	
 
import defusedxml.ElementTree as ElementTree
 

	
 
import testinfra.utils.ansible_runner
 

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

	
 

	
 
def test_tls_version_and_ciphers(host):
 
    """
 
    Tests if the correct TLS version and ciphers have been enabled.
 
    """
 

	
 
    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 LDAP server, and fetch the
 
    # results.
 
    nmap = host.run("nmap -sV --script ssl-enum-ciphers -p 443 localhost -oX /tmp/report.xml")
 
    assert nmap.rc == 0
 
    report_content = host.file('/tmp/report.xml').content_string
 

	
 
    report_root = ElementTree.fromstring(report_content)
 

	
 
    tls_versions = []
 
    tls_ciphers = set()
 

	
 
    for child in report_root.findall("./host/ports/port/script/table"):
 
        tls_versions.append(child.attrib['key'])
 

	
 
    for child in report_root.findall(".//table[@key='ciphers']/table/elem[@key='name']"):
 
        tls_ciphers.add(child.text)
 

	
 
    tls_versions.sort()
 
    tls_ciphers = sorted(list(tls_ciphers))
 

	
 
    assert tls_versions == expected_tls_versions
 
    assert tls_ciphers == expected_tls_ciphers
 

	
 

	
 
def test_https_enforcement(host):
 
    """
 
    Tests if HTTPS is being enforced.
 
    """
 

	
 
    https_enforcement = host.run('curl -I http://parameters-mandatory/')
 

	
 
    assert https_enforcement.rc == 0
 
    assert 'HTTP/1.1 301 Moved Permanently' in https_enforcement.stdout
 
    assert 'Location: https://parameters-mandatory/' in https_enforcement.stdout
 

	
 
    https_enforcement = host.run('curl -I https://parameters-mandatory/')
 

	
 
    assert https_enforcement.rc == 0
 
    assert 'Strict-Transport-Security: max-age=31536000; includeSubDomains' in https_enforcement.stdout
 

	
 

	
 
def test_default_vhost_index_page(host):
 
    """
 
    Tests content of default vhost index page.
 
    """
 

	
 
    page = host.run('curl https://parameters-mandatory/')
 

	
 
    assert page.rc == 0
 
    assert "<title>Welcome</title>" in page.stdout
 
    assert "<h1>Welcome</h1>" in page.stdout
 
    assert "<p>You are attempting to access the web server using a wrong name or an IP address. Please check your URL.</p>" in page.stdout
roles/web_server/molecule/default/tests/test_optional.py
Show inline comments
 
import os
 

	
 
import defusedxml.ElementTree as ElementTree
 

	
 
import testinfra.utils.ansible_runner
 

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

	
 

	
 
def test_tls_version_and_ciphers(host):
 
    """
 
    Tests if the correct TLS version and ciphers have been enabled.
 
    """
 

	
 
    expected_tls_versions = ["TLSv1.1", "TLSv1.2"]
 

	
 
    expected_tls_ciphers = [
 
        "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
 
        "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
 
        "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
 
        "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
 
        "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
 
        "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
 
        "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
 
        "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
 
        "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
 
    ]
 

	
 
    # Run the nmap scanner against the LDAP server, and fetch the
 
    # results.
 
    nmap = host.run("nmap -sV --script ssl-enum-ciphers -p 443 localhost -oX /tmp/report.xml")
 
    assert nmap.rc == 0
 
    report_content = host.file('/tmp/report.xml').content_string
 

	
 
    report_root = ElementTree.fromstring(report_content)
 

	
 
    tls_versions = []
 
    tls_ciphers = set()
 

	
 
    for child in report_root.findall("./host/ports/port/script/table"):
 
        tls_versions.append(child.attrib['key'])
 

	
 
    for child in report_root.findall(".//table[@key='ciphers']/table/elem[@key='name']"):
 
        tls_ciphers.add(child.text)
 

	
 
    tls_versions.sort()
 
    tls_ciphers = sorted(list(tls_ciphers))
 

	
 
    assert tls_versions == expected_tls_versions
 
    assert tls_ciphers == expected_tls_ciphers
 

	
 

	
 
def test_https_enforcement(host):
 
    """
 
    Tests if HTTPS is (not) being enforced.
 
    """
 

	
 
    https_enforcement = host.run('curl -I http://parameters-optional/')
 

	
 
    assert https_enforcement.rc == 0
 
    assert 'HTTP/1.1 200 OK' in https_enforcement.stdout
 
    assert 'HTTP/1.1 301 Moved Permanently' not in https_enforcement.stdout
 
    assert 'Location: https://parameters-optional/' not in https_enforcement.stdout
 

	
 
    https_enforcement = host.run('curl -I https://parameters-optional/')
 

	
 
    assert https_enforcement.rc == 0
 
    assert 'Strict-Transport-Security' not in https_enforcement.stdout
 

	
 

	
 
def test_default_vhost_index_page(host):
 
    """
 
    Tests content of default vhost index page.
 
    """
 

	
 
    page = host.run('curl https://parameters-optional/')
 

	
 
    assert page.rc == 0
 
    assert "<title>Optional Welcome</title>" in page.stdout
 
    assert "<h1>Optional Welcome</h1>" in page.stdout
 
    assert "<p>Welcome to parameters-optional, default virtual host.</p>" in page.stdout
roles/web_server/templates/nginx-default.j2
Show inline comments
 
#
 
# Default server (vhost) configuration.
 
#
 
{% if default_enforce_https -%}
 
server {
 
    # HTTP (plaintext) configuration.
 
    listen 80 default_server;
 
    listen [::]:80 default_server;
 

	
 
    # Set server_name to something that won't be matched (for default server).
 
    server_name _;
 

	
 
    # Redirect plaintext connections to HTTPS
 
    return 301 https://$host$request_uri;
 
}
 

	
 
{% endif -%}
 
server {
 
{% if not default_enforce_https %}
 
    # HTTP (plaintext) configuration.
 
    listen 80 default_server;
 
    listen [::]:80 default_server;
 

	
 
{% endif %}
 
    # HTTPS (TLS) configuration.
 
    listen 443 ssl default_server;
 
    listen [::]:443 ssl default_server;
 
    ssl_certificate_key /etc/ssl/private/{{ ansible_fqdn }}_https.key;
 
    ssl_certificate /etc/ssl/certs/{{ ansible_fqdn }}_https.pem;
 

	
 
{% if default_enforce_https %}
 
    # Set-up HSTS header for preventing downgrades for users that visited the
 
    # site via HTTPS at least once.
 
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
 
{% endif %}
 

	
 
    # Set-up the serving of default page.
 
    root /var/www/default/;
 
    index index.html;
 

	
 
    # Set server_name to something that won't be matched (for default server).
 
    server_name _;
 

	
 
    location / {
 
        # Always point user to the same index page.
 
        try_files $uri /index.html;
 
    }
 
}
0 comments (0 inline, 0 general)