mysql_user: support for MySQL plugin authentication (#65789)

* mysql_user: support for MySQL plugin authentication

* add changelog fragment
This commit is contained in:
Andrew Klychkov 2019-12-13 17:23:06 +03:00 committed by ansibot
parent 2ef47148cd
commit a68269660e
2 changed files with 84 additions and 5 deletions

View file

@ -0,0 +1,4 @@
minor_changes:
- mysql_user - add ``plugin`` parameter (https://github.com/ansible/ansible/pull/44267).
- mysql_user - add ``plugin_hash_string`` parameter (https://github.com/ansible/ansible/pull/44267).
- mysql_user - add ``plugin_auth_string`` parameter (https://github.com/ansible/ansible/pull/44267).

View file

@ -95,6 +95,22 @@ options:
choices: [ always, on_create ] choices: [ always, on_create ]
default: always default: always
version_added: "2.0" version_added: "2.0"
plugin:
description:
- User's plugin to authenticate (``CREATE USER user IDENTIFIED WITH plugin``).
type: str
version_added: '2.10'
plugin_hash_string:
description:
- User's plugin hash string (``CREATE USER user IDENTIFIED WITH plugin AS plugin_hash_string``).
type: str
version_added: '2.10'
plugin_auth_string:
description:
- User's plugin auth_string (``CREATE USER user IDENTIFIED WITH plugin BY plugin_auth_string``).
type: str
version_added: '2.10'
notes: notes:
- "MySQL server installs with default login_user of 'root' and no password. To secure this user - "MySQL server installs with default login_user of 'root' and no password. To secure this user
as part of an idempotent playbook, you must create at least two tasks: the first must change the root user's password, as part of an idempotent playbook, you must create at least two tasks: the first must change the root user's password,
@ -112,6 +128,7 @@ seealso:
author: author:
- Jonathan Mainguy (@Jmainguy) - Jonathan Mainguy (@Jmainguy)
- Benjamin Malynovytch (@bmalynovytch) - Benjamin Malynovytch (@bmalynovytch)
- Lukasz Tomaszkiewicz (@tomaszkiewicz)
extends_documentation_fragment: mysql extends_documentation_fragment: mysql
''' '''
@ -202,6 +219,14 @@ EXAMPLES = r'''
state: present state: present
sql_log_bin: no sql_log_bin: no
- name: Create user 'bob' authenticated with plugin 'AWSAuthenticationPlugin'
mysql_user:
name: bob
plugin: AWSAuthenticationPlugin
plugin_hash_string: RDS
priv: '*.*:ALL'
state: present
# Example .my.cnf file for setting the root password # Example .my.cnf file for setting the root password
# [client] # [client]
# user=root # user=root
@ -289,7 +314,8 @@ def user_exists(cursor, user, host, host_all):
return count[0] > 0 return count[0] > 0
def user_add(cursor, user, host, host_all, password, encrypted, new_priv, check_mode): def user_add(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string, new_priv, check_mode):
# we cannot create users without a proper hostname # we cannot create users without a proper hostname
if host_all: if host_all:
return False return False
@ -301,6 +327,12 @@ def user_add(cursor, user, host, host_all, password, encrypted, new_priv, check_
cursor.execute("CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user, host, password)) cursor.execute("CREATE USER %s@%s IDENTIFIED BY PASSWORD %s", (user, host, password))
elif password and not encrypted: elif password and not encrypted:
cursor.execute("CREATE USER %s@%s IDENTIFIED BY %s", (user, host, password)) cursor.execute("CREATE USER %s@%s IDENTIFIED BY %s", (user, host, password))
elif plugin and plugin_hash_string:
cursor.execute("CREATE USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string))
elif plugin and plugin_auth_string:
cursor.execute("CREATE USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string))
elif plugin:
cursor.execute("CREATE USER %s@%s IDENTIFIED WITH %s", (user, host, plugin))
else: else:
cursor.execute("CREATE USER %s@%s", (user, host)) cursor.execute("CREATE USER %s@%s", (user, host))
if new_priv is not None: if new_priv is not None:
@ -317,7 +349,8 @@ def is_hash(password):
return ishash return ishash
def user_mod(cursor, user, host, host_all, password, encrypted, new_priv, append_privs, module): def user_mod(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string, new_priv, append_privs, module):
changed = False changed = False
msg = "User unchanged" msg = "User unchanged"
grant_option = False grant_option = False
@ -394,6 +427,36 @@ def user_mod(cursor, user, host, host_all, password, encrypted, new_priv, append
raise e raise e
changed = True changed = True
# Handle plugin authentication
if plugin:
cursor.execute("SELECT plugin, authentication_string FROM mysql.user "
"WHERE user = %s AND host = %s", (user, host))
current_plugin = cursor.fetchone()
update = False
if current_plugin[0] != plugin:
update = True
if plugin_hash_string and current_plugin[1] != plugin_hash_string:
update = True
if plugin_auth_string and current_plugin[1] != plugin_auth_string:
# this case can cause more updates than expected,
# as plugin can hash auth_string in any way it wants
# and there's no way to figure it out for
# a check, so I prefer to update more often than never
update = True
if update:
if plugin_hash_string:
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH %s AS %s", (user, host, plugin, plugin_hash_string))
elif plugin_auth_string:
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH %s BY %s", (user, host, plugin, plugin_auth_string))
else:
cursor.execute("ALTER USER %s@%s IDENTIFIED WITH %s", (user, host, plugin))
changed = True
# Handle privileges # Handle privileges
if new_priv is not None: if new_priv is not None:
curr_priv = privileges_get(cursor, user, host) curr_priv = privileges_get(cursor, user, host)
@ -615,6 +678,9 @@ def main():
client_cert=dict(type='path', aliases=['ssl_cert']), client_cert=dict(type='path', aliases=['ssl_cert']),
client_key=dict(type='path', aliases=['ssl_key']), client_key=dict(type='path', aliases=['ssl_key']),
ca_cert=dict(type='path', aliases=['ssl_ca']), ca_cert=dict(type='path', aliases=['ssl_ca']),
plugin=dict(default=None, type='str'),
plugin_hash_string=dict(default=None, type='str'),
plugin_auth_string=dict(default=None, type='str'),
), ),
supports_check_mode=True, supports_check_mode=True,
) )
@ -637,6 +703,9 @@ def main():
ssl_ca = module.params["ca_cert"] ssl_ca = module.params["ca_cert"]
db = '' db = ''
sql_log_bin = module.params["sql_log_bin"] sql_log_bin = module.params["sql_log_bin"]
plugin = module.params["plugin"]
plugin_hash_string = module.params["plugin_hash_string"]
plugin_auth_string = module.params["plugin_auth_string"]
if mysql_driver is None: if mysql_driver is None:
module.fail_json(msg=mysql_driver_fail_msg) module.fail_json(msg=mysql_driver_fail_msg)
@ -674,9 +743,13 @@ def main():
if user_exists(cursor, user, host, host_all): if user_exists(cursor, user, host, host_all):
try: try:
if update_password == 'always': if update_password == 'always':
changed, msg = user_mod(cursor, user, host, host_all, password, encrypted, priv, append_privs, module) changed, msg = user_mod(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string,
priv, append_privs, module)
else: else:
changed, msg = user_mod(cursor, user, host, host_all, None, encrypted, priv, append_privs, module) changed, msg = user_mod(cursor, user, host, host_all, None, encrypted,
plugin, plugin_hash_string, plugin_auth_string,
priv, append_privs, module)
except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e: except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e:
module.fail_json(msg=to_native(e)) module.fail_json(msg=to_native(e))
@ -684,7 +757,9 @@ def main():
if host_all: if host_all:
module.fail_json(msg="host_all parameter cannot be used when adding a user") module.fail_json(msg="host_all parameter cannot be used when adding a user")
try: try:
changed = user_add(cursor, user, host, host_all, password, encrypted, priv, module.check_mode) changed = user_add(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string,
priv, module.check_mode)
if changed: if changed:
msg = "User added" msg = "User added"