add option subtract_privs to mysql_role and mysql_user

see https://github.com/ansible-collections/community.mysql/issues/331
This commit is contained in:
Felix Hamme 2022-04-12 09:54:22 +02:00
commit 3dc21216cb
3 changed files with 83 additions and 36 deletions

View file

@ -169,7 +169,7 @@ def is_hash(password):
def user_mod(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string, new_priv,
append_privs, tls_requires, module, role=False, maria_role=False):
append_privs, subtract_privs, tls_requires, module, role=False, maria_role=False):
changed = False
msg = "User unchanged"
grant_option = False
@ -288,47 +288,58 @@ def user_mod(cursor, user, host, host_all, password, encrypted,
# If the user has privileges on a db.table that doesn't appear at all in
# the new specification, then revoke all privileges on it.
for db_table, priv in iteritems(curr_priv):
# If the user has the GRANT OPTION on a db.table, revoke it first.
if "GRANT" in priv:
grant_option = True
if db_table not in new_priv:
if user != "root" and "PROXY" not in priv and not append_privs:
msg = "Privileges updated"
if module.check_mode:
return (True, msg)
privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_role)
changed = True
if not append_privs and not subtract_privs:
for db_table, priv in iteritems(curr_priv):
# If the user has the GRANT OPTION on a db.table, revoke it first.
if "GRANT" in priv:
grant_option = True
if db_table not in new_priv:
if user != "root" and "PROXY" not in priv:
msg = "Privileges updated"
if module.check_mode:
return (True, msg)
privileges_revoke(cursor, user, host, db_table, priv, grant_option, maria_role)
changed = True
# If the user doesn't currently have any privileges on a db.table, then
# we can perform a straight grant operation.
for db_table, priv in iteritems(new_priv):
if db_table not in curr_priv:
msg = "New privileges granted"
if module.check_mode:
return (True, msg)
privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_role)
changed = True
if not subtract_privs:
for db_table, priv in iteritems(new_priv):
if db_table not in curr_priv:
msg = "New privileges granted"
if module.check_mode:
return (True, msg)
privileges_grant(cursor, user, host, db_table, priv, tls_requires, maria_role)
changed = True
# If the db.table specification exists in both the user's current privileges
# and in the new privileges, then we need to see if there's a difference.
db_table_intersect = set(new_priv.keys()) & set(curr_priv.keys())
for db_table in db_table_intersect:
# If appending privileges, only the set difference between new privileges and current privileges matter.
# The symmetric difference isn't relevant for append because existing privileges will not be revoked.
grant_privs = []
revoke_privs = []
if append_privs:
priv_diff = set(new_priv[db_table]) - set(curr_priv[db_table])
# When appending privileges, only missing privileges need to be granted. Nothing is revoked.
grant_privs = list(set(new_priv[db_table]) - set(curr_priv[db_table]))
elif subtract_privs:
# When subtracting privileges, revoke only the intersection of requested and current privileges.
# No privileges are granted.
revoke_privs = list(set(new_priv[db_table]) & set(curr_priv[db_table]))
else:
priv_diff = set(new_priv[db_table]) ^ set(curr_priv[db_table])
# When replacing (neither append_privs nor subtract_privs), grant all missing privileges
# and revoke existing privileges that were not requested.
grant_privs = list(set(new_priv[db_table]) - set(curr_priv[db_table]))
revoke_privs = list(set(curr_priv[db_table]) - set(new_priv[db_table]))
if len(priv_diff) > 0:
if len(grant_privs) + len(revoke_privs) > 0:
msg = "Privileges updated"
if module.check_mode:
return (True, msg)
if not append_privs:
privileges_revoke(cursor, user, host, db_table, curr_priv[db_table], grant_option, maria_role)
privileges_grant(cursor, user, host, db_table, new_priv[db_table], tls_requires, maria_role)
if len(revoke_privs) > 0:
privileges_revoke(cursor, user, host, db_table, revoke_privs, grant_option, maria_role)
if len(grant_privs) > 0:
privileges_grant(cursor, user, host, db_table, grant_privs, tls_requires, maria_role)
changed = True
if role:

View file

@ -51,7 +51,14 @@ options:
append_privs:
description:
- Append the privileges defined by the I(priv) option to the existing ones
for this role instead of overwriting them.
for this role instead of overwriting them. Mutually exclusive with I(subtract_privs).
type: bool
default: no
subtract_privs:
description:
- Revoke the privileges defined by the I(priv) option and keep other existing privileges.
Mutually exclusive with I(append_privs).
type: bool
default: no
@ -233,6 +240,14 @@ EXAMPLES = r'''
name: business
members:
- marketing
- name: Ensure the role foo does not have the DELETE privilege
community.mysql.mysql_role:
state: present
name: foo
subtract_privs: yes
priv:
'db1.*': DELETE
'''
RETURN = '''#'''
@ -821,9 +836,9 @@ class Role():
return True
def update(self, users, privs, check_mode=False,
append_privs=False, append_members=False,
detach_members=False, admin=False,
set_default_role_all=True):
append_privs=False, subtract_privs=False,
append_members=False, detach_members=False,
admin=False, set_default_role_all=True):
"""Update a role.
Update a role if needed.
@ -837,6 +852,8 @@ class Role():
check_mode (bool): If True, just checks and does nothing.
append_privs (bool): If True, adds new privileges passed through privs
not touching current privileges.
subtract_privs (bool): If True, revoke the privileges passed through privs
not touching other existing privileges.
append_members (bool): If True, adds new members passed through users
not touching current members.
detach_members (bool): If True, removes members passed through users from a role.
@ -861,7 +878,7 @@ class Role():
if privs:
changed, msg = user_mod(self.cursor, self.name, self.host,
None, None, None, None, None, None,
privs, append_privs, None,
privs, append_privs, subtract_privs, None,
self.module, role=True, maria_role=self.is_mariadb)
if admin:
@ -931,6 +948,7 @@ def main():
admin=dict(type='str'),
priv=dict(type='raw'),
append_privs=dict(type='bool', default=False),
subtract_privs=dict(type='bool', default=False),
members=dict(type='list', elements='str'),
append_members=dict(type='bool', default=False),
detach_members=dict(type='bool', default=False),
@ -945,6 +963,7 @@ def main():
('admin', 'members'),
('admin', 'append_members'),
('admin', 'detach_members'),
('append_privs', 'subtract_privs'),
),
)
@ -958,6 +977,7 @@ def main():
connect_timeout = module.params['connect_timeout']
config_file = module.params['config_file']
append_privs = module.params['append_privs']
subtract_privs = module.boolean(module.params['subtract_privs'])
members = module.params['members']
append_members = module.params['append_members']
detach_members = module.params['detach_members']
@ -1047,7 +1067,7 @@ def main():
set_default_role_all)
else:
changed = role.update(members, priv, module.check_mode, append_privs,
changed = role.update(members, priv, module.check_mode, append_privs, subtract_privs,
append_members, detach_members, admin,
set_default_role_all)

View file

@ -63,7 +63,13 @@ options:
append_privs:
description:
- Append the privileges defined by priv to the existing ones for this
user instead of overwriting existing ones.
user instead of overwriting existing ones. Mutually exclusive with I(subtract_privs).
type: bool
default: no
subtract_privs:
description:
- Revoke the privileges defined by the I(priv) option and keep other existing privileges.
Mutually exclusive with I(append_privs).
type: bool
default: no
tls_requires:
@ -306,6 +312,13 @@ EXAMPLES = r'''
MAX_QUERIES_PER_HOUR: 10
MAX_CONNECTIONS_PER_HOUR: 5
- name: Ensure bob does not have the DELETE privilege
community.mysql.mysql_user:
name: bob
subtract_privs: yes
priv:
'db1.*': DELETE
# Example .my.cnf file for setting the root password
# [client]
# user=root
@ -352,6 +365,7 @@ def main():
priv=dict(type='raw'),
tls_requires=dict(type='dict'),
append_privs=dict(type='bool', default=False),
subtract_privs=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),
sql_log_bin=dict(type='bool', default=True),
@ -364,6 +378,7 @@ def main():
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
mutually_exclusive=(('append_privs', 'subtract_privs'),)
)
login_user = module.params["login_user"]
login_password = module.params["login_password"]
@ -379,6 +394,7 @@ def main():
connect_timeout = module.params["connect_timeout"]
config_file = module.params["config_file"]
append_privs = module.boolean(module.params["append_privs"])
subtract_privs = module.boolean(module.params['subtract_privs'])
update_password = module.params['update_password']
ssl_cert = module.params["client_cert"]
ssl_key = module.params["client_key"]
@ -435,11 +451,11 @@ def main():
if update_password == "always":
changed, msg = user_mod(cursor, user, host, host_all, password, encrypted,
plugin, plugin_hash_string, plugin_auth_string,
priv, append_privs, tls_requires, module)
priv, append_privs, subtract_privs, tls_requires, module)
else:
changed, msg = user_mod(cursor, user, host, host_all, None, encrypted,
plugin, plugin_hash_string, plugin_auth_string,
priv, append_privs, tls_requires, module)
priv, append_privs, subtract_privs, tls_requires, module)
except (SQLParseError, InvalidPrivsError, mysql_driver.Error) as e:
module.fail_json(msg=to_native(e))