From 5de72c80327e09c83f7fa27c3c42ef583b63ce1b Mon Sep 17 00:00:00 2001 From: Colan Schwartz Date: Thu, 7 Oct 2021 11:10:19 -0400 Subject: [PATCH 1/7] Issue #223: Generate a random default root password Instead of hardcoding an insecure default password for the `root` mysql user, generate a random one as discussed in issue #223 . --- defaults/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/defaults/main.yml b/defaults/main.yml index 318b1694..527f2fd9 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -3,7 +3,7 @@ # or sudo access mysql_user_home: /root mysql_user_name: root -mysql_user_password: root +mysql_user_password: "{{ lookup('password', '/dev/null length=20 chars=ascii_letters') }}" # The default root user installed by mysql - almost always root mysql_root_home: /root From daaea32c1810b48b24ef3cf47225efeafbdc76ef Mon Sep 17 00:00:00 2001 From: Colan Schwartz Date: Thu, 7 Oct 2021 11:27:02 -0400 Subject: [PATCH 2/7] Issue #223: Set the user password as well, not just the root one. --- defaults/main.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/defaults/main.yml b/defaults/main.yml index 527f2fd9..d84f1e32 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,14 +1,17 @@ --- +# Set a random password. +mysql_password: "{{ lookup('password', '/dev/null length=20 chars=ascii_letters') }}" + # Set this to the user ansible is logging in as - should have root # or sudo access mysql_user_home: /root mysql_user_name: root -mysql_user_password: "{{ lookup('password', '/dev/null length=20 chars=ascii_letters') }}" +mysql_user_password: "{{ mysql_password }}" # The default root user installed by mysql - almost always root mysql_root_home: /root mysql_root_username: root -mysql_root_password: root +mysql_root_password: "{{ mysql_password }}" # Set this to `true` to forcibly update the root password. mysql_root_password_update: false From 254fdf159748036a2a209c7d642b8b735d3e7b17 Mon Sep 17 00:00:00 2001 From: Colan Schwartz <13228-colan@users.noreply.gitlab.com> Date: Thu, 7 Oct 2021 15:41:36 -0400 Subject: [PATCH 3/7] Issue #223: Set the root DB password from the ini file to prevent regeneration. --- tasks/secure-installation.yml | 42 +++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/tasks/secure-installation.yml b/tasks/secure-installation.yml index d7a17b8f..c115121d 100644 --- a/tasks/secure-installation.yml +++ b/tasks/secure-installation.yml @@ -36,6 +36,32 @@ check_mode: false when: mysql_install_packages | bool or mysql_root_password_update +- name: Set the .my.cnf file path. + set_fact: + mysql_root_cnf_path: "{{ mysql_root_home }}/.my.cnf" + +- name: Copy .my.cnf file with root password credentials. + template: + src: "root-my.cnf.j2" + dest: "{{ mysql_root_cnf_path }}" + owner: root + group: root + mode: 0600 + when: mysql_install_packages | bool or mysql_root_password_update + register: mysql_root_password_setting + +- name: Fetch the .my.cnf file containing the root password + slurp: + src: "{{ mysql_root_cnf_path }}" + register: mysql_root_cnf_file + +# It would be cleaner to use the `ini` lookup plugin, but that only works +# locally so we'd have to copy the file first, which we'd rather not do because +# it contains secrets. +- name: Extract the root password from .my.cnf + set_fact: + mysql_root_password_generated: "{{ mysql_root_cnf_file['content'] | b64decode | regex_findall('password=\"(.+)\"') | first }}" + # Note: We do not use mysql_user for this operation, as it doesn't always update # the root password correctly. See: https://goo.gl/MSOejW # Set root password for MySQL >= 5.7.x. @@ -43,31 +69,23 @@ shell: > mysql -u root -NBe 'ALTER USER "{{ mysql_root_username }}"@"{{ item }}" - IDENTIFIED WITH mysql_native_password BY "{{ mysql_root_password }}"; FLUSH PRIVILEGES;' + IDENTIFIED WITH mysql_native_password BY "{{ mysql_root_password_generated }}"; FLUSH PRIVILEGES;' with_items: "{{ mysql_root_hosts.stdout_lines|default([]) }}" when: > ((mysql_install_packages | bool) or mysql_root_password_update) and ('5.7.' in mysql_cli_version.stdout or '8.0.' in mysql_cli_version.stdout) + and (mysql_root_password_setting.changed is true) # Set root password for MySQL < 5.7.x. - name: Update MySQL root password for localhost root account (< 5.7.x). shell: > mysql -NBe - 'SET PASSWORD FOR "{{ mysql_root_username }}"@"{{ item }}" = PASSWORD("{{ mysql_root_password }}"); FLUSH PRIVILEGES;' + 'SET PASSWORD FOR "{{ mysql_root_username }}"@"{{ item }}" = PASSWORD("{{ mysql_root_password_generated }}"); FLUSH PRIVILEGES;' with_items: "{{ mysql_root_hosts.stdout_lines|default([]) }}" when: > ((mysql_install_packages | bool) or mysql_root_password_update) and ('5.7.' not in mysql_cli_version.stdout and '8.0.' not in mysql_cli_version.stdout) - -# Has to be after the root password assignment, for idempotency. -- name: Copy .my.cnf file with root password credentials. - template: - src: "root-my.cnf.j2" - dest: "{{ mysql_root_home }}/.my.cnf" - owner: root - group: root - mode: 0600 - when: mysql_install_packages | bool or mysql_root_password_update + and (mysql_root_password_setting.changed is true) - name: Get list of hosts for the anonymous user. command: mysql -NBe 'SELECT Host FROM mysql.user WHERE User = ""' From 055e6e133aaf562520fd0a212d13b28f2ec97470 Mon Sep 17 00:00:00 2001 From: Colan Schwartz <13228-colan@users.noreply.gitlab.com> Date: Fri, 8 Oct 2021 13:20:51 -0400 Subject: [PATCH 4/7] Issue #223: Made the variable name for the auto-generated root password more descriptive. --- defaults/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/defaults/main.yml b/defaults/main.yml index d84f1e32..99c29ecb 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,17 +1,17 @@ --- # Set a random password. -mysql_password: "{{ lookup('password', '/dev/null length=20 chars=ascii_letters') }}" +mysql_autogenerated_password: "{{ lookup('password', '/dev/null length=20 chars=ascii_letters') }}" # Set this to the user ansible is logging in as - should have root # or sudo access mysql_user_home: /root mysql_user_name: root -mysql_user_password: "{{ mysql_password }}" +mysql_user_password: "{{ mysql_autogenerated_password }}" # The default root user installed by mysql - almost always root mysql_root_home: /root mysql_root_username: root -mysql_root_password: "{{ mysql_password }}" +mysql_root_password: "{{ mysql_autogenerated_password }}" # Set this to `true` to forcibly update the root password. mysql_root_password_update: false From cf4d1447868b02d43c9dc120ae544e2f422985dd Mon Sep 17 00:00:00 2001 From: Colan Schwartz <13228-colan@users.noreply.gitlab.com> Date: Fri, 8 Oct 2021 14:33:58 -0400 Subject: [PATCH 5/7] Issue #223: Write the user's generated password using that to set it in the DB. --- tasks/secure-installation.yml | 49 +++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/tasks/secure-installation.yml b/tasks/secure-installation.yml index c115121d..e07eaf27 100644 --- a/tasks/secure-installation.yml +++ b/tasks/secure-installation.yml @@ -1,24 +1,39 @@ --- -- name: Ensure default user is present. - mysql_user: - name: "{{ mysql_user_name }}" - host: 'localhost' - password: "{{ mysql_user_password }}" - priv: '*.*:ALL,GRANT' - state: present - when: mysql_user_name != mysql_root_username +- name: Set the user's .my.cnf file path. + set_fact: + mysql_user_cnf_path: "{{ mysql_user_home }}/.my.cnf" -# Has to be after the password assignment, for idempotency. -- name: Copy user-my.cnf file with password credentials. +- name: Write the user's .my.cnf file with password credentials. template: src: "user-my.cnf.j2" - dest: "{{ mysql_user_home }}/.my.cnf" + dest: "{{ mysql_user_cnf_path }}" owner: "{{ mysql_user_name }}" mode: 0600 when: > mysql_user_name != mysql_root_username and (mysql_install_packages | bool or mysql_user_password_update) +- name: Fetch contents of the user's .my.cnf file + slurp: + src: "{{ mysql_user_cnf_path }}" + register: mysql_user_cnf_file + +# It would be cleaner to use the `ini` lookup plugin, but that only works +# locally so we'd have to copy the file first, which we'd rather not do because +# it contains secrets. +- name: Extract the user password from .my.cnf + set_fact: + mysql_user_password_written: "{{ mysql_user_cnf_file['content'] | b64decode | regex_findall('password=\"(.+)\"') | first }}" + +- name: Ensure default user is present. + mysql_user: + name: "{{ mysql_user_name }}" + host: 'localhost' + password: "{{ mysql_user_password_written }}" + priv: '*.*:ALL,GRANT' + state: present + when: mysql_user_name != mysql_root_username + - name: Disallow root login remotely command: 'mysql -NBe "{{ item }}"' with_items: @@ -36,11 +51,11 @@ check_mode: false when: mysql_install_packages | bool or mysql_root_password_update -- name: Set the .my.cnf file path. +- name: Set root's .my.cnf file path. set_fact: mysql_root_cnf_path: "{{ mysql_root_home }}/.my.cnf" -- name: Copy .my.cnf file with root password credentials. +- name: Write root's .my.cnf file with password credentials. template: src: "root-my.cnf.j2" dest: "{{ mysql_root_cnf_path }}" @@ -50,7 +65,7 @@ when: mysql_install_packages | bool or mysql_root_password_update register: mysql_root_password_setting -- name: Fetch the .my.cnf file containing the root password +- name: Fetch contents of root's .my.cnf file slurp: src: "{{ mysql_root_cnf_path }}" register: mysql_root_cnf_file @@ -60,7 +75,7 @@ # it contains secrets. - name: Extract the root password from .my.cnf set_fact: - mysql_root_password_generated: "{{ mysql_root_cnf_file['content'] | b64decode | regex_findall('password=\"(.+)\"') | first }}" + mysql_root_password_written: "{{ mysql_root_cnf_file['content'] | b64decode | regex_findall('password=\"(.+)\"') | first }}" # Note: We do not use mysql_user for this operation, as it doesn't always update # the root password correctly. See: https://goo.gl/MSOejW @@ -69,7 +84,7 @@ shell: > mysql -u root -NBe 'ALTER USER "{{ mysql_root_username }}"@"{{ item }}" - IDENTIFIED WITH mysql_native_password BY "{{ mysql_root_password_generated }}"; FLUSH PRIVILEGES;' + IDENTIFIED WITH mysql_native_password BY "{{ mysql_root_password_written }}"; FLUSH PRIVILEGES;' with_items: "{{ mysql_root_hosts.stdout_lines|default([]) }}" when: > ((mysql_install_packages | bool) or mysql_root_password_update) @@ -80,7 +95,7 @@ - name: Update MySQL root password for localhost root account (< 5.7.x). shell: > mysql -NBe - 'SET PASSWORD FOR "{{ mysql_root_username }}"@"{{ item }}" = PASSWORD("{{ mysql_root_password_generated }}"); FLUSH PRIVILEGES;' + 'SET PASSWORD FOR "{{ mysql_root_username }}"@"{{ item }}" = PASSWORD("{{ mysql_root_password_written }}"); FLUSH PRIVILEGES;' with_items: "{{ mysql_root_hosts.stdout_lines|default([]) }}" when: > ((mysql_install_packages | bool) or mysql_root_password_update) From b6a2c4076fd34ff5f56ac1bdb712f97f72d4d529 Mon Sep 17 00:00:00 2001 From: Colan Schwartz <13228-colan@users.noreply.gitlab.com> Date: Sat, 9 Oct 2021 14:35:09 -0400 Subject: [PATCH 6/7] Issue #223: Don't fetch user password from config when it's not available/appropriate. --- tasks/secure-installation.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tasks/secure-installation.yml b/tasks/secure-installation.yml index e07eaf27..c8f2fa78 100644 --- a/tasks/secure-installation.yml +++ b/tasks/secure-installation.yml @@ -17,6 +17,7 @@ slurp: src: "{{ mysql_user_cnf_path }}" register: mysql_user_cnf_file + when: mysql_user_name != mysql_root_username # It would be cleaner to use the `ini` lookup plugin, but that only works # locally so we'd have to copy the file first, which we'd rather not do because @@ -24,6 +25,7 @@ - name: Extract the user password from .my.cnf set_fact: mysql_user_password_written: "{{ mysql_user_cnf_file['content'] | b64decode | regex_findall('password=\"(.+)\"') | first }}" + when: mysql_user_name != mysql_root_username - name: Ensure default user is present. mysql_user: From 9e251a0c5ad406509c57057206bb512b2ea36936 Mon Sep 17 00:00:00 2001 From: Colan Schwartz <13228-colan@users.noreply.gitlab.com> Date: Sat, 9 Oct 2021 14:46:39 -0400 Subject: [PATCH 7/7] Issue #223: Fixed some newly-introduced bad tests, essentially removing "is true". --- tasks/secure-installation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tasks/secure-installation.yml b/tasks/secure-installation.yml index c8f2fa78..51198874 100644 --- a/tasks/secure-installation.yml +++ b/tasks/secure-installation.yml @@ -91,7 +91,7 @@ when: > ((mysql_install_packages | bool) or mysql_root_password_update) and ('5.7.' in mysql_cli_version.stdout or '8.0.' in mysql_cli_version.stdout) - and (mysql_root_password_setting.changed is true) + and mysql_root_password_setting.changed # Set root password for MySQL < 5.7.x. - name: Update MySQL root password for localhost root account (< 5.7.x). @@ -102,7 +102,7 @@ when: > ((mysql_install_packages | bool) or mysql_root_password_update) and ('5.7.' not in mysql_cli_version.stdout and '8.0.' not in mysql_cli_version.stdout) - and (mysql_root_password_setting.changed is true) + and mysql_root_password_setting.changed - name: Get list of hosts for the anonymous user. command: mysql -NBe 'SELECT Host FROM mysql.user WHERE User = ""'