From 377933b6ee965f9f8658db91dbf8ab08eba56cb3 Mon Sep 17 00:00:00 2001 From: Adar Nimrod <nimrod@shore.co.il> Date: Mon, 29 Aug 2016 10:08:54 +0300 Subject: [PATCH] - Added pre-commit hook to lint backup shell script. - Backup is now done by the OS user nobody and MySQL user backup. - Backup script now outputs start, finish and error to stdout. - Removed dependency on ca-store Ansible role. - The MySQL user is added to the ssl group only if the group exists. - Backup provisioninig is now optional depending on if the mysql_backup_password variables is defined. - Moved most configuration to templates inside conf.d. - Remote admin account is now optional depending on if the mysql_admin_password is defined. - The mysql-server packges is reconfigured to reset root password if it's needed. --- .pre-commit-config.yaml | 5 + README.rst | 6 ++ defaults/main.yml | 4 + files/backup.sh | 34 ++++--- meta/main.yml | 5 +- molecule.yml | 6 +- tasks/backup.yml | 48 ++++++++++ tasks/main.yml | 114 +++++++++++------------ templates/mysql/conf.d/bind.cnf | 2 + templates/mysql/conf.d/binlog-ignore.cnf | 3 + templates/mysql/conf.d/serverid.cnf | 2 + templates/mysql/conf.d/ssl.cnf | 6 ++ templates/mysql/conf.d/syslog.cnf | 2 + templates/mysqldump.cnf | 13 +++ tests/playbook.yml | 7 +- vars/main.yml | 8 ++ 16 files changed, 179 insertions(+), 86 deletions(-) mode change 100644 => 100755 files/backup.sh create mode 100644 tasks/backup.yml create mode 100644 templates/mysql/conf.d/bind.cnf create mode 100644 templates/mysql/conf.d/binlog-ignore.cnf create mode 100644 templates/mysql/conf.d/serverid.cnf create mode 100644 templates/mysql/conf.d/ssl.cnf create mode 100644 templates/mysql/conf.d/syslog.cnf create mode 100644 templates/mysqldump.cnf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6274aa0..d9dd353 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,3 +10,8 @@ sha: 94b506c144d4e22ebc1deef637a818db13bcaca5 hooks: - id: ansible-pre-commit +- repo: https://www.shore.co.il/git/shell-pre-commit/ + sha: 604fe77b53d3514d5ad0f66764c7783850bb6e98 + hooks: + - id: shell-lint + files: files/backup.sh diff --git a/README.rst b/README.rst index 372b9c5..4e8163e 100644 --- a/README.rst +++ b/README.rst @@ -52,3 +52,9 @@ Nimrod Adar, `contact me <nimrod@shore.co.il>`_ or visit my `website <https://www.shore.co.il/>`_. Patches are welcome via `git send-email <http://git-scm.com/book/en/v2/Git-Commands-Email>`_. The repository is located at: https://www.shore.co.il/git/. + +TODO +---- + +- Backup script. +- Testing. diff --git a/defaults/main.yml b/defaults/main.yml index b2fc2dc..6d1103f 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -2,3 +2,7 @@ # defaults file for ansible-role-mysql mysql_admin_password: +mysql_tls_key: /etc/ssl/private/ssl-cert-snakeoil.key +mysql_tls_cert: /etc/ssl/certs/ssl-cert-snakeoil.pem +mysql_mail_alias: root +mysql_serverid: '{{ ansible_default_ipv4["address"]|ipaddr("int") }}' diff --git a/files/backup.sh b/files/backup.sh old mode 100644 new mode 100755 index 5b34c25..2589e77 --- a/files/backup.sh +++ b/files/backup.sh @@ -1,21 +1,19 @@ -#!/bin/sh -e -# Back up all databases, each to a seperate file. +#!/bin/sh +set -eu -backup() { - mysqldump --defaults-file=/etc/mysql/debian.cnf \ - --single-transaction \ - --force \ - --result-file=/var/backups/mysql_$1.sql $1 -} +echo "MySQL backup: Starting at $(date -u)." -# TODO: Find a way to remove table formatting from this command. -#alias show='mysqlshow --defaults-file=/etc/mysql/debian.cnf' -alias show="mysql --defaults-file=/etc/mysql/debian.cnf -e 'show databases;'" +# Grep filter to remove heading and internal MySQL databases. +filter='Database\|information_schema\|performance_schema\|mysql' -# The reason for dropping the first 4 lines is that the first line is the -# heading and 3 following lines are databases internal to MySQL and thus their -# backup is not needed. -for database in $(show | tail -n+5) -do - backup $database -done +if databases="$(mysql --defaults-file=/etc/mysql/mysqldump.cnf \ + --execute 'show databases;' | grep -vx $filter)" +then + mysqldump --defaults-file=/etc/mysql/mysqldump.cnf + --databases $databases || \ + echo "MySQL backup: Failed to backup." +else + echo "MySQL backup: No databases to backup." +fi + +echo "MySQL backup: Finished at $(date -u)." diff --git a/meta/main.yml b/meta/main.yml index 4ac93e8..ab66d2c 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -14,7 +14,4 @@ galaxy_info: versions: - precise - trusty -dependencies: - - src: https://www.shore.co.il/git/ansible-role-ca-store - scm: git - name: ca-store +dependencies: [] diff --git a/molecule.yml b/molecule.yml index 0e6fa47..10f7f63 100644 --- a/molecule.yml +++ b/molecule.yml @@ -13,10 +13,10 @@ vagrant: - name: virtualbox type: virtualbox platforms: - - name: openbsd - box: kaorimatz/openbsd-5.9-amd64 + - name: debian + box: debian/jessie64 instances: - - name: ansible-role-example + - name: ansible-role-mysql options: append_platform_to_hostname: yes raw_config_args: diff --git a/tasks/backup.yml b/tasks/backup.yml new file mode 100644 index 0000000..9eaaebe --- /dev/null +++ b/tasks/backup.yml @@ -0,0 +1,48 @@ +--- +- name: APT install cron + apt: + name: cron + state: present + update_cache: yes + cache_valid_time: 3600 + +- name: Add backup account + mysql_user: + login_password: '{{ mysql_root_password|default(omit) }}' + name: backup + host: '%' + password: '{{ mysql_backup_password }}' + priv: '*.*:SELECT,FILE,RELOAD,REPLICATION CLIENT' + state: present + +- name: Create backup directory + file: + path: /var/backups/mysql + owner: nobody + group: nogroup + mode: 0o0700 + state: directory + +- name: Copy backup configuration + template: + src: mysqldump.cnf + dest: /etc/mysql/mysqldump.cnf + owner: nobody + group: nogroup + mode: 0o0400 + +- name: Copy backup job + copy: + src: backup.sh + dest: /usr/local/sbin/mysql-backup + owner: root + group: root + mode: 0o0755 + +- name: Add daily backup job + cron: + user: nobody + name: MySQL backup + job: '/usr/local/sbin/mysql-backup | logger' + special_time: daily + state: present diff --git a/tasks/main.yml b/tasks/main.yml index 74b9742..8b36bbe 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -2,91 +2,87 @@ # tasks file for ansible-role-mysql - assert: that: - - ansible_os_family == 'OpenBSD' - - ansible_distribution_release == '5.9' + - ansible_os_family == 'Debian' + - ansible_distribution_release in ['wheezy', 'jessie', 'stretch', 'precise', 'trusty', 'xenial'] -- name: apt install +- name: Get groups + getent: + database: group + +- name: Preseed root password + when: mysql_root_password is defined + with_items: + - root_password + - root_password_again + debconf: + name: '{{ mysql_server_package }}' + question: 'mysql-server/{{ item }}' + vtype: password + value: '{{ mysql_root_password }}' + changed_when: False # Can't verify previous password therefore there's always + # a change, explicitly disable that. + +- name: APT install + with_items: + - mysql-server + - mysql-client + - python-mysqldb apt: name: '{{ item }}' state: present update_cache: yes cache_valid_time: 3600 - with_items: - - mysql-server - - mysql-client - - python-mysqldb - - cron + +- name: Reconfigure package in case root password was changed + changed_when: False + command: 'dpkg-reconfigure --frontend noninteractive {{ mysql_server_package }}' - name: Allow MySQL access to the TLS cert and key + when: "'ssl-cert' in getent_group" user: append: yes groups: ssl-cert name: mysql notify: - - Restart MySQL - -- name: Configure - with_dict: - 'ssl-ca': /etc/ssl/certs/ca-certificates.crt - 'ssl-cert': '{{ tls_cert_path }}' - 'ssl-key': '{{ tls_key_path }}' - 'bind-address': '0.0.0.0' - ini_file: - dest: /etc/mysql/my.cnf - owner: root - group: root - mode: '0644' - section: mysqld - option: '{{ item.key }}' - value: '{{ item.value }}' - notify: - - Restart MySQL + - Restart MySQL -- name: Log to syslog +- name: Alias mail + when: mysql_mail_alias is defined lineinfile: - dest: /etc/mysql/my.cnf - owner: root - group: root - mode: '0644' - line: 'syslog' - insertafter: '[mysqld_safe]' - notify: - - Restart MySQL + dest: /etc/aliases + create: True + line: 'mysql: {{ mysql_mail_alias }}' + regexp: 'mysql:' + state: present - name: Add admin account + when: mysql_admin_password is defined mysql_user: name: admin host: '%' password: '{{ mysql_admin_password }}' - priv: '*.*:ALL,GRANT' + priv: '*.*:ALL,GRANT,REQUIRESSL' + login_password: '{{ mysql_root_password|default(omit) }}' state: present -- name: Require SSL for admin account - mysql_user: - name: admin - host: '%' - append_privs: True - priv: '*.*:REQUIRESSL' - state: present - -- name: Allow MySQL in firewall - ufw: - rule: allow - port: 3306 - proto: tcp - -- name: Add daily backup job - copy: - src: backup.sh - dest: /etc/cron.daily/mysql - owner: root - group: root - mode: '0755' +- name: Copy configuration templates + with_fileglob: + - templates/mysql/conf.d/*.cnf + - '{{ playbook_dir }}/templates/mysql/conf.d/*.cnf' + template: + src: '{{ item }}' + dest: /etc/mysql/conf.d + owner: root + group: root + mode: 0o0644 + notify: + - Restart MySQL - meta: flush_handlers - name: Wait for service to come online wait_for: - host: '{{ ansible_default_ipv4["address"] }}' port: 3306 - state: started + +- include: backup.yml + when: mysql_backup_password is defined diff --git a/templates/mysql/conf.d/bind.cnf b/templates/mysql/conf.d/bind.cnf new file mode 100644 index 0000000..f759a49 --- /dev/null +++ b/templates/mysql/conf.d/bind.cnf @@ -0,0 +1,2 @@ +[mysqld] +bind-address = 0.0.0.0 diff --git a/templates/mysql/conf.d/binlog-ignore.cnf b/templates/mysql/conf.d/binlog-ignore.cnf new file mode 100644 index 0000000..77bb98a --- /dev/null +++ b/templates/mysql/conf.d/binlog-ignore.cnf @@ -0,0 +1,3 @@ +[mysqld] +binlog-ignore-db = information_schema, performance_schema, mysql +replicate-ignore-db = information_schema, performance_schema, mysql diff --git a/templates/mysql/conf.d/serverid.cnf b/templates/mysql/conf.d/serverid.cnf new file mode 100644 index 0000000..65756ad --- /dev/null +++ b/templates/mysql/conf.d/serverid.cnf @@ -0,0 +1,2 @@ +[mysqld] +server-id = {{ mysql_serverid }} diff --git a/templates/mysql/conf.d/ssl.cnf b/templates/mysql/conf.d/ssl.cnf new file mode 100644 index 0000000..d680959 --- /dev/null +++ b/templates/mysql/conf.d/ssl.cnf @@ -0,0 +1,6 @@ +[mysqld] +ssl-ca = /etc/ssl/certs/ca-certificates.crt +{% if mysql_tls_cert is defined and mysql_tls_key is defined %} +ssl-cert = {{ mysql_tls_cert }} +ssl-key = {{ mysql_tls_key }} +{% endif %} diff --git a/templates/mysql/conf.d/syslog.cnf b/templates/mysql/conf.d/syslog.cnf new file mode 100644 index 0000000..3b0445d --- /dev/null +++ b/templates/mysql/conf.d/syslog.cnf @@ -0,0 +1,2 @@ +[mysqld_safe] +syslog diff --git a/templates/mysqldump.cnf b/templates/mysqldump.cnf new file mode 100644 index 0000000..98acfca --- /dev/null +++ b/templates/mysqldump.cnf @@ -0,0 +1,13 @@ +[mysqldump] +single-transaction +master-data = 2 +force +result-file = /var/backups/mysql/mysql.sql +dump-date +tz-utc + +[client] +host = localhost +user = backup +password = {{ mysql_backup_password }} +socket = /var/run/mysqld/mysqld.sock diff --git a/tests/playbook.yml b/tests/playbook.yml index e739a2b..13f7717 100644 --- a/tests/playbook.yml +++ b/tests/playbook.yml @@ -1,5 +1,8 @@ --- - hosts: all - gather_facts: false + vars: + mysql_root_password: qwer12345 + mysql_backup_password: backup + mysql_admin_password: admin roles: - - role: ansible-role-example + - role: ansible-role-mysql diff --git a/vars/main.yml b/vars/main.yml index 5ec007d..5536484 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -1,2 +1,10 @@ --- # vars file for ansible-role-mysql +mysql_version: + precise: 5.5 + trusty: 5.5 + xenial: 5.7 + wheezy: 5.5 + jessie: 5.5 + stretch: 5.6 +mysql_server_package: 'mysql-server-{{ mysql_version[ansible_distribution_release] }}' -- GitLab