User locking (#702)

* function to check if a user is locked already

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add the location and logic of where I think user locking would happen.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Fix missing parameters for execute()

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add the locked attribute

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Initial user locking integration tests

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add attribute documentation

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* More descriptive names in the integration tests

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* - Changes requested/suggested by @Andersson007
- Example usage
- Changelog fragment

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Fix user_is_locked and remove host_all option.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Fix host of user (was % should have been localhost after deleting `host:` earlier)

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Switch locked to named instead of positional.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add check_mode support.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add check_mode: true test cases

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Fix names that included `check_mode: true`

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add idempotence checks

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Switch calls to user_mod with sequences of None positional arguments to full named arguments

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* locked check should not run for roles.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* check_mode is set at the task level and not the module level

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add user locking to info module and test.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Handle DictCursor

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>


* Add check_mode feedback

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>


* Add another builtin account to the exclusion list

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Initial switch to default=None for locked, will need to add a test for it.

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>

* Add check that missing locked argument does not unlock a user

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>
---------

Signed-off-by: E.S. Rosenberg a.k.a. Keeper of the Keys <es.rosenberg+github@gmail.com>
This commit is contained in:
Keeper-of-the-Keys 2025-03-19 15:40:59 +02:00 committed by GitHub
parent dd7e297d50
commit 45a29408ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 285 additions and 14 deletions

View file

@ -52,6 +52,25 @@ def user_exists(cursor, user, host, host_all):
return count[0] > 0
def user_is_locked(cursor, user, host):
cursor.execute("SHOW CREATE USER %s@%s", (user, host))
# Per discussions on irc:libera.chat:#maria the query may return up to 2 rows but "ACCOUNT LOCK" should always be in the first row.
result = cursor.fetchone()
# ACCOUNT LOCK does not have to be the last option in the CREATE USER query.
# Need to handle both DictCursor and non-DictCursor
if isinstance(result, tuple):
if result[0].find('ACCOUNT LOCK') > 0:
return True
elif isinstance(result, dict):
for res in result.values():
if res.find('ACCOUNT LOCK') > 0:
return True
return False
def sanitize_requires(tls_requires):
sanitized_requires = {}
if tls_requires:
@ -160,7 +179,7 @@ def get_existing_authentication(cursor, user, host=None):
def user_add(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string, salt, new_priv,
attributes, tls_requires, reuse_existing_password, module,
password_expire, password_expire_interval):
password_expire, password_expire_interval, locked=False):
# If attributes are set, perform a sanity check to ensure server supports user attributes before creating user
if attributes and not get_attribute_support(cursor):
module.fail_json(msg="user attributes were specified but the server does not support user attributes")
@ -250,6 +269,9 @@ def user_add(cursor, user, host, host_all, password, encrypted,
cursor.execute("ALTER USER %s@%s ATTRIBUTE %s", (user, host, json.dumps(attributes)))
final_attributes = attributes_get(cursor, user, host)
if locked:
cursor.execute("ALTER USER %s@%s ACCOUNT LOCK", (user, host))
return {'changed': True, 'password_changed': not used_existing_password, 'attributes': final_attributes}
@ -264,7 +286,7 @@ def is_hash(password):
def user_mod(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string, salt, new_priv,
append_privs, subtract_privs, attributes, tls_requires, module,
password_expire, password_expire_interval, role=False, maria_role=False):
password_expire, password_expire_interval, locked=None, role=False, maria_role=False):
changed = False
msg = "User unchanged"
grant_option = False
@ -536,6 +558,22 @@ def user_mod(cursor, user, host, host_all, password, encrypted,
if attribute_support:
final_attributes = attributes_get(cursor, user, host)
if not role and locked is not None and user_is_locked(cursor, user, host) != locked:
if not module.check_mode:
if locked:
cursor.execute("ALTER USER %s@%s ACCOUNT LOCK", (user, host))
msg = 'User locked'
else:
cursor.execute("ALTER USER %s@%s ACCOUNT UNLOCK", (user, host))
msg = 'User unlocked'
else:
if locked:
msg = 'User will be locked'
else:
msg = 'User will be unlocked'
changed = True
if role:
continue