├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── Vagrantfile ├── ansible.cfg ├── defaults └── main.yml ├── handlers └── main.yml ├── meta └── main.yml ├── tasks ├── configure.yml ├── databases.yml ├── install.yml ├── main.yml ├── monit.yml ├── secure.yml └── users.yml ├── templates ├── etc_monit_conf.d_mysql.j2 ├── etc_mysql_my.cnf.j2 └── root_dot_my.cnf.j2 ├── tests ├── idempotence_check.sh └── test.yml └── vagrant-inventory /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .AppleDouble 3 | .LSOverride 4 | Icon 5 | ._* 6 | .Spotlight-V100 7 | .Trashes 8 | .vagrant 9 | test 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | language: python 4 | python: "2.7" 5 | env: 6 | matrix: 7 | - ANSIBLE_VERSION="1.9.4" 8 | - ANSIBLE_VERSION="2.0.0.2" 9 | before_install: 10 | - sudo apt-get update -qq 11 | - sudo apt-get install -qq python-apt python-pycurl 12 | install: 13 | - pip install ansible=="$ANSIBLE_VERSION" 14 | script: 15 | - echo localhost > inventory 16 | 17 | # Syntax check 18 | - ansible-playbook -i inventory tests/test.yml --syntax-check 19 | 20 | # Play test 21 | - ansible-playbook -i inventory tests/test.yml --connection=local --sudo 22 | 23 | # Run the play again to circumvent idempotence bug on travis 24 | - ansible-playbook -i inventory tests/test.yml --connection=local --sudo --tags change_root_password 25 | 26 | # Idempotence test 27 | - ansible-playbook -i inventory tests/test.yml --connection=local --sudo > idempotence_out 28 | - ./tests/idempotence_check.sh idempotence_out 29 | 30 | # Test password changing 31 | - ansible-playbook -i inventory tests/test.yml --connection=local --sudo -e "mysql_default_root_password='CHANGEME' mysql_root_password='CHANGED'" 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014 Pieterjan Vandaele 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ANXS - MySQL [![Build Status](https://travis-ci.org/ANXS/mysql.png?branch=master)](https://travis-ci.org/ANXS/mysql) 2 | 3 | Ansible role that installs MySQL on (for now) Ubuntu variants. 4 | Features include: 5 | - Installation of MySQL and it's dependencies 6 | - Basic configuration 7 | - Standard hardening (root password, removal of test databases) 8 | - Add databases 9 | - Add users 10 | - Setup of monit process supervision 11 | 12 | 13 | #### Requirements & Dependencies 14 | - Tested on Ansible 1.4 or higher. 15 | - ANXS.monit if you want monit protection (in that case, you should set `monit_protection: true`) 16 | 17 | 18 | #### Variables 19 | 20 | ```yaml 21 | # Basic settings 22 | mysql_port: 3306 # The port on which mysql listens 23 | mysql_bind_address: '0.0.0.0' # The address the mysql server binds on 24 | mysql_root_password: '' # The new root password 25 | mysql_default_root_password: '' # The root password 26 | mysql_ppa: '' # Install MySQL from PPA repository 27 | mysql_apt_key: '' 28 | 29 | # Fine Tuning 30 | mysql_key_buffer: '16M' 31 | mysql_max_allowed_packet: '128M' 32 | mysql_thread_stack: '192K' 33 | mysql_cache_size: 8 34 | mysql_myisam_recover: 'BACKUP' 35 | mysql_max_connections: 100 36 | mysql_table_cache: 64 37 | mysql_thread_concurrency: 10 38 | mysql_query_cache_limit: '1M' 39 | mysql_query_cache_size: '16M' 40 | mysql_character_set_server: 'utf8' 41 | mysql_collation_server: 'utf8_general_ci' 42 | mysql_mysqldump_max_allowed_packet: '128M' 43 | mysql_isamchk_key_buffer: '16M' 44 | 45 | # InnoDB tuning 46 | mysql_innodb_file_per_table: 'innodb_file_per_table' 47 | mysql_innodb_flush_method: 'fdatasync' 48 | mysql_innodb_buffer_pool_size: '128M' 49 | mysql_innodb_flush_log_at_trx_commit: 1 50 | mysql_innodb_lock_wait_timeout: 50 51 | mysql_innodb_log_buffer_size: '1M' 52 | mysql_innodb_log_file_size: '5M' 53 | 54 | # List of databases to be created (optional) 55 | mysql_databases: 56 | - name: foobar 57 | collation: "utf8_general_ci" # optional, defaults to "utf8_general_ci" 58 | encoding: "utf8" # optional, defaults to "utf8" 59 | 60 | # List of users to be created (optional) 61 | mysql_users: 62 | - name: baz 63 | pass: pass 64 | priv: "*.*:ALL" # optional, defaults to "*.*:ALL" 65 | host: "%" # optional, defaults to "localhost" 66 | 67 | # GLOBAL Setting 68 | monit_protection: false # true or false, requires ANXS.monit 69 | 70 | # List of apt packages to install 71 | mysql_packages: 72 | - mysql-server 73 | - mysql-client 74 | - python-mysqldb 75 | ``` 76 | 77 | # Setting/Updating the root Password 78 | If You would like to change your mysql root password using this role, ensure 79 | you use the following variables in your play: 80 | 81 | ```yaml 82 | mysql_default_root_password: 83 | mysql_root_password: 84 | ``` 85 | 86 | #### Testing 87 | This project comes with a VagrantFile, this is a fast and easy way to test 88 | changes to the role, fire it up with `vagrant up`, provision the box with 89 | either: 90 | 91 | vagrant provision 92 | 93 | This also happens automatically after the first `vagrant up`, or: 94 | 95 | ansible-playbook test.yml -i vagrant-inventory --sudo 96 | 97 | This is the ansible way, and will easily allow command line arguments like `--tags` and `-e` 98 | 99 | See [vagrant docs](https://docs.vagrantup.com/v2/) for getting setup with vagrant 100 | 101 | 102 | #### License 103 | 104 | Licensed under the MIT License. See the LICENSE file for details. 105 | 106 | 107 | #### Feedback, bug-reports, requests, ... 108 | 109 | Are [welcome](https://github.com/ANXS/mysql/issues)! 110 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure('2') do |config| 5 | config.vm.define 'anxs' do |c| 6 | c.vm.box = 'ubuntu/trusty64' 7 | c.vm.network :private_network, ip: '192.168.88.15' 8 | c.vm.hostname = 'anxs.local' 9 | c.vm.provision 'ansible' do |ansible| 10 | ansible.playbook = 'tests/test.yml' 11 | ansible.sudo = true 12 | ansible.inventory_path = 'vagrant-inventory' 13 | ansible.host_key_checking = false 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | roles_path = ../ -------------------------------------------------------------------------------- /defaults/main.yml: -------------------------------------------------------------------------------- 1 | # file: mysql/defaults/main.yml 2 | 3 | # Basic settings 4 | mysql_port: 3306 5 | mysql_bind_address: "0.0.0.0" 6 | mysql_default_root_password: '' 7 | mysql_root_password: 'CHANGEME' 8 | mysql_language: '/usr/share/mysql/' 9 | mysql_ppa: '' 10 | mysql_apt_key: '' 11 | 12 | # Fine Tuning 13 | mysql_key_buffer: '16M' 14 | mysql_max_allowed_packet: '128M' 15 | mysql_thread_stack: '192K' 16 | mysql_cache_size: 8 17 | mysql_myisam_recover: 'BACKUP' 18 | mysql_max_connections: 100 19 | mysql_table_cache: 64 20 | mysql_thread_concurrency: 10 21 | mysql_query_cache_limit: '1M' 22 | mysql_query_cache_size: '16M' 23 | mysql_character_set_server: 'utf8' 24 | mysql_collation_server: 'utf8_general_ci' 25 | mysql_mysqldump_max_allowed_packet: '128M' 26 | mysql_isamchk_key_buffer: '16M' 27 | 28 | # InnoDB tuning 29 | mysql_innodb_file_per_table: 'innodb_file_per_table' 30 | mysql_innodb_flush_method: 'fdatasync' 31 | mysql_innodb_buffer_pool_size: '128M' 32 | mysql_innodb_flush_log_at_trx_commit: 1 33 | mysql_innodb_lock_wait_timeout: 50 34 | mysql_innodb_log_buffer_size: '1M' 35 | mysql_innodb_log_file_size: '5M' 36 | 37 | # Full-Text Search 38 | mysql_ft_min_word_len: 4 39 | 40 | # List of databases to be created 41 | mysql_databases: [] 42 | 43 | # List of users to be created 44 | mysql_users: [] 45 | 46 | # List of apt packages to install 47 | mysql_packages: 48 | - mysql-server 49 | - mysql-client 50 | - python-mysqldb 51 | -------------------------------------------------------------------------------- /handlers/main.yml: -------------------------------------------------------------------------------- 1 | # file: mysql/handlers/main.yml 2 | 3 | - name: restart mysql 4 | service: 5 | name: mysql 6 | state: restarted 7 | enabled: yes 8 | -------------------------------------------------------------------------------- /meta/main.yml: -------------------------------------------------------------------------------- 1 | # file: mysql/meta/main.yml 2 | 3 | galaxy_info: 4 | author: pjan vandaele 5 | company: ANXS 6 | description: "MySQL install and configuration (hardening, databases, users, ...)" 7 | min_ansible_version: 1.4 8 | license: MIT 9 | platforms: 10 | - name: Ubuntu 11 | versions: 12 | - all 13 | categories: 14 | - system 15 | 16 | dependencies: [] 17 | -------------------------------------------------------------------------------- /tasks/configure.yml: -------------------------------------------------------------------------------- 1 | # file: mysql/tasks/configure.yml 2 | 3 | - name: MySQL | Update the my.cnf 4 | template: 5 | src: etc_mysql_my.cnf.j2 6 | dest: /etc/mysql/my.cnf 7 | owner: root 8 | group: root 9 | mode: 0644 10 | notify: 11 | - restart mysql 12 | -------------------------------------------------------------------------------- /tasks/databases.yml: -------------------------------------------------------------------------------- 1 | # file: mysql/tasks/databases.yml 2 | 3 | - name: MySQL | Make sure the MySQL databases are present 4 | mysql_db: 5 | name: "{{ item.name }}" 6 | collation: "{{ item.collation | default('utf8_general_ci') }}" 7 | encoding: "{{ item.encoding | default('utf8') }}" 8 | state: present 9 | with_items: "{{ mysql_databases }}" 10 | when: "{{ mysql_databases|length > 0 }}" 11 | -------------------------------------------------------------------------------- /tasks/install.yml: -------------------------------------------------------------------------------- 1 | # file: mysql/tasks/install.yml 2 | 3 | - name: MySQL* | Add mysql apt key 4 | apt_key: data="{{ mysql_apt_key }}" state=present 5 | when: mysql_apt_key != False and mysql_apt_key != "" 6 | 7 | - name: MySQL | Add APT repository 8 | apt_repository: repo="{{mysql_ppa}}" update_cache=yes 9 | when: mysql_ppa != False and mysql_ppa != "" 10 | 11 | - name: MySQL | Make sure the MySQL packages are installed 12 | apt: 13 | pkg: "{{item}}" 14 | update_cache: yes 15 | cache_valid_time: 3600 16 | with_items: "{{ mysql_packages }}" 17 | 18 | - name: MySQL | Ensure MySQL is running 19 | service: 20 | name: mysql 21 | state: started 22 | -------------------------------------------------------------------------------- /tasks/main.yml: -------------------------------------------------------------------------------- 1 | # file: mysql/tasks/main.yml 2 | 3 | - include: install.yml 4 | - include: configure.yml 5 | - include: secure.yml 6 | - include: databases.yml 7 | - include: users.yml 8 | - include: monit.yml 9 | when: monit_protection is defined and monit_protection == true 10 | -------------------------------------------------------------------------------- /tasks/monit.yml: -------------------------------------------------------------------------------- 1 | # file: mysql/tasks/monit.yml 2 | 3 | - name: MySQL | (Monit) Copy the mysql monit service file 4 | template: 5 | src: etc_monit_conf.d_mysql.j2 6 | dest: /etc/monit/conf.d/mysql 7 | -------------------------------------------------------------------------------- /tasks/secure.yml: -------------------------------------------------------------------------------- 1 | # file: mysql/tasks/secure.yml 2 | 3 | - name: MySQL | Test whether the root password has already been changed (external hostname). 4 | mysql_user: 5 | user: root 6 | host: "{{item}}" 7 | password: "{{mysql_root_password}}" 8 | login_user: root 9 | login_password: "{{mysql_root_password}}" 10 | priv: "*.*:ALL,GRANT" 11 | with_items: 12 | - "{{ansible_hostname}}" 13 | - 127.0.0.1 14 | - ::1 15 | - localhost 16 | register: root_pw_already_set 17 | ignore_errors: yes 18 | when: ansible_hostname != 'localhost' 19 | tags: 20 | - change_root_password 21 | 22 | - name: MySQL | Changing the root password (external hostname). 23 | mysql_user: 24 | user: root 25 | host: "{{item}}" 26 | password: "{{mysql_root_password}}" 27 | login_user: root 28 | login_password: "{{mysql_default_root_password}}" 29 | priv: "*.*:ALL,GRANT" 30 | with_items: 31 | - "{{ansible_hostname}}" 32 | - 127.0.0.1 33 | - ::1 34 | - localhost 35 | when: "root_pw_already_set|failed and ansible_hostname != 'localhost'" 36 | tags: 37 | - change_root_password 38 | 39 | - name: MySQL | Test whether the root password has already been changed (local hostname). 40 | mysql_user: 41 | user: root 42 | host: "{{item}}" 43 | password: "{{mysql_root_password}}" 44 | login_user: root 45 | login_password: "{{mysql_root_password}}" 46 | priv: "*.*:ALL,GRANT" 47 | with_items: 48 | - 127.0.0.1 49 | - ::1 50 | - localhost 51 | register: root_pw_already_set 52 | ignore_errors: yes 53 | when: ansible_hostname == 'localhost' 54 | tags: 55 | - change_root_password 56 | 57 | - name: MySQL | Changing the root password (local hostname). 58 | mysql_user: 59 | user: root 60 | host: "{{item}}" 61 | password: "{{mysql_root_password}}" 62 | login_user: root 63 | login_password: "{{mysql_default_root_password}}" 64 | priv: "*.*:ALL,GRANT" 65 | with_items: 66 | - 127.0.0.1 67 | - ::1 68 | - localhost 69 | when: "root_pw_already_set|failed and ansible_hostname == 'localhost'" 70 | tags: 71 | - change_root_password 72 | 73 | - name: MySQL | Configure MySql for easy access as root user 74 | template: 75 | src: root_dot_my.cnf.j2 76 | dest: /root/.my.cnf 77 | owner: root 78 | group: root 79 | mode: 0600 80 | 81 | - name: MySQL | Remove anonymous MySQL server user 82 | mysql_user: 83 | name: "" 84 | host: "{{item}}" 85 | state: absent 86 | with_items: 87 | - "{{ansible_hostname}}" 88 | - localhost 89 | 90 | - name: MySQL | Remove the MySQL test database 91 | mysql_db: 92 | name: test 93 | state: absent 94 | -------------------------------------------------------------------------------- /tasks/users.yml: -------------------------------------------------------------------------------- 1 | # file: mysql/tasks/users.yml 2 | 3 | - name: MySQL | Make sure the MySQL users are present 4 | mysql_user: 5 | name: "{{item.name}}" 6 | password: "{{item.pass | default('pass')}}" 7 | priv: "{{item.priv | default('*.*:ALL')}}" 8 | state: present 9 | host: "{{item.host | default('localhost')}}" 10 | with_items: "{{ mysql_users }}" 11 | when: "{{ mysql_users|length > 0 }}" 12 | -------------------------------------------------------------------------------- /templates/etc_monit_conf.d_mysql.j2: -------------------------------------------------------------------------------- 1 | check process mysqld with pidfile /var/run/mysqld/mysqld.pid 2 | group database 3 | start program = "/etc/init.d/mysql start" 4 | stop program = "/etc/init.d/mysql stop" 5 | if failed host localhost port {{ mysql_port }} protocol mysql then restart 6 | if 5 restarts within 5 cycles then timeout 7 | -------------------------------------------------------------------------------- /templates/etc_mysql_my.cnf.j2: -------------------------------------------------------------------------------- 1 | # 2 | # The MySQL database server configuration file. 3 | # 4 | # For explanations see 5 | # http://dev.mysql.com/doc/mysql/en/server-system-variables.html 6 | 7 | [client] 8 | port = {{ mysql_port }} 9 | socket = /var/run/mysqld/mysqld.sock 10 | 11 | [mysqld_safe] 12 | socket = /var/run/mysqld/mysqld.sock 13 | nice = 0 14 | 15 | [mysqld] 16 | # * Basic Settings 17 | user = mysql 18 | pid-file = /var/run/mysqld/mysqld.pid 19 | socket = /var/run/mysqld/mysqld.sock 20 | port = {{ mysql_port }} 21 | basedir = /usr 22 | datadir = /var/lib/mysql 23 | tmpdir = /tmp 24 | # language is for pre-5.5. In 5.5 it is an alias for lc_messages_dir. 25 | language = {{ mysql_language }} 26 | bind-address = {{ mysql_bind_address }} 27 | skip-external-locking 28 | 29 | 30 | # * Fine Tuning 31 | thread_stack = {{ mysql_thread_stack }} 32 | thread_cache_size = {{ mysql_cache_size }} 33 | max_connections = {{ mysql_max_connections }} 34 | table_open_cache = {{ mysql_table_cache }} 35 | max_allowed_packet = {{ mysql_max_allowed_packet }} 36 | {% if not '5.7' in mysql_ppa %} 37 | # ** Not compatible with 5.7 38 | key_buffer = {{ mysql_key_buffer }} 39 | myisam-recover = {{ mysql_myisam_recover }} 40 | thread_concurrency = {{ mysql_thread_concurrency }} 41 | {% endif %} 42 | 43 | # ** Query Cache Configuration 44 | query_cache_limit = {{ mysql_query_cache_limit }} 45 | query_cache_size = {{ mysql_query_cache_size }} 46 | 47 | # ** Logging and Replication 48 | #general_log_file = /var/log/mysql/mysql.log 49 | #general_log = 1 50 | # 51 | #log_slow_queries = /var/log/mysql/mysql-slow.log 52 | #long_query_time = 2 53 | #log-queries-not-using-indexes 54 | # 55 | # The following can be used as easy to replay backup logs or for replication. 56 | #server-id = 1 57 | #log_bin = /var/log/mysql/mysql-bin.log 58 | expire_logs_days = 10 59 | max_binlog_size = 100M 60 | #binlog_do_db = include_database_name 61 | #binlog_ignore_db = include_database_name 62 | 63 | # ** InnoDB 64 | # InnoDB is enabled by default with a 10MB datafile in /var/lib/mysql/. 65 | # Read the manual for more InnoDB related options. There are many! 66 | innodb_flush_log_at_trx_commit = {{ mysql_innodb_flush_log_at_trx_commit }} 67 | innodb_buffer_pool_size = {{ mysql_innodb_buffer_pool_size }} 68 | {% if mysql_innodb_flush_method != 'fdatasync': %} 69 | innodb_flush_method = {{ mysql_innodb_flush_method }} 70 | {% endif %} 71 | innodb_lock_wait_timeout = {{ mysql_innodb_lock_wait_timeout }} 72 | innodb_log_buffer_size = {{ mysql_innodb_log_buffer_size }} 73 | innodb_log_file_size = {{ mysql_innodb_log_file_size }} 74 | {{ mysql_innodb_file_per_table }} 75 | 76 | # ** Security Features 77 | # Read the manual, too, if you want chroot! 78 | # chroot = /var/lib/mysql/ 79 | # 80 | # For generating SSL certificates I recommend the OpenSSL GUI "tinyca". 81 | # ssl-ca=/etc/mysql/cacert.pem 82 | # ssl-cert=/etc/mysql/server-cert.pem 83 | # ssl-key=/etc/mysql/server-key.pem 84 | 85 | character_set_server = {{ mysql_character_set_server }} 86 | collation_server = {{ mysql_collation_server }} 87 | 88 | # ** Full-Text Search 89 | ft_min_word_len = {{ mysql_ft_min_word_len }} 90 | 91 | [mysqldump] 92 | quick 93 | quote-names 94 | max_allowed_packet = {{ mysql_mysqldump_max_allowed_packet }} 95 | 96 | [mysql] 97 | #no-auto-rehash # faster start of mysql but no tab completition 98 | 99 | [isamchk] 100 | key_buffer = {{ mysql_isamchk_key_buffer }} 101 | 102 | # 103 | # * IMPORTANT: Additional settings that can override those from this file! 104 | # The files must end with '.cnf', otherwise they'll be ignored. 105 | # 106 | !includedir /etc/mysql/conf.d/ 107 | -------------------------------------------------------------------------------- /templates/root_dot_my.cnf.j2: -------------------------------------------------------------------------------- 1 | [client] 2 | user=root 3 | password={{ mysql_root_password }} 4 | -------------------------------------------------------------------------------- /tests/idempotence_check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Process the output of the given file (should contain a plays stdout/err) 4 | # If we pass, return with 0 else return with 1, and print useful output 5 | 6 | _file="$1" 7 | 8 | # Assert filename has been passed 9 | [ $# -eq 0 ] && { echo "Usage: $0 filename"; exit 1; } 10 | 11 | # Assert file exists 12 | [ ! -f "$_file" ] && { echo "$0: $_file file not found."; exit 2; } 13 | 14 | # Make sure nothing has changed or failed 15 | grep -q 'changed=0.*failed=0' $_file 16 | 17 | # Success condition 18 | if [ $? -eq 0 ]; then 19 | echo 'Idempotence test: pass' 20 | exit 21 | 22 | # Failure condition, extract useful information and exit 23 | else 24 | echo 'Idempotence test: fail' 25 | echo '' 26 | grep --color=auto -B1 -A1 "\(changed\|failed\):" $_file 27 | exit 1 28 | fi 29 | -------------------------------------------------------------------------------- /tests/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - hosts: all 4 | remote_user: root 5 | sudo: yes 6 | roles: 7 | - mysql -------------------------------------------------------------------------------- /vagrant-inventory: -------------------------------------------------------------------------------- 1 | [anxs] 2 | anxs.local ansible_ssh_host=192.168.88.15 ansible_ssh_port=22 ansible_ssh_user=vagrant 3 | --------------------------------------------------------------------------------