mirror of
https://github.com/ansible-collections/community.mysql.git
synced 2025-04-08 11:40:33 -07:00
Merge f43676cb1f
into a5ee4b3d1a
This commit is contained in:
commit
cf06d1de28
8 changed files with 597 additions and 20 deletions
2
changelogs/fragments/49-mysql_user_login_tracking.yml
Normal file
2
changelogs/fragments/49-mysql_user_login_tracking.yml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
minor_changes:
|
||||||
|
- mysql_user - add the ``account_locking`` options to support login attempt tracking and account locking feature (https://github.com/ansible-collections/community.mysql/issues/49).
|
|
@ -116,6 +116,27 @@ options:
|
||||||
- Used when I(state=present), ignored otherwise.
|
- Used when I(state=present), ignored otherwise.
|
||||||
type: dict
|
type: dict
|
||||||
version_added: '0.1.0'
|
version_added: '0.1.0'
|
||||||
|
account_locking:
|
||||||
|
description:
|
||||||
|
- Configure user accounts such that too many consecutive login failures cause temporary account locking. Provided since MySQL 8.0.19.
|
||||||
|
- "Available options are C(FAILED_LOGIN_ATTEMPTS: num), C(PASSWORD_LOCK_TIME: num | UNBOUNDED)."
|
||||||
|
- Used when I(state=present) and target server is MySQL >= 8.0.19, ignored otherwise.
|
||||||
|
- U(https://dev.mysql.com/doc/refman/8.0/en/password-management.html#failed-login-tracking).
|
||||||
|
type: dict
|
||||||
|
suboptions:
|
||||||
|
FAILED_LOGIN_ATTEMPTS:
|
||||||
|
description:
|
||||||
|
- Number of failed login attempts before the user account is locked.
|
||||||
|
- Permitted values are in the range from 0 to 32767.
|
||||||
|
- A value of 0 disables the option.
|
||||||
|
type: int
|
||||||
|
PASSWORD_LOCK_TIME:
|
||||||
|
description:
|
||||||
|
- Number of days the account stays locked after the FAILED_LOGIN_ATTEMPTS threshold is exceeded.
|
||||||
|
- Permitted values are in the range from 0 to 32767, or the string ``UNBOUNDED``
|
||||||
|
- A value of 0 disables the option.
|
||||||
|
- A value of ``UNBOUNDED`` permanently locks the account until it's administratively unlocked.
|
||||||
|
version_added: '1.2.0'
|
||||||
|
|
||||||
notes:
|
notes:
|
||||||
- "MySQL server installs with default I(login_user) of C(root) and no password.
|
- "MySQL server installs with default I(login_user) of C(root) and no password.
|
||||||
|
@ -139,6 +160,7 @@ author:
|
||||||
- Jonathan Mainguy (@Jmainguy)
|
- Jonathan Mainguy (@Jmainguy)
|
||||||
- Benjamin Malynovytch (@bmalynovytch)
|
- Benjamin Malynovytch (@bmalynovytch)
|
||||||
- Lukasz Tomaszkiewicz (@tomaszkiewicz)
|
- Lukasz Tomaszkiewicz (@tomaszkiewicz)
|
||||||
|
- Jorge Rodriguez (@Jorge-Rodriguez)
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- community.mysql.mysql
|
- community.mysql.mysql
|
||||||
|
|
||||||
|
@ -188,6 +210,22 @@ EXAMPLES = r'''
|
||||||
'db1.*': 'ALL,GRANT'
|
'db1.*': 'ALL,GRANT'
|
||||||
'db2.*': 'ALL,GRANT'
|
'db2.*': 'ALL,GRANT'
|
||||||
|
|
||||||
|
- name: Create user with password and locking such that the account locks after three failed attempts
|
||||||
|
community.mysql.mysql_user:
|
||||||
|
name: bob
|
||||||
|
password: 12345
|
||||||
|
account_locking:
|
||||||
|
FAILED_LOGIN_ATTEMPTS: 3
|
||||||
|
PASSWORD_LOCK_TIME: UNBOUNDED
|
||||||
|
|
||||||
|
- name: Create user with password and locking such that the account locks for 5 days after three failed attempts
|
||||||
|
community.mysql.mysql_user:
|
||||||
|
name: bob
|
||||||
|
password: 12345
|
||||||
|
account_locking:
|
||||||
|
FAILED_LOGIN_ATTEMPTS: 3
|
||||||
|
PASSWORD_LOCK_TIME: 5
|
||||||
|
|
||||||
# Note that REQUIRESSL is a special privilege that should only apply to *.* by itself.
|
# Note that REQUIRESSL is a special privilege that should only apply to *.* by itself.
|
||||||
# Setting this privilege in this manner is supported for backwards compatibility only.
|
# Setting this privilege in this manner is supported for backwards compatibility only.
|
||||||
# Use 'tls_requires' instead.
|
# Use 'tls_requires' instead.
|
||||||
|
@ -217,7 +255,14 @@ EXAMPLES = r'''
|
||||||
name: bob
|
name: bob
|
||||||
tls_requires:
|
tls_requires:
|
||||||
|
|
||||||
- name: Ensure no user named 'sally'@'localhost' exists, also passing in the auth credentials
|
- name: Create user with enabled loging tracking.
|
||||||
|
community.mysql.mysql_user:
|
||||||
|
name: bob
|
||||||
|
account_locking:
|
||||||
|
PASSWORD_LOCK_TIME: 2
|
||||||
|
FAILED_LOGIN_ATTEMPTS: 5
|
||||||
|
|
||||||
|
- name: Ensure no user named 'sally'@'localhost' exists, also passing in the auth credentials.
|
||||||
community.mysql.mysql_user:
|
community.mysql.mysql_user:
|
||||||
login_user: root
|
login_user: root
|
||||||
login_password: 123456
|
login_password: 123456
|
||||||
|
@ -387,6 +432,57 @@ def supports_identified_by_password(cursor):
|
||||||
return LooseVersion(version_str) < LooseVersion('8')
|
return LooseVersion(version_str) < LooseVersion('8')
|
||||||
|
|
||||||
|
|
||||||
|
def validate_account_locking(cursor, account_locking, module):
|
||||||
|
cursor.execute("SELECT VERSION()")
|
||||||
|
result = cursor.fetchone()
|
||||||
|
version_str = result[0]
|
||||||
|
version = version_str.split('-')[0].split('.')
|
||||||
|
|
||||||
|
locking = {}
|
||||||
|
|
||||||
|
if 'mariadb' in version_str.lower():
|
||||||
|
module.warn("MariaDB does not support this manner of account locking. Use the MAX_PASSWORD_ERRORS server variable instead.")
|
||||||
|
module.warn("Account locking settings are being ignored.")
|
||||||
|
else:
|
||||||
|
if int(version[0]) * 1000 + int(version[2]) < 8019:
|
||||||
|
module.warn("MySQL is too old to support this manner of account locking.")
|
||||||
|
module.warn("Account locking settings are being ignored.")
|
||||||
|
else:
|
||||||
|
if account_locking is not None:
|
||||||
|
locking = {
|
||||||
|
"FAILED_LOGIN_ATTEMPTS": str(account_locking.get("FAILED_LOGIN_ATTEMPTS", 0)),
|
||||||
|
"PASSWORD_LOCK_TIME": str(account_locking.get("PASSWORD_LOCK_TIME", 0))
|
||||||
|
}
|
||||||
|
if any([int(value) < 0 or int(value) > 32767 for value in locking.values() if re.match("[-+]?\\d+$", value)]):
|
||||||
|
module.fail_json(msg="Account locking values are out of the valid range (0-32767)")
|
||||||
|
if ("PASSWORD_LOCK_TIME" in locking.keys()
|
||||||
|
and not re.match("[-+]?\\d+$", locking.get("PASSWORD_LOCK_TIME"))
|
||||||
|
and locking.get("PASSWORD_LOCK_TIME") != "UNBOUNDED"):
|
||||||
|
module.fail_json(msg="PASSWORD_LOCK_TIME must be an integer between 0 and 32767 or 'UNBOUNDED'")
|
||||||
|
return locking
|
||||||
|
|
||||||
|
|
||||||
|
def get_account_locking(cursor, user, host):
|
||||||
|
cursor.execute("SELECT VERSION()")
|
||||||
|
result = cursor.fetchone()
|
||||||
|
version_str = result[0]
|
||||||
|
version = version_str.split('-')[0].split('.')
|
||||||
|
|
||||||
|
locking = {}
|
||||||
|
|
||||||
|
if 'mariadb' in version_str.lower() or int(version[0]) * 1000 + int(version[2]) < 8019:
|
||||||
|
return locking
|
||||||
|
|
||||||
|
cursor.execute("SHOW CREATE USER %s@%s", (user, host))
|
||||||
|
result = cursor.fetchone()
|
||||||
|
|
||||||
|
for setting in ('FAILED_LOGIN_ATTEMPTS', 'PASSWORD_LOCK_TIME'):
|
||||||
|
match = re.search("%s (\\d+|UNBOUNDED)" % setting, result[0])
|
||||||
|
if match:
|
||||||
|
locking[setting] = match.groups()[0]
|
||||||
|
return locking
|
||||||
|
|
||||||
|
|
||||||
def get_mode(cursor):
|
def get_mode(cursor):
|
||||||
cursor.execute('SELECT @@GLOBAL.sql_mode')
|
cursor.execute('SELECT @@GLOBAL.sql_mode')
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
|
@ -427,7 +523,7 @@ def sanitize_requires(tls_requires):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def mogrify_requires(query, params, tls_requires):
|
def mogrify_requires(query, params, tls_requires, account_locking):
|
||||||
if tls_requires:
|
if tls_requires:
|
||||||
if isinstance(tls_requires, dict):
|
if isinstance(tls_requires, dict):
|
||||||
k, v = zip(*tls_requires.items())
|
k, v = zip(*tls_requires.items())
|
||||||
|
@ -436,10 +532,17 @@ def mogrify_requires(query, params, tls_requires):
|
||||||
else:
|
else:
|
||||||
requires_query = tls_requires
|
requires_query = tls_requires
|
||||||
query = " REQUIRE ".join((query, requires_query))
|
query = " REQUIRE ".join((query, requires_query))
|
||||||
|
return mogrify_account_locking(query, params, account_locking)
|
||||||
|
|
||||||
|
|
||||||
|
def do_not_mogrify_requires(query, params, tls_requires, account_locking):
|
||||||
return query, params
|
return query, params
|
||||||
|
|
||||||
|
|
||||||
def do_not_mogrify_requires(query, params, tls_requires):
|
def mogrify_account_locking(query, params, account_locking):
|
||||||
|
if account_locking:
|
||||||
|
for k, v in account_locking.items():
|
||||||
|
query = ' '.join((query, k, str(v)))
|
||||||
return query, params
|
return query, params
|
||||||
|
|
||||||
|
|
||||||
|
@ -478,11 +581,13 @@ def get_grants(cursor, user, host):
|
||||||
|
|
||||||
def user_add(cursor, user, host, host_all, password, encrypted,
|
def user_add(cursor, user, host, host_all, password, encrypted,
|
||||||
plugin, plugin_hash_string, plugin_auth_string, new_priv,
|
plugin, plugin_hash_string, plugin_auth_string, new_priv,
|
||||||
tls_requires, check_mode):
|
tls_requires, account_locking, check_mode, module):
|
||||||
# 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
|
||||||
|
|
||||||
|
locking = validate_account_locking(cursor, account_locking, module)
|
||||||
|
|
||||||
if check_mode:
|
if check_mode:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -512,7 +617,7 @@ def user_add(cursor, user, host, host_all, password, encrypted,
|
||||||
else:
|
else:
|
||||||
query_with_args = "CREATE USER %s@%s", (user, host)
|
query_with_args = "CREATE USER %s@%s", (user, host)
|
||||||
|
|
||||||
query_with_args_and_tls_requires = query_with_args + (tls_requires,)
|
query_with_args_and_tls_requires = query_with_args + (tls_requires, locking)
|
||||||
cursor.execute(*mogrify(*query_with_args_and_tls_requires))
|
cursor.execute(*mogrify(*query_with_args_and_tls_requires))
|
||||||
|
|
||||||
if new_priv is not None:
|
if new_priv is not None:
|
||||||
|
@ -533,7 +638,7 @@ def is_hash(password):
|
||||||
|
|
||||||
def user_mod(cursor, user, host, host_all, password, encrypted,
|
def user_mod(cursor, user, host, host_all, password, encrypted,
|
||||||
plugin, plugin_hash_string, plugin_auth_string, new_priv,
|
plugin, plugin_hash_string, plugin_auth_string, new_priv,
|
||||||
append_privs, tls_requires, module):
|
append_privs, tls_requires, account_locking, module):
|
||||||
changed = False
|
changed = False
|
||||||
msg = "User unchanged"
|
msg = "User unchanged"
|
||||||
grant_option = False
|
grant_option = False
|
||||||
|
@ -541,6 +646,8 @@ def user_mod(cursor, user, host, host_all, password, encrypted,
|
||||||
# Determine what user management method server uses
|
# Determine what user management method server uses
|
||||||
old_user_mgmt = use_old_user_mgmt(cursor)
|
old_user_mgmt = use_old_user_mgmt(cursor)
|
||||||
|
|
||||||
|
locking = validate_account_locking(cursor, account_locking, module)
|
||||||
|
|
||||||
if host_all:
|
if host_all:
|
||||||
hostnames = user_get_hostnames(cursor, user)
|
hostnames = user_get_hostnames(cursor, user)
|
||||||
else:
|
else:
|
||||||
|
@ -707,7 +814,7 @@ def user_mod(cursor, user, host, host_all, password, encrypted,
|
||||||
|
|
||||||
if tls_requires is not None:
|
if tls_requires is not None:
|
||||||
query = " ".join((pre_query, "%s@%s"))
|
query = " ".join((pre_query, "%s@%s"))
|
||||||
query_with_args = mogrify_requires(query, (user, host), tls_requires)
|
query_with_args = mogrify_requires(query, (user, host), tls_requires, locking)
|
||||||
else:
|
else:
|
||||||
query = " ".join((pre_query, "%s@%s REQUIRE NONE"))
|
query = " ".join((pre_query, "%s@%s REQUIRE NONE"))
|
||||||
query_with_args = query, (user, host)
|
query_with_args = query, (user, host)
|
||||||
|
@ -715,6 +822,17 @@ def user_mod(cursor, user, host, host_all, password, encrypted,
|
||||||
cursor.execute(*query_with_args)
|
cursor.execute(*query_with_args)
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
|
# Handle Account locking
|
||||||
|
locking = validate_account_locking(cursor, account_locking, module)
|
||||||
|
current_locking = get_account_locking(cursor, user, host)
|
||||||
|
clear_locking = dict((x, y) for x, y in locking.items() if y != '0')
|
||||||
|
if current_locking != clear_locking:
|
||||||
|
msg = "Account locking updated"
|
||||||
|
if module.check_mode:
|
||||||
|
return (True, msg)
|
||||||
|
cursor.execute(*mogrify_account_locking("ALTER USER %s@%s", (user, host), locking))
|
||||||
|
changed = True
|
||||||
|
|
||||||
return (changed, msg)
|
return (changed, msg)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1158,6 +1276,7 @@ def main():
|
||||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||||
priv=dict(type='raw'),
|
priv=dict(type='raw'),
|
||||||
tls_requires=dict(type='dict'),
|
tls_requires=dict(type='dict'),
|
||||||
|
account_locking=dict(type='dict', default={}),
|
||||||
append_privs=dict(type='bool', default=False),
|
append_privs=dict(type='bool', default=False),
|
||||||
check_implicit_admin=dict(type='bool', default=False),
|
check_implicit_admin=dict(type='bool', default=False),
|
||||||
update_password=dict(type='str', default='always', choices=['always', 'on_create'], no_log=False),
|
update_password=dict(type='str', default='always', choices=['always', 'on_create'], no_log=False),
|
||||||
|
@ -1181,6 +1300,7 @@ def main():
|
||||||
state = module.params["state"]
|
state = module.params["state"]
|
||||||
priv = module.params["priv"]
|
priv = module.params["priv"]
|
||||||
tls_requires = sanitize_requires(module.params["tls_requires"])
|
tls_requires = sanitize_requires(module.params["tls_requires"])
|
||||||
|
account_locking = module.params['account_locking']
|
||||||
check_implicit_admin = module.params["check_implicit_admin"]
|
check_implicit_admin = module.params["check_implicit_admin"]
|
||||||
connect_timeout = module.params["connect_timeout"]
|
connect_timeout = module.params["connect_timeout"]
|
||||||
config_file = module.params["config_file"]
|
config_file = module.params["config_file"]
|
||||||
|
@ -1239,12 +1359,12 @@ def main():
|
||||||
try:
|
try:
|
||||||
if update_password == "always":
|
if update_password == "always":
|
||||||
changed, msg = user_mod(cursor, user, host, host_all, password, encrypted,
|
changed, msg = user_mod(cursor, user, host, host_all, password, encrypted,
|
||||||
plugin, plugin_hash_string, plugin_auth_string,
|
plugin, plugin_hash_string, plugin_auth_string, priv,
|
||||||
priv, append_privs, tls_requires, module)
|
append_privs, tls_requires, account_locking, module)
|
||||||
else:
|
else:
|
||||||
changed, msg = user_mod(cursor, user, host, host_all, None, encrypted,
|
changed, msg = user_mod(cursor, user, host, host_all, None, encrypted,
|
||||||
plugin, plugin_hash_string, plugin_auth_string,
|
plugin, plugin_hash_string, plugin_auth_string, priv,
|
||||||
priv, append_privs, tls_requires, module)
|
append_privs, tls_requires, account_locking, 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))
|
||||||
|
@ -1253,8 +1373,8 @@ 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,
|
changed = user_add(cursor, user, host, host_all, password, encrypted,
|
||||||
plugin, plugin_hash_string, plugin_auth_string,
|
plugin, plugin_hash_string, plugin_auth_string, priv,
|
||||||
priv, tls_requires, module.check_mode)
|
tls_requires, account_locking, module.check_mode, module)
|
||||||
if changed:
|
if changed:
|
||||||
msg = "User added"
|
msg = "User added"
|
||||||
|
|
||||||
|
|
|
@ -37,4 +37,4 @@
|
||||||
- name: assert output message mysql user was created
|
- name: assert output message mysql user was created
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed == true"
|
- result is changed
|
||||||
|
|
275
tests/integration/targets/test_mysql_user/tasks/issue-49.yml
Normal file
275
tests/integration/targets/test_mysql_user/tasks/issue-49.yml
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
---
|
||||||
|
- vars:
|
||||||
|
mysql_parameters: &mysql_params
|
||||||
|
login_user: '{{ mysql_user }}'
|
||||||
|
login_password: '{{ mysql_password }}'
|
||||||
|
login_host: 127.0.0.1
|
||||||
|
login_port: '{{ mysql_primary_port }}'
|
||||||
|
|
||||||
|
block:
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
- name: find out the database version
|
||||||
|
mysql_info:
|
||||||
|
<<: *mysql_params
|
||||||
|
filter: version
|
||||||
|
register: db_version
|
||||||
|
|
||||||
|
- set_fact:
|
||||||
|
version_string: "{{[db_version.version.major, db_version.version.minor, db_version.version.release] | join('.')}}"
|
||||||
|
|
||||||
|
- name: Drop mysql user if exists
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_1 }}'
|
||||||
|
state: absent
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: Create user with account locking in test mode
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_1 }}'
|
||||||
|
password: '{{ user_password_1 }}'
|
||||||
|
account_locking:
|
||||||
|
PASSWORD_LOCK_TIME: 3
|
||||||
|
FAILED_LOGIN_ATTEMPTS: 3
|
||||||
|
check_mode: True
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
|
||||||
|
- name: Create user with account locking with password lock time below range
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_1 }}'
|
||||||
|
password: '{{ user_password_1 }}'
|
||||||
|
account_locking:
|
||||||
|
PASSWORD_LOCK_TIME: -1
|
||||||
|
FAILED_LOGIN_ATTEMPTS: 3
|
||||||
|
register: result
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is failed
|
||||||
|
- result.msg == "Account locking values are out of the valid range (0-32767)"
|
||||||
|
when: version_string is version('8.0.19', '>=') and version_string is version('10', '<')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
when: version_string is version('8.0.19', '<') or version_string is version('10', '>=')
|
||||||
|
|
||||||
|
- name: Create user with account locking with password lock time above range
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_1 }}'
|
||||||
|
password: '{{ user_password_1 }}'
|
||||||
|
account_locking:
|
||||||
|
PASSWORD_LOCK_TIME: 32768
|
||||||
|
FAILED_LOGIN_ATTEMPTS: 3
|
||||||
|
register: result
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is failed
|
||||||
|
- result.msg == "Account locking values are out of the valid range (0-32767)"
|
||||||
|
when: version_string is version('8.0.19', '>=') and version_string is version('10', '<')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is succeeded
|
||||||
|
when: version_string is version('8.0.19', '<') or version_string is version('10', '>=')
|
||||||
|
|
||||||
|
- name: Create user with account locking with failed login attempts below range
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_1 }}'
|
||||||
|
password: '{{ user_password_1 }}'
|
||||||
|
account_locking:
|
||||||
|
PASSWORD_LOCK_TIME: 2
|
||||||
|
FAILED_LOGIN_ATTEMPTS: -1
|
||||||
|
register: result
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is failed
|
||||||
|
- result.msg == "Account locking values are out of the valid range (0-32767)"
|
||||||
|
when: version_string is version('8.0.19', '>=') and version_string is version('10', '<')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is succeeded
|
||||||
|
when: version_string is version('8.0.19', '<') or version_string is version('10', '>=')
|
||||||
|
|
||||||
|
- name: Create user with account locking with failed login attempts above range
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_1 }}'
|
||||||
|
password: '{{ user_password_1 }}'
|
||||||
|
account_locking:
|
||||||
|
PASSWORD_LOCK_TIME: 2
|
||||||
|
FAILED_LOGIN_ATTEMPTS: 32768
|
||||||
|
register: result
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is failed
|
||||||
|
- result.msg == "Account locking values are out of the valid range (0-32767)"
|
||||||
|
when: version_string is version('8.0.19', '>=') and version_string is version('10', '<')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is succeeded
|
||||||
|
when: version_string is version('8.0.19', '<') or version_string is version('10', '>=')
|
||||||
|
|
||||||
|
- name: Create user with account locking with invalid password lock time
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_1 }}'
|
||||||
|
password: '{{ user_password_1 }}'
|
||||||
|
account_locking:
|
||||||
|
PASSWORD_LOCK_TIME: INVALID
|
||||||
|
FAILED_LOGIN_ATTEMPTS: 3
|
||||||
|
register: result
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is failed
|
||||||
|
- result.msg == "PASSWORD_LOCK_TIME must be an integer between 0 and 32767 or 'UNBOUNDED'"
|
||||||
|
when: version_string is version('8.0.19', '>=') and version_string is version('10', '<')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is succeeded
|
||||||
|
when: version_string is version('8.0.19', '<') or version_string is version('10', '>=')
|
||||||
|
|
||||||
|
- include: assert_no_user.yml user_name={{ user_name_1 }}
|
||||||
|
when: version_string is version('8.0.19', '>=') and version_string is version('10', '<')
|
||||||
|
|
||||||
|
- name: Create user with account locking
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_1 }}'
|
||||||
|
password: '{{ user_password_1 }}'
|
||||||
|
account_locking:
|
||||||
|
PASSWORD_LOCK_TIME: 3
|
||||||
|
FAILED_LOGIN_ATTEMPTS: 3
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
when: version_string is version('8.0.19', '>=') and version_string is version('10', '<')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is succeeded
|
||||||
|
when: version_string is version('8.0.19', '<') or version_string is version('10', '>=')
|
||||||
|
|
||||||
|
- include: assert_user.yml user_name={{ user_name_1 }}
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: retrieve create request
|
||||||
|
command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\""
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "{{ 'PASSWORD_LOCK_TIME 3' in result.stdout }}"
|
||||||
|
- "{{ 'FAILED_LOGIN_ATTEMPTS 3' in result.stdout }}"
|
||||||
|
when: version_string is version('8.0.19', '>=') and version_string is version('10', '<')
|
||||||
|
|
||||||
|
- name: Create existing user with account locking in test mode
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_1 }}'
|
||||||
|
password: '{{ user_password_1 }}'
|
||||||
|
account_locking:
|
||||||
|
PASSWORD_LOCK_TIME: 3
|
||||||
|
FAILED_LOGIN_ATTEMPTS: 3
|
||||||
|
check_mode: True
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that: result is not changed
|
||||||
|
|
||||||
|
- name: Create existing user with account locking
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_1 }}'
|
||||||
|
password: '{{ user_password_1 }}'
|
||||||
|
account_locking:
|
||||||
|
PASSWORD_LOCK_TIME: 3
|
||||||
|
FAILED_LOGIN_ATTEMPTS: 3
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that: result is not changed
|
||||||
|
|
||||||
|
- name: Update existing user with account locking in test mode
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_1 }}'
|
||||||
|
password: '{{ user_password_1 }}'
|
||||||
|
account_locking:
|
||||||
|
PASSWORD_LOCK_TIME: 3
|
||||||
|
FAILED_LOGIN_ATTEMPTS: 5
|
||||||
|
check_mode: True
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that: result is changed
|
||||||
|
when: version_string is version('8.0.19', '>=') and version_string is version('10', '<')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that: result is not changed
|
||||||
|
when: version_string is version('8.0.19', '<') or version_string is version('10', '>=')
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: retrieve create request
|
||||||
|
command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\""
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "{{ 'PASSWORD_LOCK_TIME 3' in result.stdout }}"
|
||||||
|
- "{{ 'FAILED_LOGIN_ATTEMPTS 3' in result.stdout }}"
|
||||||
|
when: version_string is version('8.0.19', '>=') and version_string is version('10', '<')
|
||||||
|
|
||||||
|
- name: Update existing user with account locking
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_1 }}'
|
||||||
|
password: '{{ user_password_1 }}'
|
||||||
|
account_locking:
|
||||||
|
PASSWORD_LOCK_TIME: 2
|
||||||
|
FAILED_LOGIN_ATTEMPTS: 5
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that: result is changed
|
||||||
|
when: version_string is version('8.0.19', '>=') and version_string is version('10', '<')
|
||||||
|
|
||||||
|
- assert:
|
||||||
|
that: result is not changed
|
||||||
|
when: version_string is version('8.0.19', '<') or version_string is version('10', '>=')
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: retrieve create request
|
||||||
|
command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\""
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- "{{ 'PASSWORD_LOCK_TIME 2' in result.stdout }}"
|
||||||
|
- "{{ 'FAILED_LOGIN_ATTEMPTS 5' in result.stdout }}"
|
||||||
|
when: version_string is version('8.0.19', '>=') and version_string is version('10', '<')
|
||||||
|
|
||||||
|
- include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }}
|
||||||
|
|
||||||
|
- include: assert_no_user.yml user_name={{user_name_1}}
|
|
@ -36,6 +36,7 @@
|
||||||
login_port: '{{ mysql_primary_port }}'
|
login_port: '{{ mysql_primary_port }}'
|
||||||
|
|
||||||
block:
|
block:
|
||||||
|
- include: issue-49.yml
|
||||||
|
|
||||||
- include: issue-28.yml
|
- include: issue-28.yml
|
||||||
|
|
||||||
|
@ -79,7 +80,7 @@
|
||||||
- name: assert output message mysql user was removed
|
- name: assert output message mysql user was removed
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed == true"
|
- result is changed
|
||||||
|
|
||||||
- include: assert_no_user.yml user_name={{user_name_1}}
|
- include: assert_no_user.yml user_name={{user_name_1}}
|
||||||
|
|
||||||
|
@ -236,6 +237,7 @@
|
||||||
# Test plaintext and encrypted password scenarios.
|
# Test plaintext and encrypted password scenarios.
|
||||||
#
|
#
|
||||||
- include: test_user_password.yml
|
- include: test_user_password.yml
|
||||||
|
- include: test_user_password_update.yml
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Test plugin authentication scenarios.
|
# Test plugin authentication scenarios.
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
- name: assert output message mysql user was removed
|
- name: assert output message mysql user was removed
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed == true"
|
- result is changed
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: create blank mysql user to be removed later
|
- name: create blank mysql user to be removed later
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
- name: assert changed is true for removing all blank users
|
- name: assert changed is true for removing all blank users
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed == true"
|
- result is changed
|
||||||
|
|
||||||
- name: remove blank mysql user with hosts=all (expect ok)
|
- name: remove blank mysql user with hosts=all (expect ok)
|
||||||
mysql_user:
|
mysql_user:
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
- name: assert output message for current privileges
|
- name: assert output message for current privileges
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed == true"
|
- result is changed
|
||||||
|
|
||||||
- name: run command to show privileges for user (expect privileges in stdout)
|
- name: run command to show privileges for user (expect privileges in stdout)
|
||||||
command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{user_name_2}}'@'localhost'\""
|
command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{user_name_2}}'@'localhost'\""
|
||||||
|
@ -101,7 +101,7 @@
|
||||||
- name: Assert that priv changed
|
- name: Assert that priv changed
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed == true"
|
- result is changed
|
||||||
|
|
||||||
- name: Add privs to a specific table (expect ok)
|
- name: Add privs to a specific table (expect ok)
|
||||||
mysql_user:
|
mysql_user:
|
||||||
|
@ -162,7 +162,7 @@
|
||||||
- name: Assert that priv changed
|
- name: Assert that priv changed
|
||||||
assert:
|
assert:
|
||||||
that:
|
that:
|
||||||
- "result.changed == true"
|
- result is changed
|
||||||
|
|
||||||
- name: Test idempotency (expect ok)
|
- name: Test idempotency (expect ok)
|
||||||
mysql_user:
|
mysql_user:
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
# test code update password for the mysql_user module
|
||||||
|
# (c) 2014, Wayne Rosario <wrosario@ansible.com>
|
||||||
|
|
||||||
|
# This file is part of Ansible
|
||||||
|
#
|
||||||
|
# Ansible is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 dof the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# Ansible is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
- vars:
|
||||||
|
mysql_parameters: &mysql_params
|
||||||
|
login_user: '{{ mysql_user }}'
|
||||||
|
login_password: '{{ mysql_password }}'
|
||||||
|
login_host: 127.0.0.1
|
||||||
|
login_port: '{{ mysql_primary_port }}'
|
||||||
|
|
||||||
|
block:
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Update user password for a user.
|
||||||
|
# Assert the user password is updated and old password can no longer be used.
|
||||||
|
#
|
||||||
|
- name: create user1 state=present with a password
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_1 }}'
|
||||||
|
password: '{{ user_password_1 }}'
|
||||||
|
priv: '*.*:ALL'
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: create user2 state=present with a password
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_2 }}'
|
||||||
|
password: '{{ user_password_2 }}'
|
||||||
|
priv: '*.*:ALL'
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: store user2 grants with old password (mysql 5.7.6 and newer)
|
||||||
|
command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ user_name_2 }}'@'localhost'\""
|
||||||
|
register: user_password_old_create
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: store user2 grants with old password (mysql 5.7.5 and older)
|
||||||
|
command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_2 }}'@'localhost'\""
|
||||||
|
register: user_password_old
|
||||||
|
when: user_password_old_create is failed
|
||||||
|
|
||||||
|
- name: update user2 state=present with same password (expect changed=false)
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_2 }}'
|
||||||
|
password: '{{ user_password_2 }}'
|
||||||
|
priv: '*.*:ALL'
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert output user2 was not updated
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "result.changed == false"
|
||||||
|
|
||||||
|
- include: assert_user.yml user_name={{user_name_2}} priv='ALL PRIVILEGES'
|
||||||
|
|
||||||
|
- name: update user2 state=present with a new password (expect changed=true)
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ user_name_2 }}'
|
||||||
|
password: '{{ user_password_1 }}'
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- include: assert_user.yml user_name={{user_name_2}} priv='ALL PRIVILEGES'
|
||||||
|
|
||||||
|
- name: store user2 grants with old password (mysql 5.7.6 and newer)
|
||||||
|
command: "{{ mysql_command }} -e \"SHOW CREATE USER '{{ user_name_2 }}'@'localhost'\""
|
||||||
|
register: user_password_new_create
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: store user2 grants with new password
|
||||||
|
command: "{{ mysql_command }} -e \"SHOW GRANTS FOR '{{ user_name_2 }}'@'localhost'\""
|
||||||
|
register: user_password_new
|
||||||
|
when: user_password_new_create is failed
|
||||||
|
|
||||||
|
- name: assert output message password was update for user2 (mysql 5.7.6 and newer)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "user_password_old_create.stdout != user_password_new_create.stdout"
|
||||||
|
when: user_password_new_create is not failed
|
||||||
|
|
||||||
|
- name: assert output message password was update for user2 (mysql 5.7.5 and older)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "user_password_old.stdout != user_password_new.stdout"
|
||||||
|
when: user_password_new_create is failed
|
||||||
|
|
||||||
|
- name: create database using user2 and old password
|
||||||
|
mysql_db:
|
||||||
|
login_user: '{{ user_name_2 }}'
|
||||||
|
login_password: '{{ user_password_2 }}'
|
||||||
|
login_host: '{{ mysql_host }}'
|
||||||
|
login_port: '{{ mysql_primary_port }}'
|
||||||
|
name: '{{ db_name }}'
|
||||||
|
state: present
|
||||||
|
ignore_errors: true
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- debug: var=result.msg
|
||||||
|
- name: assert output message that database not create with old password
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "result.failed == true"
|
||||||
|
|
||||||
|
- name: create database using user2 and new password
|
||||||
|
mysql_db:
|
||||||
|
login_user: '{{ user_name_2 }}'
|
||||||
|
login_password: '{{ user_password_1 }}'
|
||||||
|
login_host: '{{ mysql_host }}'
|
||||||
|
login_port: '{{ mysql_primary_port }}'
|
||||||
|
name: '{{ db_name }}'
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert output message that database is created with new password
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
|
||||||
|
- name: remove database
|
||||||
|
mysql_db:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: '{{ db_name }}'
|
||||||
|
state: absent
|
||||||
|
login_unix_socket: '{{ mysql_socket }}'
|
||||||
|
|
||||||
|
- include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }}
|
||||||
|
|
||||||
|
- include: remove_user.yml user_name={{user_name_2}} user_password={{ user_password_1 }}
|
||||||
|
|
||||||
|
- name: Create user with Fdt8fd^34ds using hash. (expect changed=true)
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: jmainguy
|
||||||
|
password: '*0cb5b86f23fdc24db19a29b8854eb860cbc47793'
|
||||||
|
encrypted: yes
|
||||||
|
register: encrypt_result
|
||||||
|
|
||||||
|
- name: Check that the module made a change
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "encrypt_result.changed == True"
|
||||||
|
|
||||||
|
- name: See if the password needs to be updated. (expect changed=false)
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: jmainguy
|
||||||
|
password: 'Fdt8fd^34ds'
|
||||||
|
register: plain_result
|
||||||
|
|
||||||
|
- name: Check that the module did not change the password
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- "plain_result.changed == False"
|
||||||
|
|
||||||
|
- name: Remove user (cleanup)
|
||||||
|
mysql_user:
|
||||||
|
<<: *mysql_params
|
||||||
|
name: jmainguy
|
||||||
|
state: absent
|
Loading…
Add table
Reference in a new issue