Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

password_expire support for mysql_user #598

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
314ec02
initial commit for password_expire support
tompal3 Nov 22, 2023
721dd8e
sanity check and default values
tompal3 Nov 22, 2023
10780ae
add one more if block for version check
tompal3 Nov 22, 2023
6d73c24
some changes and integration tests
tompal3 Nov 28, 2023
a69c0e9
docs and sanity and integration test fix
tompal3 Nov 28, 2023
778fbd4
make integration tests work
tompal3 Nov 29, 2023
23db3f4
make integration tests work
tompal3 Nov 29, 2023
d506d83
Merge remote-tracking branch 'origin/main' into password_expiration_m…
tompal3 Nov 30, 2023
a9abc8a
fix unneeded commits
tompal3 Nov 30, 2023
9b13b59
fix verify as well
tompal3 Nov 30, 2023
e255551
Update plugins/modules/mysql_user.py
tompal3 Jan 9, 2024
379b25e
Update tests/integration/targets/test_mysql_user/tasks/test_password_…
tompal3 Jan 9, 2024
682fae4
Apply suggestions from code review
tompal3 Jan 9, 2024
5b54cd3
Update plugins/modules/mysql_user.py
tompal3 Feb 20, 2024
a3dbfda
Update plugins/modules/mysql_user.py
tompal3 Feb 20, 2024
2cf33e1
Update plugins/modules/mysql_user.py
tompal3 Feb 20, 2024
7eb35e3
Update plugins/modules/mysql_user.py
tompal3 Feb 20, 2024
40e43ac
Update plugins/module_utils/user.py
tompal3 Feb 20, 2024
c415325
Update plugins/module_utils/user.py
tompal3 Feb 20, 2024
494b537
Update plugins/module_utils/user.py
tompal3 Feb 20, 2024
3e10ac4
typo and no_log remove for password_expire* vars
tompal3 Feb 20, 2024
ef058c4
add change log fragment
tompal3 Feb 20, 2024
642d87a
move one if statement to module initialiazation
tompal3 Feb 20, 2024
273fd2c
Merge branch 'main' into password_expiration_mysql_user
tompal3 Feb 21, 2024
ea79c2a
fix merge conflicts
tompal3 Feb 21, 2024
aed8620
fix order
tompal3 Feb 21, 2024
5959890
some fixes
tompal3 Feb 21, 2024
16f8844
set no_log to true for password word containing keys
tompal3 Feb 22, 2024
20e3f9d
fix sanity error
tompal3 Feb 22, 2024
1c25ab5
Update changelogs/fragments/598-password_expire-support-for-mysql_use…
tompal3 Feb 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions plugins/module_utils/implementations/mariadb/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@ def server_supports_alter_user(cursor):
version = get_server_version(cursor)

return LooseVersion(version) >= LooseVersion("10.2")


def server_supports_password_expire(cursor):
version = get_server_version(cursor)

return LooseVersion(version) >= LooseVersion("10.4.3")
6 changes: 6 additions & 0 deletions plugins/module_utils/implementations/mysql/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ def server_supports_alter_user(cursor):
version = get_server_version(cursor)

return LooseVersion(version) >= LooseVersion("5.6")


def server_supports_password_expire(cursor):
version = get_server_version(cursor)

return LooseVersion(version) >= LooseVersion("5.7")
109 changes: 106 additions & 3 deletions plugins/module_utils/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,13 @@

def user_add(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string, new_priv,
tls_requires, check_mode, reuse_existing_password):
tls_requires, module, reuse_existing_password,
password_expire, password_expire_interval):
# we cannot create users without a proper hostname
if host_all:
return {'changed': False, 'password_changed': False}

if check_mode:
if module.check_mode:
return {'changed': True, 'password_changed': None}

# Determine what user management method server uses
Expand Down Expand Up @@ -200,6 +201,12 @@
query_with_args_and_tls_requires = query_with_args + (tls_requires,)
cursor.execute(*mogrify(*query_with_args_and_tls_requires))

if password_expire:
if not impl.server_supports_password_expire(cursor):
module.fail_json(msg="The server version does not match the requirements "

Check warning on line 206 in plugins/module_utils/user.py

View check run for this annotation

Codecov / codecov/patch

plugins/module_utils/user.py#L206

Added line #L206 was not covered by tests
"for password_expire parameter. See module's documentation.")
set_password_expire(cursor, user, host, password_expire, password_expire_interval)

if new_priv is not None:
for db_table, priv in iteritems(new_priv):
privileges_grant(cursor, user, host, db_table, priv, tls_requires)
Expand All @@ -218,7 +225,8 @@

def user_mod(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string, new_priv,
append_privs, subtract_privs, tls_requires, module, role=False, maria_role=False):
append_privs, subtract_privs, tls_requires, module,
password_expire, password_expire_interval, role=False, maria_role=False):
changed = False
msg = "User unchanged"
grant_option = False
Expand Down Expand Up @@ -301,6 +309,30 @@
raise e
changed = True

# Handle password expiration
if bool(password_expire):
if impl.server_supports_password_expire(cursor):
update = False
mariadb_role = True if "mariadb" in str(impl.__name__) else False
current_password_policy = get_password_expiration_policy(cursor, user, host, maria_role=mariadb_role)
password_expired = is_password_expired(cursor, user, host)
# Check if changes needed to be applied.
if not ((current_password_policy == -1 and password_expire == "default") or
(current_password_policy == 0 and password_expire == "never") or
(current_password_policy == password_expire_interval and password_expire == "interval") or
(password_expire == 'now' and password_expired)):

update = True

if module.check_mode:
return {'changed': True, 'msg': msg, 'password_changed': password_changed}
set_password_expire(cursor, user, host, password_expire, password_expire_interval)
password_changed = True
changed = True
else:
module.fail_json(msg="The server version does not match the requirements "

Check warning on line 333 in plugins/module_utils/user.py

View check run for this annotation

Codecov / codecov/patch

plugins/module_utils/user.py#L333

Added line #L333 was not covered by tests
"for password_expire parameter. See module's documentation.")
tompal3 marked this conversation as resolved.
Show resolved Hide resolved

# Handle plugin authentication
if plugin and not role:
cursor.execute("SELECT plugin, authentication_string FROM mysql.user "
Expand Down Expand Up @@ -924,6 +956,77 @@
return True


def set_password_expire(cursor, user, host, password_expire, password_expire_interval):
"""Fuction to set passowrd expiration for user.

Args:
cursor (cursor): DB driver cursor object.
user (str): User name.
host (str): User hostname.
password_expire (str): Password expiration mode.
password_expire_days (int): Invterval of days password expires.
"""
if password_expire.lower() == "never":
statment = "PASSWORD EXPIRE NEVER"
elif password_expire.lower() == "default":
statment = "PASSWORD EXPIRE DEFAULT"
elif password_expire.lower() == "interval":
statment = "PASSWORD EXPIRE INTERVAL %d DAY" % (password_expire_interval)
elif password_expire.lower() == "now":
statment = "PASSWORD EXPIRE"
tompal3 marked this conversation as resolved.
Show resolved Hide resolved

params = (user, host)
query = ["ALTER USER %s@%s"]

query.append(statment)
query = ' '.join(query)
cursor.execute(query, params)
tompal3 marked this conversation as resolved.
Show resolved Hide resolved


def get_password_expiration_policy(cursor, user, host, maria_role=False):
"""Function to get password policy for user.

Args:
cursor (cursor): DB driver cursor object.
user (str): User name.
host (str): User hostname.
maria_role (bool, optional): mariadb or mysql. Defaults to False.

Returns:
policy (int): Current users password policy.
"""
if not maria_role:
statment = "SELECT IFNULL(password_lifetime, -1) FROM mysql.user \
WHERE User = %s AND Host = %s", (user, host)
else:
statment = "SELECT JSON_EXTRACT(Priv, '$.password_lifetime') AS password_lifetime \
FROM mysql.global_priv \
WHERE User = %s AND Host = %s", (user, host)
cursor.execute(*statment)
policy = cursor.fetchone()[0]
return int(policy)


def is_password_expired(cursor, user, host):
"""Function to check if password is expired

Args:
cursor (cursor): DB driver cursor object.
user (str): User name.
host (str): User hostname.

Returns:
expired (bool): True if expired, else False.
"""
statment = "SELECT password_expired FROM mysql.user \
WHERE User = %s AND Host = %s", (user, host)
cursor.execute(*statment)
expired = cursor.fetchone()[0]
if str(expired) == "Y":
return True
return False


def get_impl(cursor):
global impl
cursor.execute("SELECT VERSION()")
Expand Down
3 changes: 2 additions & 1 deletion plugins/modules/mysql_role.py
Original file line number Diff line number Diff line change
Expand Up @@ -932,7 +932,8 @@ def update(self, users, privs, check_mode=False,
result = user_mod(self.cursor, self.name, self.host,
None, None, None, None, None, None,
privs, append_privs, subtract_privs, None,
self.module, role=True, maria_role=self.is_mariadb)
self.module, None, None, role=True,
maria_role=self.is_mariadb)
changed = result['changed']

if admin:
Expand Down
34 changes: 31 additions & 3 deletions plugins/modules/mysql_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,18 @@
- Cannot be used to set global variables, use the M(community.mysql.mysql_variables) module instead.
type: dict
version_added: '3.6.0'
password_expire:
description:
- C(never) password will never expire.
- C(default) password is defined ussing global system varaiable I(default_password_lifetime) setting.
tompal3 marked this conversation as resolved.
Show resolved Hide resolved
- C(interval) password will expire in days which is defined in I(password_expire_interval).
- C(now) password will expire immediately.
type: str
choices: [ now, never, default, interval ]
tompal3 marked this conversation as resolved.
Show resolved Hide resolved
password_expire_interval:
description:
- number of days password will expire. Used when I(password_expire=interval).
tompal3 marked this conversation as resolved.
Show resolved Hide resolved
type: int
tompal3 marked this conversation as resolved.
Show resolved Hide resolved

column_case_sensitive:
description:
Expand Down Expand Up @@ -415,6 +427,8 @@ def main():
force_context=dict(type='bool', default=False),
session_vars=dict(type='dict'),
column_case_sensitive=dict(type='bool', default=None), # TODO 4.0.0 add default=True
password_expire=dict(type='str', choices=['now', 'never', 'default', 'interval'], no_log=True),
password_expire_interval=dict(type='int', no_log=True),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
password_expire=dict(type='str', choices=['now', 'never', 'default', 'interval'], no_log=True),
password_expire_interval=dict(type='int', no_log=True),
password_expire=dict(type='str', choices=['now', 'never', 'default', 'interval'], no_log=True),
password_expire_interval=dict(type='int', no_log=True),

why do we no_log them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no reason why we should, will remove that one.

)
module = AnsibleModule(
argument_spec=argument_spec,
Expand Down Expand Up @@ -451,6 +465,8 @@ def main():
resource_limits = module.params["resource_limits"]
session_vars = module.params["session_vars"]
column_case_sensitive = module.params["column_case_sensitive"]
password_expire = module.params["password_expire"]
password_expire_interval = module.params["password_expire_interval"]

if priv and not isinstance(priv, (str, dict)):
module.fail_json(msg="priv parameter must be str or dict but %s was passed" % type(priv))
Expand All @@ -461,6 +477,15 @@ def main():
if mysql_driver is None:
module.fail_json(msg=mysql_driver_fail_msg)

if password_expire == "interval" and not password_expire_interval:
module.fail_json(msg="password_expire value interval \
should be used with password_expire_interval")

if password_expire_interval:
if password_expire_interval < 1:
module.fail_json(msg="password_expire_interval value \
should be positive number")
tompal3 marked this conversation as resolved.
Show resolved Hide resolved

cursor = None
try:
if check_implicit_admin:
Expand Down Expand Up @@ -506,12 +531,14 @@ def main():
if update_password == "always":
result = user_mod(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string,
priv, append_privs, subtract_privs, tls_requires, module)
priv, append_privs, subtract_privs, tls_requires, module,
password_expire, password_expire_interval)

else:
result = user_mod(cursor, user, host, host_all, None, encrypted,
None, None, None,
priv, append_privs, subtract_privs, tls_requires, module)
priv, append_privs, subtract_privs, tls_requires, module,
password_expire, password_expire_interval)
changed = result['changed']
msg = result['msg']
password_changed = result['password_changed']
Expand All @@ -527,7 +554,8 @@ def main():
reuse_existing_password = update_password == 'on_new_username'
result = user_add(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string,
priv, tls_requires, module.check_mode, reuse_existing_password)
priv, tls_requires, module, reuse_existing_password,
password_expire, password_expire_interval)
changed = result['changed']
password_changed = result['password_changed']
if changed:
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/targets/test_mysql_user/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@

- include_tasks: test_idempotency.yml

- include_tasks: test_password_expire.yml

# ============================================================
# Create user with no privileges and verify default privileges are assign
#
Expand Down
Loading