mirror of
https://github.com/ansible-collections/community.mysql.git
synced 2025-05-20 07:59:11 -07:00
Fix mysql_user on_new_username IndexError (#642)
* fix tuple indexerror when no accounts are found * Fix tests for update_password not executed * Add test for case where existing user have different password * lint to prevent warning about jinja templating in when clause * Refactor get_existing_authentication to return a list of all row found Previously we were returning only the first row found. We need to be able to see if there is a difference in the existing passwords. * Refactor host option to be optional This make it possible to use the same method from mysql_user to help update_password retrieve existing password for all account with the same username independently of their hostname. And from mysql_info to get the password of a specif user using WHERE user = '' AND host = '' * Add change log fragment * Add link to the PR in the change log * lint for ansible devel * Fix templating type error could not cconvert to bool with ansible devel * Revert changes made for ansible-devel that broke tests for Ansible 2.15 * Revert changes made for ansible-devel that broke tests * Cut unnecessary set, uniqueness is ensured by the group_by in the query * Cut auth plugin from returned values when multiple existing auths exists Discussed here: https://github.com/ansible-collections/community.mysql/pull/642/files#r1649720519 * fix convertion of list(dict) to list(tuple) * Fix test for empty password on MySQL 8+
This commit is contained in:
parent
1922e7154e
commit
33e8754c4e
8 changed files with 141 additions and 74 deletions
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
bugfixes:
|
||||||
|
|
||||||
|
- mysql_user - Fixed an IndexError in the update_password functionality introduced in PR https://github.com/ansible-collections/community.mysql/pull/580 and released in community.mysql 3.8.0. If you used this functionality, please avoid versions 3.8.0 to 3.9.0 (https://github.com/ansible-collections/community.mysql/pull/642).
|
||||||
|
- mysql_user - Added a warning to update_password's on_new_username option if multiple accounts with the same username but different passwords exist (https://github.com/ansible-collections/community.mysql/pull/642).
|
|
@ -95,8 +95,12 @@ def get_grants(cursor, user, host):
|
||||||
return grants.split(", ")
|
return grants.split(", ")
|
||||||
|
|
||||||
|
|
||||||
def get_existing_authentication(cursor, user, host):
|
def get_existing_authentication(cursor, user, host=None):
|
||||||
# Return the plugin and auth_string if there is exactly one distinct existing plugin and auth_string.
|
""" Return a list of dict containing the plugin and auth_string for the
|
||||||
|
specified username.
|
||||||
|
If hostname is provided, return only the information about this particular
|
||||||
|
account.
|
||||||
|
"""
|
||||||
cursor.execute("SELECT VERSION()")
|
cursor.execute("SELECT VERSION()")
|
||||||
srv_type = cursor.fetchone()
|
srv_type = cursor.fetchone()
|
||||||
# Mysql_info use a DictCursor so we must convert back to a list
|
# Mysql_info use a DictCursor so we must convert back to a list
|
||||||
|
@ -107,37 +111,50 @@ def get_existing_authentication(cursor, user, host):
|
||||||
if 'mariadb' in srv_type[0].lower():
|
if 'mariadb' in srv_type[0].lower():
|
||||||
# before MariaDB 10.2.19 and 10.3.11, "password" and "authentication_string" can differ
|
# before MariaDB 10.2.19 and 10.3.11, "password" and "authentication_string" can differ
|
||||||
# when using mysql_native_password
|
# when using mysql_native_password
|
||||||
cursor.execute("""select plugin, auth from (
|
if host:
|
||||||
select plugin, password as auth from mysql.user where user=%(user)s
|
cursor.execute("""select plugin, auth from (
|
||||||
and host=%(host)s
|
select plugin, password as auth from mysql.user where user=%(user)s
|
||||||
union select plugin, authentication_string as auth from mysql.user where user=%(user)s
|
and host=%(host)s
|
||||||
and host=%(host)s) x group by plugin, auth limit 2
|
union select plugin, authentication_string as auth from mysql.user where user=%(user)s
|
||||||
""", {'user': user, 'host': host})
|
and host=%(host)s) x group by plugin, auth
|
||||||
|
""", {'user': user, 'host': host})
|
||||||
|
else:
|
||||||
|
cursor.execute("""select plugin, auth from (
|
||||||
|
select plugin, password as auth from mysql.user where user=%(user)s
|
||||||
|
union select plugin, authentication_string as auth from mysql.user where user=%(user)s
|
||||||
|
) x group by plugin, auth
|
||||||
|
""", {'user': user})
|
||||||
else:
|
else:
|
||||||
cursor.execute("""select plugin, authentication_string as auth
|
if host:
|
||||||
from mysql.user where user=%(user)s and host=%(host)s
|
cursor.execute("""select plugin, authentication_string as auth
|
||||||
group by plugin, authentication_string limit 2""", {'user': user, 'host': host})
|
from mysql.user where user=%(user)s and host=%(host)s
|
||||||
|
group by plugin, authentication_string""", {'user': user, 'host': host})
|
||||||
|
else:
|
||||||
|
cursor.execute("""select plugin, authentication_string as auth
|
||||||
|
from mysql.user where user=%(user)s
|
||||||
|
group by plugin, authentication_string""", {'user': user})
|
||||||
|
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
# Mysql_info use a DictCursor so we must convert back to a list
|
if len(rows) == 0:
|
||||||
# otherwise we get KeyError 0
|
return []
|
||||||
if isinstance(rows, dict):
|
|
||||||
rows = list(rows.values())
|
|
||||||
|
|
||||||
# 'plugin_auth_string' contains the hash string. Must be removed in c.mysql 4.0
|
# Mysql_info use a DictCursor so we must convert list(dict)
|
||||||
# See https://github.com/ansible-collections/community.mysql/pull/629
|
# to list(tuple) otherwise we get KeyError 0
|
||||||
if isinstance(rows[0], tuple):
|
|
||||||
return {'plugin': rows[0][0],
|
|
||||||
'plugin_auth_string': rows[0][1],
|
|
||||||
'plugin_hash_string': rows[0][1]}
|
|
||||||
|
|
||||||
# 'plugin_auth_string' contains the hash string. Must be removed in c.mysql 4.0
|
|
||||||
# See https://github.com/ansible-collections/community.mysql/pull/629
|
|
||||||
if isinstance(rows[0], dict):
|
if isinstance(rows[0], dict):
|
||||||
return {'plugin': rows[0].get('plugin'),
|
rows = [tuple(row.values()) for row in rows]
|
||||||
'plugin_auth_string': rows[0].get('auth'),
|
|
||||||
'plugin_hash_string': rows[0].get('auth')}
|
existing_auth_list = []
|
||||||
return None
|
|
||||||
|
# 'plugin_auth_string' contains the hash string. Must be removed in c.mysql 4.0
|
||||||
|
# See https://github.com/ansible-collections/community.mysql/pull/629
|
||||||
|
for r in rows:
|
||||||
|
existing_auth_list.append({
|
||||||
|
'plugin': r[0],
|
||||||
|
'plugin_auth_string': r[1],
|
||||||
|
'plugin_hash_string': r[1]})
|
||||||
|
|
||||||
|
return existing_auth_list
|
||||||
|
|
||||||
|
|
||||||
def user_add(cursor, user, host, host_all, password, encrypted,
|
def user_add(cursor, user, host, host_all, password, encrypted,
|
||||||
|
@ -161,14 +178,24 @@ def user_add(cursor, user, host, host_all, password, encrypted,
|
||||||
|
|
||||||
mogrify = do_not_mogrify_requires if old_user_mgmt else mogrify_requires
|
mogrify = do_not_mogrify_requires if old_user_mgmt else mogrify_requires
|
||||||
|
|
||||||
|
# This is for update_password: on_new_username
|
||||||
used_existing_password = False
|
used_existing_password = False
|
||||||
if reuse_existing_password:
|
if reuse_existing_password:
|
||||||
existing_auth = get_existing_authentication(cursor, user, host)
|
existing_auth = get_existing_authentication(cursor, user)
|
||||||
if existing_auth:
|
if existing_auth:
|
||||||
plugin = existing_auth['plugin']
|
if len(existing_auth) != 1:
|
||||||
plugin_hash_string = existing_auth['plugin_hash_string']
|
module.warn("An account with the username %s has a different "
|
||||||
password = None
|
"password than the others existing accounts. Thus "
|
||||||
used_existing_password = True
|
"on_new_username can't decide which password to "
|
||||||
|
"reuse so it will use your provided password "
|
||||||
|
"instead. If no password is provided, the account "
|
||||||
|
"will have an empty password!" % user)
|
||||||
|
used_existing_password = False
|
||||||
|
else:
|
||||||
|
plugin_hash_string = existing_auth[0]['plugin_hash_string']
|
||||||
|
password = None
|
||||||
|
used_existing_password = True
|
||||||
|
plugin = existing_auth[0]['plugin'] # What if plugin differ?
|
||||||
if password and encrypted:
|
if password and encrypted:
|
||||||
if impl.supports_identified_by_password(cursor):
|
if impl.supports_identified_by_password(cursor):
|
||||||
query_with_args = "CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user, host, password)
|
query_with_args = "CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user, host, password)
|
||||||
|
|
|
@ -639,7 +639,7 @@ class MySQL_Info(object):
|
||||||
|
|
||||||
authentications = get_existing_authentication(self.cursor, user, host)
|
authentications = get_existing_authentication(self.cursor, user, host)
|
||||||
if authentications:
|
if authentications:
|
||||||
output_dict.update(authentications)
|
output_dict.update(authentications[0])
|
||||||
|
|
||||||
# TODO password_option
|
# TODO password_option
|
||||||
# TODO lock_option
|
# TODO lock_option
|
||||||
|
|
|
@ -295,3 +295,7 @@
|
||||||
- name: Mysql_user - test column case sensitive
|
- name: Mysql_user - test column case sensitive
|
||||||
ansible.builtin.import_tasks:
|
ansible.builtin.import_tasks:
|
||||||
file: test_column_case_sensitive.yml
|
file: test_column_case_sensitive.yml
|
||||||
|
|
||||||
|
- name: Mysql_user - test update_password
|
||||||
|
ansible.builtin.import_tasks:
|
||||||
|
file: test_update_password.yml
|
||||||
|
|
|
@ -127,3 +127,29 @@
|
||||||
update_password: on_create
|
update_password: on_create
|
||||||
- username: test3
|
- username: test3
|
||||||
update_password: on_new_username
|
update_password: on_new_username
|
||||||
|
|
||||||
|
# another new user, another new password and multiple existing users with
|
||||||
|
# varying passwords without providing a password
|
||||||
|
- name: update_password | Create account with on_new_username while omit password
|
||||||
|
community.mysql.mysql_user:
|
||||||
|
login_user: '{{ mysql_parameters.login_user }}'
|
||||||
|
login_password: '{{ mysql_parameters.login_password }}'
|
||||||
|
login_host: '{{ mysql_parameters.login_host }}'
|
||||||
|
login_port: '{{ mysql_parameters.login_port }}'
|
||||||
|
state: present
|
||||||
|
name: test3
|
||||||
|
host: '10.10.10.10'
|
||||||
|
update_password: on_new_username
|
||||||
|
|
||||||
|
- name: update_password | Assert create account with on_new_username while omit password produce empty auth string
|
||||||
|
ansible.builtin.command: >-
|
||||||
|
{{ mysql_command }} -BNe "SELECT user, host, plugin, authentication_string
|
||||||
|
FROM mysql.user where user='test3' and host='10.10.10.10'"
|
||||||
|
register: test3_info
|
||||||
|
changed_when: false
|
||||||
|
failed_when:
|
||||||
|
# MariaDB default plugin is mysql_native_password
|
||||||
|
- "'test3\t10.10.10.10\tmysql_native_password\t' != test3_info.stdout"
|
||||||
|
|
||||||
|
# MySQL 8+ default plugin is caching_sha2_password
|
||||||
|
- "'test3\t10.10.10.10\tcaching_sha2_password\t' != test3_info.stdout"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
- name: Utils | Assert user password | Apply update_password to {{ username }}
|
- name: Utils | Assert user password | Apply update_password to {{ username }}
|
||||||
mysql_user:
|
community.mysql.mysql_user:
|
||||||
login_user: '{{ mysql_parameters.login_user }}'
|
login_user: '{{ mysql_parameters.login_user }}'
|
||||||
login_password: '{{ mysql_parameters.login_password }}'
|
login_password: '{{ mysql_parameters.login_password }}'
|
||||||
login_host: '{{ mysql_parameters.login_host }}'
|
login_host: '{{ mysql_parameters.login_host }}'
|
||||||
|
@ -13,16 +13,17 @@
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- name: Utils | Assert user password | Assert a change occurred
|
- name: Utils | Assert user password | Assert a change occurred
|
||||||
assert:
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed | bool == {{ expect_change }} | bool"
|
- result.changed | bool == expect_change | bool
|
||||||
- "result.password_changed == {{ expect_password_change }}"
|
- result.password_changed == expect_password_change
|
||||||
|
|
||||||
- name: Utils | Assert user password | Query user {{ username }}
|
- name: Utils | Assert user password | Assert expect_hash is in user stdout for {{ username }}
|
||||||
command: "{{ mysql_command }} -BNe \"SELECT plugin, authentication_string FROM mysql.user where user='{{ username }}' and host='{{ host }}'\""
|
ansible.builtin.command: >-
|
||||||
|
{{ mysql_command }} -BNe "SELECT plugin, authentication_string
|
||||||
|
FROM mysql.user where user='{{ username }}' and host='{{ host }}'"
|
||||||
register: existing_user
|
register: existing_user
|
||||||
|
changed_when: false
|
||||||
- name: Utils | Assert user password | Assert expect_hash is in user stdout
|
failed_when: pattern not in existing_user.stdout_lines
|
||||||
assert:
|
vars:
|
||||||
that:
|
pattern: "mysql_native_password\t{{ expect_password_hash }}"
|
||||||
- "'mysql_native_password\t{{ expect_password_hash }}' in existing_user.stdout_lines"
|
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
---
|
---
|
||||||
- name: set fact tls_enabled
|
- name: set fact tls_enabled
|
||||||
command: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\""
|
ansible.builtin.command:
|
||||||
|
cmd: "{{ mysql_command }} \"-e SHOW VARIABLES LIKE 'have_ssl';\""
|
||||||
register: result
|
register: result
|
||||||
- set_fact:
|
|
||||||
|
- name: Set tls_enabled fact
|
||||||
|
ansible.builtin.set_fact:
|
||||||
tls_enabled: "{{ 'YES' in result.stdout | bool | default('false', true) }}"
|
tls_enabled: "{{ 'YES' in result.stdout | bool | default('false', true) }}"
|
||||||
|
|
||||||
- vars:
|
- vars:
|
||||||
|
@ -16,21 +19,21 @@
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: get server certificate
|
- name: get server certificate
|
||||||
copy:
|
ansible.builtin.copy:
|
||||||
content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'\") }}"
|
content: "{{ lookup('pipe', \"openssl s_client -starttls mysql -connect localhost:3307 -showcerts 2>/dev/null </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'\") }}"
|
||||||
dest: /tmp/cert.pem
|
dest: /tmp/cert.pem
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
- name: Drop mysql user if exists
|
- name: Drop mysql user if exists
|
||||||
mysql_user:
|
community.mysql.mysql_user:
|
||||||
<<: *mysql_params
|
<<: *mysql_params
|
||||||
name: '{{ user_name_1 }}'
|
name: '{{ user_name_1 }}'
|
||||||
host_all: true
|
host_all: true
|
||||||
state: absent
|
state: absent
|
||||||
ignore_errors: yes
|
ignore_errors: true
|
||||||
|
|
||||||
- name: create user with ssl requirement
|
- name: create user with ssl requirement
|
||||||
mysql_user:
|
community.mysql.mysql_user:
|
||||||
<<: *mysql_params
|
<<: *mysql_params
|
||||||
name: "{{ user_name_1 }}"
|
name: "{{ user_name_1 }}"
|
||||||
host: '%'
|
host: '%'
|
||||||
|
@ -40,7 +43,7 @@
|
||||||
SSL:
|
SSL:
|
||||||
|
|
||||||
- name: attempt connection with newly created user (expect failure)
|
- name: attempt connection with newly created user (expect failure)
|
||||||
mysql_variables:
|
community.mysql.mysql_variables:
|
||||||
variable: '{{ set_name }}'
|
variable: '{{ set_name }}'
|
||||||
login_user: '{{ user_name_1 }}'
|
login_user: '{{ user_name_1 }}'
|
||||||
login_password: '{{ user_password_1 }}'
|
login_password: '{{ user_password_1 }}'
|
||||||
|
@ -48,22 +51,24 @@
|
||||||
login_port: '{{ mysql_primary_port }}'
|
login_port: '{{ mysql_primary_port }}'
|
||||||
ca_cert: /tmp/cert.pem
|
ca_cert: /tmp/cert.pem
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: yes
|
ignore_errors: true
|
||||||
|
|
||||||
- assert:
|
- name: Assert that result is failed for pymysql
|
||||||
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- result is failed
|
- result is failed
|
||||||
when:
|
when:
|
||||||
- connector_name == 'pymysql'
|
- connector_name == 'pymysql'
|
||||||
|
|
||||||
- assert:
|
- name: Assert that result is success for mysqlclient
|
||||||
|
ansible.builtin.assert:
|
||||||
that:
|
that:
|
||||||
- result is succeeded
|
- result is succeeded
|
||||||
when:
|
when:
|
||||||
- connector_name != 'pymysql'
|
- connector_name != 'pymysql'
|
||||||
|
|
||||||
- name: attempt connection with newly created user ignoring hostname
|
- name: attempt connection with newly created user ignoring hostname
|
||||||
mysql_variables:
|
community.mysql.mysql_variables:
|
||||||
variable: '{{ set_name }}'
|
variable: '{{ set_name }}'
|
||||||
login_user: '{{ user_name_1 }}'
|
login_user: '{{ user_name_1 }}'
|
||||||
login_password: '{{ user_password_1 }}'
|
login_password: '{{ user_password_1 }}'
|
||||||
|
@ -72,14 +77,12 @@
|
||||||
ca_cert: /tmp/cert.pem
|
ca_cert: /tmp/cert.pem
|
||||||
check_hostname: no
|
check_hostname: no
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: yes
|
ignore_errors: true
|
||||||
|
failed_when:
|
||||||
- assert:
|
- result is failed or 'pymysql >= 0.7.11 is required' not in result.msg
|
||||||
that:
|
|
||||||
- result is succeeded or 'pymysql >= 0.7.11 is required' in result.msg
|
|
||||||
|
|
||||||
- name: Drop mysql user
|
- name: Drop mysql user
|
||||||
mysql_user:
|
community.mysql.mysql_user:
|
||||||
<<: *mysql_params
|
<<: *mysql_params
|
||||||
name: '{{ user_name_1 }}'
|
name: '{{ user_name_1 }}'
|
||||||
host_all: true
|
host_all: true
|
||||||
|
|
|
@ -47,8 +47,8 @@
|
||||||
# Verify mysql_variable successfully updates a variable (issue:4568)
|
# Verify mysql_variable successfully updates a variable (issue:4568)
|
||||||
#
|
#
|
||||||
- set_fact:
|
- set_fact:
|
||||||
set_name: 'delay_key_write'
|
set_name: 'delay_key_write'
|
||||||
set_value: 'ON'
|
set_value: 'ON'
|
||||||
|
|
||||||
- name: set mysql variable
|
- name: set mysql variable
|
||||||
mysql_variables:
|
mysql_variables:
|
||||||
|
@ -74,8 +74,8 @@
|
||||||
# Verify mysql_variable successfully updates a variable using single quotes
|
# Verify mysql_variable successfully updates a variable using single quotes
|
||||||
#
|
#
|
||||||
- set_fact:
|
- set_fact:
|
||||||
set_name: 'wait_timeout'
|
set_name: 'wait_timeout'
|
||||||
set_value: '300'
|
set_value: '300'
|
||||||
|
|
||||||
- name: set mysql variable to a temp value
|
- name: set mysql variable to a temp value
|
||||||
mysql_variables:
|
mysql_variables:
|
||||||
|
@ -105,8 +105,8 @@
|
||||||
# Verify mysql_variable successfully updates a variable using double quotes
|
# Verify mysql_variable successfully updates a variable using double quotes
|
||||||
#
|
#
|
||||||
- set_fact:
|
- set_fact:
|
||||||
set_name: "wait_timeout"
|
set_name: "wait_timeout"
|
||||||
set_value: "400"
|
set_value: "400"
|
||||||
|
|
||||||
- name: set mysql variable to a temp value
|
- name: set mysql variable to a temp value
|
||||||
mysql_variables:
|
mysql_variables:
|
||||||
|
@ -132,8 +132,8 @@
|
||||||
# Verify mysql_variable successfully updates a variable using no quotes
|
# Verify mysql_variable successfully updates a variable using no quotes
|
||||||
#
|
#
|
||||||
- set_fact:
|
- set_fact:
|
||||||
set_name: wait_timeout
|
set_name: wait_timeout
|
||||||
set_value: 500
|
set_value: 500
|
||||||
|
|
||||||
- name: set mysql variable to a temp value
|
- name: set mysql variable to a temp value
|
||||||
mysql_variables:
|
mysql_variables:
|
||||||
|
@ -251,8 +251,8 @@
|
||||||
# Verify mysql_variable works with the login_user and login_password parameters
|
# Verify mysql_variable works with the login_user and login_password parameters
|
||||||
#
|
#
|
||||||
- set_fact:
|
- set_fact:
|
||||||
set_name: wait_timeout
|
set_name: wait_timeout
|
||||||
set_value: 77
|
set_value: 77
|
||||||
|
|
||||||
- name: query mysql_variable using login_user and password_password
|
- name: query mysql_variable using login_user and password_password
|
||||||
mysql_variables:
|
mysql_variables:
|
||||||
|
@ -291,8 +291,8 @@
|
||||||
# Verify mysql_variable fails with an incorrect login_password parameter
|
# Verify mysql_variable fails with an incorrect login_password parameter
|
||||||
#
|
#
|
||||||
- set_fact:
|
- set_fact:
|
||||||
set_name: connect_timeout
|
set_name: connect_timeout
|
||||||
set_value: 10
|
set_value: 10
|
||||||
|
|
||||||
- name: query mysql_variable using incorrect login_password
|
- name: query mysql_variable using incorrect login_password
|
||||||
mysql_variables:
|
mysql_variables:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue