Add support for MariaDB 10.5's new grants and grant aliases (#59280)

* Add support for MariaDB 10.5's new grants and grant aliases

* Add support for MariaDB 10.5.9's REPLICA/SLAVE MONITOR grant

* Add tests for MariaDB grant aliases

* Update test_mysql.py

* Update test_mysql.py

Co-authored-by: Gareth J. Greenaway <gareth@saltstack.com>
This commit is contained in:
Heinz Wiesinger 2022-09-28 23:59:45 +02:00 committed by GitHub
parent 17401db607
commit 8b53054b5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 499 additions and 0 deletions

View file

@ -82,7 +82,11 @@ __grants__ = [
"ALTER ROUTINE",
"BACKUP_ADMIN",
"BINLOG_ADMIN",
"BINLOG ADMIN",
"BINLOG MONITOR",
"BINLOG REPLAY",
"CONNECTION_ADMIN",
"CONNECTION ADMIN",
"CREATE",
"CREATE ROLE",
"CREATE ROUTINE",
@ -96,6 +100,7 @@ __grants__ = [
"ENCRYPTION_KEY_ADMIN",
"EVENT",
"EXECUTE",
"FEDERATED ADMIN",
"FILE",
"GRANT OPTION",
"GROUP_REPLICATION_ADMIN",
@ -104,15 +109,21 @@ __grants__ = [
"LOCK TABLES",
"PERSIST_RO_VARIABLES_ADMIN",
"PROCESS",
"READ_ONLY ADMIN",
"REFERENCES",
"RELOAD",
"REPLICA MONITOR",
"REPLICATION CLIENT",
"REPLICATION MASTER ADMIN",
"REPLICATION REPLICA",
"REPLICATION SLAVE",
"REPLICATION_SLAVE_ADMIN",
"REPLICATION SLAVE ADMIN",
"RESOURCE_GROUP_ADMIN",
"RESOURCE_GROUP_USER",
"ROLE_ADMIN",
"SELECT",
"SET USER",
"SET_USER_ID",
"SHOW DATABASES",
"SHOW VIEW",
@ -620,6 +631,67 @@ def _grant_to_tokens(grant):
return dict(user=user, host=host, grant=grant_tokens, database=database)
def _resolve_grant_aliases(grants, server_version):
"""
There can be a situation where the database supports grants "A" and "B", where
"B" is an alias for "A". In that case, when you want to grant "B" to a user,
the database will actually report it added "A". We need to resolve those
aliases to not report (wrong) errors.
:param grants: the tokenized grants
:param server_version: version string of the connected database
"""
if "MariaDB" not in server_version:
return grants
mariadb_version_compare_replication_replica = "10.5.1"
mariadb_version_compare_binlog_monitor = "10.5.2"
mariadb_version_compare_slave_monitor = "10.5.9"
resolved_tokens = []
for token in grants:
if (
salt.utils.versions.version_cmp(
server_version, mariadb_version_compare_replication_replica
)
>= 0
):
if token == "REPLICATION REPLICA":
# https://mariadb.com/kb/en/grant/#replication-replica
resolved_tokens.append("REPLICATION SLAVE")
continue
if (
salt.utils.versions.version_cmp(
server_version, mariadb_version_compare_binlog_monitor
)
>= 0
):
if token == "REPLICATION CLIENT":
# https://mariadb.com/kb/en/grant/#replication-client
resolved_tokens.append("BINLOG MONITOR")
continue
if (
salt.utils.versions.version_cmp(
server_version, mariadb_version_compare_slave_monitor
)
>= 0
):
if token == "REPLICA MONITOR":
# https://mariadb.com/kb/en/grant/#replica-monitor
resolved_tokens.append("SLAVE MONITOR")
continue
resolved_tokens.append(token)
return resolved_tokens
def quote_identifier(identifier, for_grants=False):
r"""
Return an identifier name (column, table, database, etc) escaped for MySQL
@ -2439,6 +2511,9 @@ def grant_exists(
_grants = {}
for grant in grants:
grant_token = _grant_to_tokens(grant)
grant_token["grant"] = _resolve_grant_aliases(
grant_token["grant"], server_version
)
if grant_token["database"] not in _grants:
_grants[grant_token["database"]] = {
"user": grant_token["user"],
@ -2450,6 +2525,9 @@ def grant_exists(
_grants[grant_token["database"]]["grant"].extend(grant_token["grant"])
target_tokens = _grant_to_tokens(target)
target_tokens["grant"] = _resolve_grant_aliases(
target_tokens["grant"], server_version
)
for database, grant_tokens in _grants.items():
try:
_grant_tokens = {}

View file

@ -8,6 +8,7 @@ import pytest
from pytestshellutils.utils import format_callback_to_string
import salt.modules.mysql as mysqlmod
from salt.utils.versions import version_cmp
from tests.support.pytest.mysql import * # pylint: disable=wildcard-import,unused-wildcard-import
log = logging.getLogger(__name__)
@ -297,6 +298,426 @@ def test_grant_add_revoke(mysql):
assert ret
def test_grant_replication_replica_add_revoke(mysql, mysql_container):
# The REPLICATION REPLICA grant is only available for mariadb
if "mariadb" not in mysql_container.mysql_name:
pytest.skip(
"The REPLICATION REPLICA grant is unavailable "
"for the {}:{} docker image.".format(
mysql_container.mysql_name, mysql_container.mysql_version
)
)
# The REPLICATION REPLICA grant was added in mariadb 10.5.1
if version_cmp(mysql_container.mysql_version, "10.5.1") < 0:
pytest.skip(
"The REPLICATION REPLICA grant is unavailable "
"for the {}:{} docker image.".format(
mysql_container.mysql_name, mysql_container.mysql_version
)
)
# Create the database
ret = mysql.db_create("salt")
assert ret
# Create a user
ret = mysql.user_create(
"george",
host="localhost",
password="badpassword",
)
assert ret
# Grant privileges to user to specific table
ret = mysql.grant_add(
grant="REPLICATION REPLICA",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Check the grant exists
ret = mysql.grant_exists(
grant="REPLICATION REPLICA",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Revoke the global grant
ret = mysql.grant_revoke(
grant="REPLICATION REPLICA",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Check the grant does not exist
ret = mysql.grant_exists(
grant="REPLICATION REPLICA",
database="*.*",
user="george",
host="localhost",
)
assert not ret
# Remove the user
ret = mysql.user_remove("george", host="localhost")
assert ret
# Remove the database
ret = mysql.db_remove("salt")
assert ret
def test_grant_replication_slave_add_revoke(mysql, mysql_container):
# Create the database
ret = mysql.db_create("salt")
assert ret
# Create a user
ret = mysql.user_create(
"george",
host="localhost",
password="badpassword",
)
assert ret
# Grant privileges to user to specific table
ret = mysql.grant_add(
grant="REPLICATION SLAVE",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Check the grant exists
ret = mysql.grant_exists(
grant="REPLICATION SLAVE",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Revoke the global grant
ret = mysql.grant_revoke(
grant="REPLICATION SLAVE",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Check the grant does not exist
ret = mysql.grant_exists(
grant="REPLICATION SLAVE",
database="*.*",
user="george",
host="localhost",
)
assert not ret
# Remove the user
ret = mysql.user_remove("george", host="localhost")
assert ret
# Remove the database
ret = mysql.db_remove("salt")
assert ret
def test_grant_replication_client_add_revoke(mysql, mysql_container):
# Create the database
ret = mysql.db_create("salt")
assert ret
# Create a user
ret = mysql.user_create(
"george",
host="localhost",
password="badpassword",
)
assert ret
# Grant privileges to user to specific table
ret = mysql.grant_add(
grant="REPLICATION CLIENT",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Check the grant exists
ret = mysql.grant_exists(
grant="REPLICATION CLIENT",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Revoke the global grant
ret = mysql.grant_revoke(
grant="REPLICATION CLIENT",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Check the grant does not exist
ret = mysql.grant_exists(
grant="REPLICATION CLIENT",
database="*.*",
user="george",
host="localhost",
)
assert not ret
# Remove the user
ret = mysql.user_remove("george", host="localhost")
assert ret
# Remove the database
ret = mysql.db_remove("salt")
assert ret
def test_grant_binlog_monitor_add_revoke(mysql, mysql_container):
# The BINLOG MONITOR grant is only available for mariadb
if "mariadb" not in mysql_container.mysql_name:
pytest.skip(
"The BINLOG MONITOR grant is unavailable "
"for the {}:{} docker image.".format(
mysql_container.mysql_name, mysql_container.mysql_version
)
)
# The BINLOG MONITOR grant was added in mariadb 10.5.2
if version_cmp(mysql_container.mysql_version, "10.5.2") < 0:
pytest.skip(
"The BINLOG_MONITOR grant is unavailable "
"for the {}:{} docker image.".format(
mysql_container.mysql_name, mysql_container.mysql_version
)
)
# Create the database
ret = mysql.db_create("salt")
assert ret
# Create a user
ret = mysql.user_create(
"george",
host="localhost",
password="badpassword",
)
assert ret
# Grant privileges to user to specific table
ret = mysql.grant_add(
grant="BINLOG MONITOR",
database="salt.*",
user="george",
host="localhost",
)
assert ret
# Check the grant exists
ret = mysql.grant_exists(
grant="BINLOG MONITOR",
database="salt.*",
user="george",
host="localhost",
)
assert ret
# Revoke the global grant
ret = mysql.grant_revoke(
grant="BINLOG MONITOR",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Check the grant does not exist
ret = mysql.grant_exists(
grant="BINLOG MONITOR",
database="salt.*",
user="george",
host="localhost",
)
assert not ret
# Remove the user
ret = mysql.user_remove("george", host="localhost")
assert ret
# Remove the database
ret = mysql.db_remove("salt")
assert ret
def test_grant_replica_monitor_add_revoke(mysql, mysql_container):
# The REPLICA MONITOR grant is only available for mariadb
if "mariadb" not in mysql_container.mysql_name:
pytest.skip(
"The REPLICA MONITOR grant is unavailable "
"for the {}:{} docker image.".format(
mysql_container.mysql_name, mysql_container.mysql_version
)
)
# The REPLICA MONITOR grant was added in mariadb 10.5.9
if version_cmp(mysql_container.mysql_version, "10.5.9") < 0:
pytest.skip(
"The REPLICA MONITOR grant is unavailable "
"for the {}:{} docker image.".format(
mysql_container.mysql_name, mysql_container.mysql_version
)
)
# Create the database
ret = mysql.db_create("salt")
assert ret
# Create a user
ret = mysql.user_create(
"george",
host="localhost",
password="badpassword",
)
assert ret
# Grant privileges to user to specific table
ret = mysql.grant_add(
grant="REPLICA MONITOR",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Check the grant exists
ret = mysql.grant_exists(
grant="REPLICA MONITOR",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Revoke the global grant
ret = mysql.grant_revoke(
grant="REPLICA MONITOR",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Check the grant does not exist
ret = mysql.grant_exists(
grant="REPLICA MONITOR",
database="*.*",
user="george",
host="localhost",
)
assert not ret
# Remove the user
ret = mysql.user_remove("george", host="localhost")
assert ret
# Remove the database
ret = mysql.db_remove("salt")
assert ret
def test_grant_slave_monitor_add_revoke(mysql, mysql_container):
# The SLAVE MONITOR grant is only available for mariadb
if "mariadb" not in mysql_container.mysql_name:
pytest.skip(
"The SLAVE MONITOR grant is unavailable "
"for the {}:{} docker image.".format(
mysql_container.mysql_name, mysql_container.mysql_version
)
)
# The SLAVE MONITOR grant was added in mariadb 10.5.9
if version_cmp(mysql_container.mysql_version, "10.5.9") < 0:
pytest.skip(
"The SLAVE MONITOR grant is unavailable "
"for the {}:{} docker image.".format(
mysql_container.mysql_name, mysql_container.mysql_version
)
)
# Create the database
ret = mysql.db_create("salt")
assert ret
# Create a user
ret = mysql.user_create(
"george",
host="localhost",
password="badpassword",
)
assert ret
# Grant privileges to user to specific table
ret = mysql.grant_add(
grant="SLAVE MONITOR",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Check the grant exists
ret = mysql.grant_exists(
grant="SLAVE MONITOR",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Revoke the global grant
ret = mysql.grant_revoke(
grant="SLAVE MONITOR",
database="*.*",
user="george",
host="localhost",
)
assert ret
# Check the grant does not exist
ret = mysql.grant_exists(
grant="SLAVE MONITOR",
database="salt.*",
user="george",
host="localhost",
)
assert not ret
# Remove the user
ret = mysql.user_remove("george", host="localhost")
assert ret
# Remove the database
ret = mysql.db_remove("salt")
assert ret
def test_plugin_add_status_remove(mysql, mysql_combo):
if "mariadb" in mysql_combo.mysql_name: