Skip to content

Commit

Permalink
feat: add do command to update the authentication plugin of MySQL use…
Browse files Browse the repository at this point in the history
…rs to caching_sha2_password

closes #1095
  • Loading branch information
Danyal-Faheem committed Aug 21, 2024
1 parent 53cffff commit 820cebe
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- [Improvement] Add a do command to update the authentication plugin of existing MySQL users from mysql_native_password to caching_sha2_password for compatibility with MySQL v8.4.0 and above. (by @Danyal-Faheem)
20 changes: 20 additions & 0 deletions docs/local.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,26 @@ The default Open edX theme is rather bland, so Tutor makes it easy to switch to

Out of the box, only the default "open-edx" theme is available. We also developed `Indigo, a beautiful, customizable theme <https://github.com/overhangio/indigo>`__ which is easy to install with Tutor.

.. _update_mysql_authentication_plugin:

Updating the authentication plugin of MySQL users
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

As of MySQL v8.4.0, the ``mysql_native_password`` authentication plugin has been deprecated. Users created with this authentication plugin should ideally be updated to use the latest ``caching_sha2_password`` authentication plugin.

Tutor makes it easy do so with this handy command::

tutor local do update_mysql_authentication_plugin

If you only want to update the authentication plugin of specific users, you can use the ``--users`` option. This option takes comma seperated names of users to upgrade::

tutor local do update_mysql_authentication_plugin --users=discovery,ecommerce

Do note that if you are updating a specific user, there should be corresponding entries in the configuration for the mysql username and password for that user. For example, if you are trying to update the user ``myuser``, the following case sensitive entries need to be present in the configuration::

MYUSER_MYSQL_USERNAME
MYUSER_MYSQL_PASSWORD

Running arbitrary ``manage.py`` commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
7 changes: 7 additions & 0 deletions docs/troubleshooting.rst
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,10 @@ NPM Dependency Conflict When overriding ``@edx/frontend-component-header`` or ``
----------------------------------------------------------------------------------------------------------------

The detailed steps are mentioned in `tutor-mfe <https://github.com/overhangio/tutor-mfe?tab=readme-ov-file#npm-dependency-conflict-when-overriding-edxfrontend-component-header-or-edxfrontend-component-footer>`__ documentation.

"Plugin 'mysql_native_password' is not loaded"
----------------------------------------------

This issue can occur when Tutor is upgraded from v15 (Olive) or earlier to v18 (Redwood) because the users created in Tutor v15 utilize the mysql_native_password authentication plugin by default. This plugin has been deprecated as of MySQL v8.4.0 which is the default MySQL server used in Tutor v18.

The handy :ref:`update_mysql_authentication_plugin <update_mysql_authentication_plugin>` do command in tutor can be used to fix this issue.
21 changes: 21 additions & 0 deletions tests/commands/test_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,24 @@ def test_set_theme(self) -> None:
self.assertIn("lms-job", dc_args)
self.assertIn("assign_theme('beautiful', 'domain1')", dc_args[-1])
self.assertIn("assign_theme('beautiful', 'domain2')", dc_args[-1])

def test_update_mysql_authentication_plugin(self) -> None:
with temporary_root() as root:
self.invoke_in_root(root, ["config", "save"])
with patch("tutor.utils.docker_compose") as mock_docker_compose:
result = self.invoke_in_root(
root,
[
"local",
"do",
"update-mysql-authentication-plugin",
],
)
dc_args, _dc_kwargs = mock_docker_compose.call_args

self.assertIsNone(result.exception)
self.assertEqual(0, result.exit_code)
self.assertIn("lms-job", dc_args)
self.assertIn("caching_sha2_password", dc_args[-1])
self.assertIn("openedx", dc_args[-1])
self.assertIn("root", dc_args[-1])
41 changes: 40 additions & 1 deletion tutor/commands/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
from typing_extensions import ParamSpec

from tutor import config as tutor_config
from tutor import env, fmt, hooks
from tutor import env, fmt, hooks, plugins
from tutor.hooks import priorities
from tutor.utils import get_mysql_change_authentication_plugin_query


class DoGroup(click.Group):
Expand Down Expand Up @@ -315,6 +316,43 @@ def sqlshell(args: list[str]) -> t.Iterable[tuple[str, str]]:
yield ("lms", command)


@click.command(context_settings={"ignore_unknown_options": True})
@click.option(
"--users",
is_flag=False,
nargs=1,
help="Specific users to upgrade the authentication plugin of. Requires comma-seperated values with no space in-between.",
)
def update_mysql_authentication_plugin(users: str) -> t.Iterable[tuple[str, str]]:
"""
Update the authentication plugin of MySQL users from mysql_native_password to caching_sha2_password
Handy command used when upgrading to v8.4 of MySQL which deprecates mysql_native_password
"""

context = click.get_current_context().obj
config = tutor_config.load(context.root)

if not config["RUN_MYSQL"]:
fmt.echo_info(
f"You are not running MySQL (RUN_MYSQL=False). It is your "
f"responsibility to update the authentication plugin of mysql users."
)
return

users_to_update = users.split(",") if users else list(plugins.iter_loaded())

query = get_mysql_change_authentication_plugin_query(
config, users_to_update, not users
)

mysql_command = (
"mysql --user={{ MYSQL_ROOT_USERNAME }} --password={{ MYSQL_ROOT_PASSWORD }} --host={{ MYSQL_HOST }} --port={{ MYSQL_PORT }} --database={{ OPENEDX_MYSQL_DATABASE }} "
+ shlex.join(["-e", query])
)

yield ("lms", mysql_command)


def add_job_commands(do_command_group: click.Group) -> None:
"""
This is meant to be called with the `local/dev/k8s do` group commands, to add the
Expand Down Expand Up @@ -397,5 +435,6 @@ def do_callback(service_commands: t.Iterable[tuple[str, str]]) -> None:
print_edx_platform_setting,
settheme,
sqlshell,
update_mysql_authentication_plugin,
]
)
2 changes: 1 addition & 1 deletion tutor/templates/k8s/deployments.yml
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ spec:
- "--character-set-server=utf8mb4"
- "--collation-server=utf8mb4_unicode_ci"
- "--binlog-expire-logs-seconds=259200"
- "--mysql-native-password=ON"
{% if DOCKER_IMAGE_MYSQL >= "docker.io/mysql:8.4.0" -%}- "--mysql-native-password=ON"{%- endif %}
env:
- name: MYSQL_ROOT_PASSWORD
value: "{{ MYSQL_ROOT_PASSWORD }}"
Expand Down
2 changes: 1 addition & 1 deletion tutor/templates/local/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ services:
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
--binlog-expire-logs-seconds=259200
--mysql-native-password=ON
{% if DOCKER_IMAGE_MYSQL >= "docker.io/mysql:8.4.0" -%}--mysql-native-password=ON{%- endif %}
restart: unless-stopped
user: "999:999"
volumes:
Expand Down
66 changes: 66 additions & 0 deletions tutor/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from Crypto.PublicKey.RSA import RsaKey

from . import exceptions, fmt
from tutor.types import Config, ConfigValue


def encrypt(text: str) -> str:
Expand Down Expand Up @@ -366,3 +367,68 @@ def format_table(rows: List[Tuple[str, ...]], separator: str = "\t") -> str:
# Append EOL at all lines but the last one
formatted += "\n"
return formatted


def get_mysql_change_authentication_plugin_query(
config: Config, users: List[str], all_users: bool
) -> str:
"""
Generates SQL queries to update the authentication plugin for MySQL users.
This method constructs queries to change the authentication plugin to
`caching_sha2_password`. User credentials must be provided in the tutor
configuration under the keys `<user>_MYSQL_USERNAME` and `<user>_MYSQL_PASSWORD`.
Args:
config (Config): Tutor configuration object
users (List[str]): List of specific MySQL users to update.
all_users (bool): Flag indicating whether to include ROOT and OPENEDX users
in addition to those specified in the `users` list.
Returns:
str: A string containing the SQL queries to execute.
Raises:
TutorError: If any user in the `users` list does not have corresponding
username or password entries in the configuration.
"""

host = "%"
query = ""

def generate_mysql_authentication_plugin_update_query(
username: ConfigValue, password: ConfigValue, host: str
) -> str:
return f"ALTER USER '{username}'@'{host}' IDENTIFIED with caching_sha2_password BY '{password}';"

def generate_user_queries(users: List[str]) -> str:
query = ""
for user in users:
user_uppercase = user.upper()
if not (
f"{user_uppercase}_MYSQL_USERNAME" in config
and f"{user_uppercase}_MYSQL_PASSWORD" in config
):
raise exceptions.TutorError(
f"Username or Password for User {user} not found in config. "
f"Please make sure that the following entries are present in the configuration:\n"
f"{user_uppercase}_MYSQL_USERNAME\n{user_uppercase}_MYSQL_PASSWORD"
)
query += generate_mysql_authentication_plugin_update_query(
config[f"{user_uppercase}_MYSQL_USERNAME"],
config[f"{user_uppercase}_MYSQL_PASSWORD"],
host,
)
return query

if not all_users:
return generate_user_queries(users)

query += generate_mysql_authentication_plugin_update_query(
config["MYSQL_ROOT_USERNAME"], config["MYSQL_ROOT_PASSWORD"], host
)
query += generate_mysql_authentication_plugin_update_query(
config["OPENEDX_MYSQL_USERNAME"], config["OPENEDX_MYSQL_PASSWORD"], host
)

return query + generate_user_queries(users)

0 comments on commit 820cebe

Please sign in to comment.