Changeset - d54738f1b930
[Not reviewed]
0 4 0
Branko Majic (branko) - 2 years ago 2023-08-09 23:27:43
branko@majic.rs
MAR-181: Drop support for Debian 9 Stretch from database role:

- Bump VM memory to 512MB (otherwise MariaDB fails to start up).
4 files changed with 2 insertions and 22 deletions:
0 comments (0 inline, 0 general)
docs/rolereference.rst
Show inline comments
 
@@ -1927,385 +1927,384 @@ Parameters
 
  ``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`` for Python 2 or
 
  ``/etc/pip_check_requirements_upgrades-py3/FQDN`` for Python 3. 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)
 
- Debian 10 (Buster)
 

	
 

	
 
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-up the database root user for passwordless login via UNIX
 
  socket authentication.
 
* Drops the ``debian-sys-maint`` database user (which was used in
 
  Debian Jessie and earlier for maintenance tasks) if it is present,
 
  and updates the Debian system maintenance configuration file to use
 
  the root account over unix socket authentication.
 

	
 

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

	
 
Depends on the following roles:
 

	
 
* **common**
 

	
 

	
 
Parameters
 
~~~~~~~~~~
 

	
 
This role has no parameters.
 

	
 

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

	
 
Role is compatible with the following distributions:
 

	
 
- Debian 10 (Buster)
 

	
 

	
 
Examples
 
~~~~~~~~
 

	
 
This role has no parameters which can be configured configure.
 

	
 

	
 
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)
 
- Debian 10 (Buster)
 

	
 

	
 
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)
 
  Defines host keys used for the dedicated OpenSSH server instance for
 
  backup. Key values that must be provided are:
 

	
 
  - **rsa**
 
  - **ed25519**
 
  - **ecdsa**
 

	
 
  Values for each key should be the corresponding private key
 
  generated using the appropriate algorithm. Keys for this purpose can
 
  be easily created via commands::
 

	
 
    ssh-keygen -f backup_server_rsa_key -N '' -t rsa
 
    ssh-keygen -f backup_server_ed25519_key -N '' -t ed25519
 
    ssh-keygen -f backup_server_ecdsa_key -N '' -t ecdsa
 

	
 

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

	
 
Role is compatible with the following distributions:
 

	
 
- Debian 9 (Stretch)
 
- Debian 10 (Stretch)
 

	
 

	
 
Examples
 
~~~~~~~~
 

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

	
 
.. code-block:: yaml
 

	
 
  - role: backup_server
 
    backup_clients:
 
      - server: web.example.com
 
        uid: 3000
 
        public_key: "{{ lookup('file', inventory_dir + '/ssh/web.example.com.pub') }}"
 
        ip: 10.32.64.18
 
      - server: mail.example.com
 
        public_key: "{{ lookup('file', inventory_dir + '/ssh/mail.example.com.pub') }}"
 
        ip: 10.32.64.15
 
    backup_host_ssh_private_keys:
 
      rsa: "{{ lookup('file', inventory_dir + '/ssh/backup_server_rsa_key') }}"
 
      ed25519: "{{ lookup('file', inventory_dir + '/ssh/backup_server_ed25519_key') }}"
 
      ecdsa: "{{ lookup('file', inventory_dir + '/ssh/backup_server_ecdsa_key') }}"
 

	
 

	
 
Backup Client
 
-------------
 

	
 
The ``backup_client`` role can be used for setting-up the server as a backup
 
client so it can perform backups to the backup server.
 

	
 
Backup clients utilise duplicity (via the duply convenience wrapper) for
 
performing the backups to a backup server via *SFTP* protocol.
 

	
 
The role itself will take care of deploying the necessary software,
 
configuration files, and encryption/signing private key to the backup client in
 
order to be able to perform backup.
 

	
 
Files that should be backed-up are specified using the ``backup`` role.
 

	
 
The role implements the following:
 

	
 
* Installs backup software (Duplicity, Duply).
 
* Sets-up Duply configuration under directory ``/etc/duply/main/``.
 
* Deploys encryption/signing private key (usually host-specific), as well as
 
  additional encryption public keys to the server, and imports them into local
 
  GnuPG keyring used by backup software.
 
* Deploys private SSH key for logging-in into the backup server over SFTP.
 
* Deploys ``known_hosts`` file for SFTP fingerprint verification.
 
* Sets-up a handler that runs scripts/binaries before the actual backup
 
  run. This is helpful for producing database backups. Such scripts/binaries
 
  should be deployed to directory ``/etc/duply/main/pre.d/``, and marked
 
  executable by the root user.
 

	
 
Duply is configured as follows:
 

	
 
* GnuPG keyring is stored under ``/etc/duply/main/gnupg/``. The keyring should
 
  not be managed manually.
 
* SSH private key for logging-in into backup server is stored in location
 
  ``/etc/duply/main/ssh/identity``. Backup server SFTP fingerprint is stored in
 
  location ``/etc/duply/main/ssh/known_hosts``.
 
* Base directory for back-ups is root (``/``), but *all* files are excluded by
 
  default to prevent huge back-ups. Ansible roles that want to utilise the
 
  backup client role can specify which patterns should be included in the backup
 
  when including the ``backup`` role. Include pattern file is assembled and
 
  stored in location ``/etc/duply/main/include``.
 
* Backups are encrypted and signed with the specified encryption key.
 
* Maximum age for old backups is set to 6 months.
 
* Maximum age for full backups is set to 1 month.
 
* Volume size is set to 1GB.
 
* Pre-backup scripts are run via ``/etc/duply/main/pre`` handler that tries to
 
  execute scripts/binaries from directory ``/etc/duply/main/pre.d/``.
 

	
 
.. note::
 
   Since at time of this writing there are no lookup plugins for extracting key
 
   material/information from GnuPG keyring, you may want to resort to extraction
roles/database/meta/main.yml
Show inline comments
 
---
 

	
 
allow_duplicates: true
 

	
 
dependencies:
 
  - database_server
 
  - role: backup
 
    when: enable_backup
 
    backup_patterns_filename: "database_{{ db_name }}"
 
    backup_patterns:
 
      - "/srv/backup/mariadb/{{ db_name }}.sql"
 

	
 
galaxy_info:
 
  author: Branko Majic
 
  description: Creates MariaDB database and accompanying user to access it
 
  license: BSD
 
  min_ansible_version: 2.9
 
  platforms:
 
    - name: Debian
 
      versions:
 
        - 9
 
        - 10
roles/database/molecule/default/molecule.yml
Show inline comments
 
---
 

	
 
dependency: {}
 

	
 
driver:
 
  name: vagrant
 
  provider:
 
    name: virtualbox
 

	
 
lint:
 
  name: yamllint
 
  options:
 
    config-file: ../../.yamllint.yml
 

	
 
platforms:
 

	
 
  - name: parameters-mandatory-stretch64
 
    groups:
 
      - parameters-mandatory
 
    box: debian/contrib-stretch64
 
    memory: 256
 
    cpus: 1
 

	
 
  - name: parameters-optional-stretch64
 
    groups:
 
      - parameters-optional
 
      - backup-server
 
    box: debian/contrib-stretch64
 
    memory: 512
 
    cpus: 1
 

	
 
  - name: parameters-mandatory-buster64
 
    groups:
 
      - parameters-mandatory
 
    box: debian/contrib-buster64
 
    memory: 256
 
    memory: 512
 
    cpus: 1
 

	
 
  - name: parameters-optional-buster64
 
    groups:
 
      - parameters-optional
 
      - backup-server
 
    box: debian/contrib-buster64
 
    memory: 512
 
    cpus: 1
 

	
 
provisioner:
 
  name: ansible
 
  config_options:
 
    defaults:
 
      force_valid_group_names: "ignore"
 
      interpreter_python: "/usr/bin/python3"
 
    ssh_connection:
 
      pipelining: "True"
 
  lint:
 
    name: ansible-lint
 

	
 
scenario:
 
  name: default
 

	
 
verifier:
 
  name: testinfra
 
  lint:
 
    name: flake8
roles/database/molecule/default/tests/test_default.py
Show inline comments
 
import os
 

	
 
import testinfra.utils.ansible_runner
 

	
 

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

	
 

	
 
def test_database_created(host):
 
    """
 
    Tests if database has been created.
 
    """
 

	
 
    with host.sudo():
 
        show_databases = host.run("mysql -BNe \"show databases like 'testdb'\"")
 

	
 
        assert show_databases.rc == 0
 
        assert show_databases.stdout == "testdb\n"
 

	
 

	
 
def test_database_user_login(host):
 
    """
 
    Tests database user login.
 
    """
 

	
 
    login = host.run("mysql -utestdb -ptestdbpassword -BNe 'show databases'")
 

	
 
    assert login.rc == 0
 

	
 

	
 
def test_database_user_permissions(host):
 
    """
 
    Tests if database user has been granted correct permissions on the database.
 
    """
 

	
 
    ansible_facts = host.ansible("setup")["ansible_facts"]
 
    ansible_distribution_release = ansible_facts['ansible_distribution_release']
 

	
 
    # Small difference in usage of backtick (`) instead of single
 
    # quote (') when displaying grants for user.
 
    if ansible_distribution_release == "stretch":
 
        expected_usage = "GRANT USAGE ON *.* TO 'testdb'@'localhost' IDENTIFIED BY PASSWORD '*676852B7FAE972722AD20D6E74781D6B1A100544'"
 
        expected_privileges = "GRANT ALL PRIVILEGES ON `testdb`.* TO 'testdb'@'localhost'"
 
    elif ansible_distribution_release == "buster":
 
    if ansible_distribution_release == "buster":
 
        expected_usage = "GRANT USAGE ON *.* TO `testdb`@`localhost` IDENTIFIED BY PASSWORD '*676852B7FAE972722AD20D6E74781D6B1A100544'"
 
        expected_privileges = "GRANT ALL PRIVILEGES ON `testdb`.* TO `testdb`@`localhost`"
 
    else:
 
        raise Exception("Tried running test on unsupported distribution: %s" % ansible_distribution_release)
 

	
 
    visible_databases = host.run("mysql -utestdb -ptestdbpassword -BNe 'show databases'")
 

	
 
    assert visible_databases.rc == 0
 
    assert visible_databases.stdout == "information_schema\ntestdb\n"
 

	
 
    with host.sudo():
 
        permissions_command = host.run("mysql -BNe 'show grants for testdb@localhost'")
 
        permissions = permissions_command.stdout.rstrip().split("\n")
 
        assert len(permissions) == 2
 
        assert expected_usage in permissions
 
        assert expected_privileges in permissions
0 comments (0 inline, 0 general)