diff --git a/README.md b/README.md index f176f6c..d6846b5 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,16 @@ Ansible playbook that provisions a group of servers to run mariadb as a Galera C ## Requirements -If you are using HaProxy as a front end to the cluster, you will need to create a user for the health checks to work properly. The user does not need to be able to access any databases, it just needs to be able to authenticate against the server. +If you are using HaProxy as a front end to the cluster **without SSL**, you will need to create a user for the health checks to work properly. The user does not need to be able to access any databases, it just needs to be able to authenticate against the server. ```sql CREATE USER 'haproxy_check'@'10.10.10.253' WITH MAX_QUERIES_PER_HOUR 1 MAX_UPDATES_PER_HOUR 0 MAX_STATEMENT_TIME 0.0000000001; ``` +If you are using HaProxy as a front end to the cluster **with SSL**, use default tcp checks to validate the service is available as HaProxy does not support the SSL settings for mysql. + +If this is the first install, the `mariadb_cluster_bootstrap` variable will need to be set to true to bootstrap the cluster. + ## Variables | Variable | Required | Default | Description | @@ -43,6 +47,9 @@ You can view the status of the cluster by running the following command. ```sql SHOW GLOBAL STATUS LIKE 'wsrep_%'; +SHOW GLOBAL VARIABLES LIKE 'have_ssl'; +SHOW SESSION STATUS LIKE 'Ssl_cipher'; +SHOW GLOBAL VARIABLES LIKE 'require_secure_transport'; ``` ## License diff --git a/collections/requirements.yml b/collections/requirements.yml new file mode 100644 index 0000000..2c4c856 --- /dev/null +++ b/collections/requirements.yml @@ -0,0 +1,4 @@ +--- +collections: +- community.general +- community.crypto \ No newline at end of file diff --git a/roles/mariadb_cluster/defaults/main.yml b/roles/mariadb_cluster/defaults/main.yml index 70b9b51..6a694d2 100644 --- a/roles/mariadb_cluster/defaults/main.yml +++ b/roles/mariadb_cluster/defaults/main.yml @@ -3,3 +3,7 @@ mariadb_cluster_wsrep_cluster_name: "my_wsrep_cluster" mariadb_cluster_access_ip: "" +mariadb_cluster_master: "{{ groups['mariadb_cluster'][0] }}" +mariadb_cluster_ssl: true +mariadb_cluster_bootstrap: false +mariadb_cluster_cert_length: "+7300d" diff --git a/roles/mariadb_cluster/handlers/main.yml b/roles/mariadb_cluster/handlers/main.yml index f21ee31..413b365 100644 --- a/roles/mariadb_cluster/handlers/main.yml +++ b/roles/mariadb_cluster/handlers/main.yml @@ -17,3 +17,4 @@ - name: Bootstrap Galera include_tasks: tasks/bootstrap-galera.yml + when: mariadb_cluster_bootstrap == true diff --git a/roles/mariadb_cluster/tasks/bootstrap-galera.yml b/roles/mariadb_cluster/tasks/bootstrap-galera.yml index d625e63..9d815d6 100644 --- a/roles/mariadb_cluster/tasks/bootstrap-galera.yml +++ b/roles/mariadb_cluster/tasks/bootstrap-galera.yml @@ -3,12 +3,12 @@ - name: Stop MariaDB on first MariaDB Galera cluster node service: name=mariadb state=stopped - when: inventory_hostname == groups['mariadb_cluster'][0] + when: inventory_hostname == mariadb_cluster_master - name: Bootstrap first MariaDB Galera cluster node command: galera_new_cluster - when: inventory_hostname == groups['mariadb_cluster'][0] + when: inventory_hostname == mariadb_cluster_master - name: Restart the other MariaDB Galera cluster nodes service: name=mariadb state=restarted - when: inventory_hostname != groups['mariadb_cluster'][0] + when: inventory_hostname != mariadb_cluster_master diff --git a/roles/mariadb_cluster/tasks/certificates-server.yml b/roles/mariadb_cluster/tasks/certificates-server.yml new file mode 100644 index 0000000..d776fe3 --- /dev/null +++ b/roles/mariadb_cluster/tasks/certificates-server.yml @@ -0,0 +1,39 @@ +--- +# file: roles/mariadb_cluster/tasks/certificates-server.yml + + +- name: "{{ hostvars[item]['ansible_hostname'] }} - Create private key" + community.crypto.openssl_privatekey: + path: /etc/ssl/galera/server.key + +- name: "{{ hostvars[item]['ansible_hostname'] }} - Check if server certificate exists" + stat: + path: "/etc/ssl/galera/server.pem" + register: serverCertCheck + +- name: "{{ hostvars[item]['ansible_hostname'] }} - CSR" + block: + - name: "{{ hostvars[item]['ansible_hostname'] }} - Create CSR for new certificate" + community.crypto.openssl_csr_pipe: + privatekey_path: /etc/ssl/galera/server.key + common_name: "{{ hostvars[item]['ansible_hostname'] }}" + subject_alt_name: + - "DNS:{{ mariadb_cluster_wsrep_cluster_name }}" + register: csr + + - name: "{{ hostvars[item]['ansible_hostname'] }} - Sign certificate with CA" + community.crypto.x509_certificate_pipe: + csr_content: "{{ csr.csr }}" + provider: ownca + ownca_path: /etc/ssl/galera/ca-certificate.pem + ownca_privatekey_path: /etc/ssl/galera/ca-certificate.key + ownca_not_after: "{{ mariadb_cluster_cert_length }}" + ownca_not_before: "-1d" + delegate_to: "{{ mariadb_cluster_master }}" + register: certificate + + - name: "{{ hostvars[item]['ansible_hostname'] }} - Write certificate file" + copy: + dest: /etc/ssl/galera/server.pem + content: "{{ certificate.certificate }}" + when: not serverCertCheck.stat.exists diff --git a/roles/mariadb_cluster/tasks/certificates.yml b/roles/mariadb_cluster/tasks/certificates.yml new file mode 100644 index 0000000..84c5c20 --- /dev/null +++ b/roles/mariadb_cluster/tasks/certificates.yml @@ -0,0 +1,67 @@ +--- +# file: roles/mariadb_cluster/tasks/certificates.yml + +- name: Ensure directory exists for local self-signed TLS certs + file: + path: /etc/ssl/galera/ + state: directory + owner: mysql + group: mysql + recurse: true + +- name: CA Setup + block: + - name: Generate an OpenSSL private key + community.crypto.openssl_privatekey: + path: /etc/ssl/galera/ca-certificate.key + + - name: Create certificate signing request (CSR) for CA certificate + community.crypto.openssl_csr_pipe: + privatekey_path: /etc/ssl/galera/ca-certificate.key + common_name: Galera-Ansible + use_common_name_for_san: false + basic_constraints: + - 'CA:TRUE' + basic_constraints_critical: true + key_usage: + - keyCertSign + key_usage_critical: true + register: ca_csr + changed_when: false + + - name: Create self-signed CA certificate from CSR + community.crypto.x509_certificate: + path: /etc/ssl/galera/ca-certificate.pem + csr_content: "{{ ca_csr.csr }}" + privatekey_path: /etc/ssl/galera/ca-certificate.key + selfsigned_not_after: "{{ mariadb_cluster_cert_length }}" + provider: selfsigned + + - name: Copy ca-certificate locally for transfer + fetch: + src: /etc/ssl/galera/ca-certificate.pem + dest: /tmp/galera-ca-certificate.pem + flat: yes + when: inventory_hostname == mariadb_cluster_master + +- name: Transfer ca cert to other members + copy: + src: /tmp/galera-ca-certificate.pem + dest: /etc/ssl/galera/ca-certificate.pem + owner: mysql + group: mysql + mode: '0644' + +- name: Server Certificates + include_tasks: certificates-server.yml + loop: "{{ groups['mariadb_cluster'] }}" + loop_control: + extended: yes + +- name: Ensure mysql has permissions to access certs + file: + path: /etc/ssl/galera/ + state: directory + owner: mysql + group: mysql + recurse: true diff --git a/roles/mariadb_cluster/tasks/main.yml b/roles/mariadb_cluster/tasks/main.yml index 0b3e10b..b4c62ad 100644 --- a/roles/mariadb_cluster/tasks/main.yml +++ b/roles/mariadb_cluster/tasks/main.yml @@ -11,13 +11,24 @@ - mariadb-server - galera state: latest + tags: packages - name: Update galera config template: src: "galera.cnf.j2" - dest: "/etc/my.cnf.d/galera.cnf" + dest: "/etc/my.cnf.d/z-galera.cnf" notify: Bootstrap Galera +- name: Certificates tasks + include_tasks: certificates.yml + when: mariadb_cluster_ssl == true + +- name: Update ssl config + template: + src: "ssl.cnf.j2" + dest: "/etc/my.cnf.d/z-ssl.cnf" + when: mariadb_cluster_ssl == true + - name: Enable firewall rule for MySQL access firewalld: port: 3306/tcp @@ -29,11 +40,12 @@ - name: "Enable firewall rule for MySQL access to Access IP" firewalld: - rich_rule: 'rule family="ipv4" source address="{{ mariadb_cluster_access_ip }}" port port="3306" protocol="tcp" accept' + rich_rule: 'rule family="ipv4" source address="{{ item }}" port port="3306" protocol="tcp" accept' permanent: yes state: enabled immediate: yes notify: Reload firewalld + loop: "{{ mariadb_cluster_access_ip }}" when: mariadb_cluster_access_ip != "" - name: Setup access for other servers @@ -66,6 +78,12 @@ dest: /etc/systemd/system/mariadb.service.d/override.conf notify: Daemon Reload +- name: Set selinux nis_enabled + seboolean: + name: nis_enabled + state: true + persistent: true + - name: Flush handlers meta: flush_handlers diff --git a/roles/mariadb_cluster/templates/galera.cnf.j2 b/roles/mariadb_cluster/templates/galera.cnf.j2 index acc6f05..52d41f2 100644 --- a/roles/mariadb_cluster/templates/galera.cnf.j2 +++ b/roles/mariadb_cluster/templates/galera.cnf.j2 @@ -37,7 +37,11 @@ wsrep_on=1 wsrep_provider=/usr/lib64/galera/libgalera_smm.so # Provider specific configuration options +{% if mariadb_cluster_ssl == true %} +wsrep_provider_options="socket.ssl_key=/etc/ssl/galera/server.key;socket.ssl_cert=/etc/ssl/galera/server.pem;socket.ssl_ca=/etc/ssl/galera/ca-certificate.pem" +{% else %} #wsrep_provider_options= +{% endif %} # Logical cluster name. Should be the same for all nodes. wsrep_cluster_name="{{ mariadb_cluster_wsrep_cluster_name }}" diff --git a/roles/mariadb_cluster/templates/ssl.cnf.j2 b/roles/mariadb_cluster/templates/ssl.cnf.j2 new file mode 100644 index 0000000..119b971 --- /dev/null +++ b/roles/mariadb_cluster/templates/ssl.cnf.j2 @@ -0,0 +1,12 @@ +# MySQL Server +[mysqld] +ssl-ca = /etc/ssl/galera/ca-certificate.pem +ssl-key = /etc/ssl/galera/server.key +ssl-cert = /etc/ssl/galera/server.pem +require_secure_transport = 1 + +# MySQL Client Configuration +[mysql] +ssl-ca = /etc/ssl/galera/ca-certificate.pem +ssl-key = /etc/ssl/galera/server.key +ssl-cert = /etc/ssl/galera/server.pem