@ -105,6 +105,7 @@ notes:
author :
author :
- Jonathan Mainguy ( @Jmainguy )
- Jonathan Mainguy ( @Jmainguy )
- Benjamin Malynovytch ( @bmalynovytch )
extends_documentation_fragment : mysql
extends_documentation_fragment : mysql
'''
'''
@ -239,22 +240,25 @@ class InvalidPrivsError(Exception):
#
#
# User Authentication Management was change in MySQL 5.7
# User Authentication Management changed in MySQL 5.7 and MariaDB 10.2.0
# This is a generic check for if the server version is less than version 5.7
def use_old_user_mgmt ( cursor ) :
def server_version_check ( cursor ) :
cursor . execute ( " SELECT VERSION() " )
cursor . execute ( " SELECT VERSION() " )
result = cursor . fetchone ( )
result = cursor . fetchone ( )
version_str = result [ 0 ]
version_str = result [ 0 ]
version = version_str . split ( ' . ' )
version = version_str . split ( ' . ' )
# Currently we have no facility to handle new-style password update on
# mariadb and the old-style update continues to work
if ' mariadb ' in version_str . lower ( ) :
if ' mariadb ' in version_str . lower ( ) :
return True
# Prior to MariaDB 10.2
if int ( version [ 0 ] ) < = 5 and int ( version [ 1 ] ) < 7 :
if int ( version [ 0 ] ) * 1000 + int ( version [ 1 ] ) < 10002 :
return True
return True
else :
return False
else :
else :
return False
# Prior to MySQL 5.7
if int ( version [ 0 ] ) * 1000 + int ( version [ 1 ] ) < 5007 :
return True
else :
return False
def get_mode ( cursor ) :
def get_mode ( cursor ) :
@ -270,9 +274,9 @@ def get_mode(cursor):
def user_exists ( cursor , user , host , host_all ) :
def user_exists ( cursor , user , host , host_all ) :
if host_all :
if host_all :
cursor . execute ( " SELECT count(*) FROM user WHERE user = %s " , ( [ user ] ) )
cursor . execute ( " SELECT count(*) FROM mysql. user WHERE user = %s " , ( [ user ] ) )
else :
else :
cursor . execute ( " SELECT count(*) FROM user WHERE user = %s AND host = %s " , ( user , host ) )
cursor . execute ( " SELECT count(*) FROM mysql. user WHERE user = %s AND host = %s " , ( user , host ) )
count = cursor . fetchone ( )
count = cursor . fetchone ( )
return count [ 0 ] > 0
return count [ 0 ] > 0
@ -308,6 +312,7 @@ def is_hash(password):
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 , new_priv , append_privs , module ) :
changed = False
changed = False
msg = " User unchanged "
grant_option = False
grant_option = False
if host_all :
if host_all :
@ -319,41 +324,68 @@ def user_mod(cursor, user, host, host_all, password, encrypted, new_priv, append
# Handle clear text and hashed passwords.
# Handle clear text and hashed passwords.
if bool ( password ) :
if bool ( password ) :
# Determine what user management method server uses
# Determine what user management method server uses
old_user_mgmt = server_version_check ( cursor )
old_user_mgmt = use_old_user_mgmt ( cursor )
if old_user_mgmt :
# Get a list of valid columns in mysql.user table to check if Password and/or authentication_string exist
cursor . execute ( " SELECT password FROM user WHERE user = %s AND host = %s " , ( user , host ) )
cursor . execute ( """
else :
SELECT COLUMN_NAME FROM information_schema . COLUMNS
cursor . execute ( " SELECT authentication_string FROM user WHERE user = %s AND host = %s " , ( user , host ) )
WHERE TABLE_SCHEMA = ' mysql ' AND TABLE_NAME = ' user ' AND COLUMN_NAME IN ( ' Password ' , ' authentication_string ' )
current_pass_hash = cursor . fetchone ( )
ORDER BY COLUMN_NAME DESC LIMIT 1
""" )
colA = cursor . fetchone ( )
cursor . execute ( """
SELECT COLUMN_NAME FROM information_schema . COLUMNS
WHERE TABLE_SCHEMA = ' mysql ' AND TABLE_NAME = ' user ' AND COLUMN_NAME IN ( ' Password ' , ' authentication_string ' )
ORDER BY COLUMN_NAME ASC LIMIT 1
""" )
colB = cursor . fetchone ( )
# Select hash from either Password or authentication_string, depending which one exists and/or is filled
cursor . execute ( """
SELECT COALESCE (
CASE WHEN % s = ' ' THEN NULL ELSE % s END ,
CASE WHEN % s = ' ' THEN NULL ELSE % s END
)
FROM mysql . user WHERE user = % % s AND host = % % s
""" % (colA[0], colA[0], colB[0], colB[0]), (user, host))
current_pass_hash = cursor . fetchone ( ) [ 0 ]
if encrypted :
if encrypted :
encrypted_string = ( password )
encrypted_password = password
if is_hash ( password ) :
if not is_hash ( encrypted_password ) :
if current_pass_hash [ 0 ] != encrypted_string :
if module . check_mode :
return True
if old_user_mgmt :
cursor . execute ( " SET PASSWORD FOR %s @ %s = %s " , ( user , host , password ) )
else :
cursor . execute ( " ALTER USER %s @ %s IDENTIFIED WITH mysql_native_password AS %s " , ( user , host , password ) )
changed = True
else :
module . fail_json ( msg = " encrypted was specified however it does not appear to be a valid hash expecting: *SHA1(SHA1(your_password)) " )
module . fail_json ( msg = " encrypted was specified however it does not appear to be a valid hash expecting: *SHA1(SHA1(your_password)) " )
else :
else :
if old_user_mgmt :
if old_user_mgmt :
cursor . execute ( " SELECT PASSWORD( %s ) " , ( password , ) )
cursor . execute ( " SELECT PASSWORD( %s ) " , ( password , ) )
else :
else :
cursor . execute ( " SELECT CONCAT( ' * ' , UCASE(SHA1(UNHEX(SHA1( %s ))))) " , ( password , ) )
cursor . execute ( " SELECT CONCAT( ' * ' , UCASE(SHA1(UNHEX(SHA1( %s ))))) " , ( password , ) )
new_pass_hash = cursor . fetchone ( )
encrypted_password = cursor . fetchone ( ) [ 0 ]
if current_pass_hash [ 0 ] != new_pass_hash [ 0 ] :
if module . check_mode :
if current_pass_hash != encrypted_password :
return True
msg = " Password updated "
if old_user_mgmt :
if module . check_mode :
cursor . execute ( " SET PASSWORD FOR %s @ %s = PASSWORD( %s ) " , ( user , host , password ) )
return ( True , msg )
else :
if old_user_mgmt :
cursor . execute ( " ALTER USER %s @ %s IDENTIFIED WITH mysql_native_password BY %s " , ( user , host , password ) )
cursor . execute ( " SET PASSWORD FOR %s @ %s = %s " , ( user , host , encrypted_password ) )
changed = True
msg = " Password updated (old style) "
else :
try :
cursor . execute ( " ALTER USER %s @ %s IDENTIFIED WITH mysql_native_password AS %s " , ( user , host , encrypted_password ) )
msg = " Password updated (new style) "
except ( mysql_driver . Error ) as e :
# https://stackoverflow.com/questions/51600000/authentication-string-of-root-user-on-mysql
# Replacing empty root password with new authentication mechanisms fails with error 1396
if e . args [ 0 ] == 1396 :
cursor . execute (
" UPDATE user SET plugin = %s , authentication_string = %s , Password = ' ' WHERE User = %s AND Host = %s " ,
( ' mysql_native_password ' , encrypted_password , user , host )
)
cursor . execute ( " FLUSH PRIVILEGES " )
msg = " Password forced update "
else :
raise e
changed = True
# Handle privileges
# Handle privileges
if new_priv is not None :
if new_priv is not None :
@ -367,8 +399,9 @@ def user_mod(cursor, user, host, host_all, password, encrypted, new_priv, append
grant_option = True
grant_option = True
if db_table not in new_priv :
if db_table not in new_priv :
if user != " root " and " PROXY " not in priv and not append_privs :
if user != " root " and " PROXY " not in priv and not append_privs :
msg = " Privileges updated "
if module . check_mode :
if module . check_mode :
return True
return ( True , msg )
privileges_revoke ( cursor , user , host , db_table , priv , grant_option )
privileges_revoke ( cursor , user , host , db_table , priv , grant_option )
changed = True
changed = True
@ -376,8 +409,9 @@ def user_mod(cursor, user, host, host_all, password, encrypted, new_priv, append
# we can perform a straight grant operation.
# we can perform a straight grant operation.
for db_table , priv in iteritems ( new_priv ) :
for db_table , priv in iteritems ( new_priv ) :
if db_table not in curr_priv :
if db_table not in curr_priv :
msg = " New privileges granted "
if module . check_mode :
if module . check_mode :
return True
return ( True , msg )
privileges_grant ( cursor , user , host , db_table , priv )
privileges_grant ( cursor , user , host , db_table , priv )
changed = True
changed = True
@ -387,14 +421,15 @@ def user_mod(cursor, user, host, host_all, password, encrypted, new_priv, append
for db_table in db_table_intersect :
for db_table in db_table_intersect :
priv_diff = set ( new_priv [ db_table ] ) ^ set ( curr_priv [ db_table ] )
priv_diff = set ( new_priv [ db_table ] ) ^ set ( curr_priv [ db_table ] )
if len ( priv_diff ) > 0 :
if len ( priv_diff ) > 0 :
msg = " Privileges updated "
if module . check_mode :
if module . check_mode :
return True
return ( True , msg )
if not append_privs :
if not append_privs :
privileges_revoke ( cursor , user , host , db_table , curr_priv [ db_table ] , grant_option )
privileges_revoke ( cursor , user , host , db_table , curr_priv [ db_table ] , grant_option )
privileges_grant ( cursor , user , host , db_table , new_priv [ db_table ] )
privileges_grant ( cursor , user , host , db_table , new_priv [ db_table ] )
changed = True
changed = True
return changed
return ( changed , msg )
def user_delete ( cursor , user , host , host_all , check_mode ) :
def user_delete ( cursor , user , host , host_all , check_mode ) :
@ -444,7 +479,7 @@ def privileges_get(cursor, user, host):
return x
return x
for grant in grants :
for grant in grants :
res = re . match ( """ GRANT (.+) ON (.+) TO ([ ' ` " ]).* \\ 3@([ ' ` " ]).* \\ 4( IDENTIFIED BY PASSWORD ([ ' ` " ]).+ \ \6 )? ?(.*)""" , grant [ 0 ] )
res = re . match ( """ GRANT (.+) ON (.+) TO ([ ' ` " ]).* \\ 3@([ ' ` " ]).* \\ 4( IDENTIFIED BY PASSWORD ([ ' ` " ]).+ \ 5 )? ?(.*)""" , grant [ 0 ] )
if res is None :
if res is None :
raise InvalidPrivsError ( ' unable to parse the MySQL grant string: %s ' % grant [ 0 ] )
raise InvalidPrivsError ( ' unable to parse the MySQL grant string: %s ' % grant [ 0 ] )
privileges = res . group ( 1 ) . split ( " , " )
privileges = res . group ( 1 ) . split ( " , " )
@ -593,7 +628,7 @@ def main():
ssl_cert = module . params [ " client_cert " ]
ssl_cert = module . params [ " client_cert " ]
ssl_key = module . params [ " client_key " ]
ssl_key = module . params [ " client_key " ]
ssl_ca = module . params [ " ca_cert " ]
ssl_ca = module . params [ " ca_cert " ]
db = ' mysql '
db = ' '
sql_log_bin = module . params [ " sql_log_bin " ]
sql_log_bin = module . params [ " sql_log_bin " ]
if mysql_driver is None :
if mysql_driver is None :
@ -632,9 +667,9 @@ 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 = user_mod ( cursor , user , host , host_all , password , encrypted , priv , append_privs , module )
changed , msg = user_mod ( cursor , user , host , host_all , password , encrypted , priv , append_privs , module )
else :
else :
changed = user_mod ( cursor , user , host , host_all , None , encrypted , priv , append_privs , module )
changed , msg = user_mod ( cursor , user , host , host_all , None , encrypted , 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 ) )
@ -643,14 +678,19 @@ def main():
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 , priv , module . check_mode )
if changed :
msg = " User added "
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 ) )
elif state == " absent " :
elif state == " absent " :
if user_exists ( cursor , user , host , host_all ) :
if user_exists ( cursor , user , host , host_all ) :
changed = user_delete ( cursor , user , host , host_all , module . check_mode )
changed = user_delete ( cursor , user , host , host_all , module . check_mode )
msg = " User deleted "
else :
else :
changed = False
changed = False
module . exit_json ( changed = changed , user = user )
msg = " User doesn ' t exist "
module . exit_json ( changed = changed , user = user , msg = msg )
if __name__ == ' __main__ ' :
if __name__ == ' __main__ ' :