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

feat: add do command to update the authentication plugin of MySQL users #1097

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Danyal-Faheem
Copy link
Contributor

@Danyal-Faheem Danyal-Faheem commented Jul 18, 2024

closes #1095

This PR is a continuation of the issue mentioned in #1089.

  • Add a do command to update the authentication plugin of MySQL users from mysql_native_password to caching_sha2_password.
  • Can specify either all to update all users or specific users to update the authentication plugin of.
  • Command expects entries present in the config in the format USER_MYSQL_USERNAME and USER_MYSQL_PASSWORD to work.

@Danyal-Faheem Danyal-Faheem self-assigned this Jul 18, 2024
@@ -43,6 +43,39 @@ def upgrade_from_nutmeg(context: click.Context, config: Config) -> None:
)


def get_mysql_change_authentication_plugin_query(config: Config) -> str:
"""Helper function to generate queries depending on the loaded plugins"""
Copy link
Contributor

Choose a reason for hiding this comment

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

the docstring can be upgraded to highlight what is done by default and what is done based on plugin's availability.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

# We need to first revert MySQL back to v8.1 to build the data dictionary on it
# And also to update the authentication plugin as it is disabled on v8.4
message = f"""Automatic release upgrade is unsupported in Kubernetes. If you are upgrading from Olive or an earlier release to Redwood, you
should upgrade the authentication plugin of your users. To upgrade, run something similar to:
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of saying run something similar, we should add the exact commands the user should run.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are the exact commands that the user should run. I've updated the wording in the message.

@DawoudSheraz DawoudSheraz requested a review from regisb July 30, 2024 07:27
@@ -43,6 +43,46 @@ def upgrade_from_nutmeg(context: click.Context, config: Config) -> None:
)


def get_mysql_change_authentication_plugin_query(config: Config) -> str:
Copy link
Contributor

Choose a reason for hiding this comment

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

Wondering if we should have an override/additional param provided by user to only upgrade a particular plugin's database. Lets say they have upgraded the default already and do not have plugins enabled. Once plugins are enabled, they will be re-doing the migration for default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I was also thinking that we could add a --users option with comma separated name of users to the do command. This way, users can also choose to upgrade their choice of databases.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added a --users option as well.

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

host = "%"
Copy link
Contributor

Choose a reason for hiding this comment

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

why is the host set to %?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The host for users created by Tutor is always set to '%'. The users created by MySQL itself uses the 'localhost' host.

@Danyal-Faheem Danyal-Faheem force-pushed the danyalfaheem/mysql-authentication-plugin-change branch 3 times, most recently from adaa02d to 820cebe Compare August 21, 2024 13:25
@Danyal-Faheem Danyal-Faheem force-pushed the danyalfaheem/mysql-authentication-plugin-change branch from ef6022d to 4d0042c Compare August 30, 2024 14:37
@@ -44,7 +44,11 @@ services:
--character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci
--binlog-expire-logs-seconds=259200
# We only require this option for MySQL 8.4 and above
# Breaks MySQL for previous versions as this option does not exist on versions earlier than 8.4
{% if DOCKER_IMAGE_MYSQL.split(':')[-1] == "latest" or (DOCKER_IMAGE_MYSQL.split(':')[-1].split('.') | map('int') | list >= '8.4.0'.split('.') | map('int') | list) -%}
Copy link
Contributor

@DawoudSheraz DawoudSheraz Sep 4, 2024

Choose a reason for hiding this comment

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

nit: we can set DOCKER_IMAGE_MYSQL.split(':')[-1] to a variable and use that in comparison. It will make the code readable.

docs/local.rst Outdated

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::
Copy link
Contributor

Choose a reason for hiding this comment

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

this paragraph can be reworded to focus that for any particular user, the command expects the configuration settings in a certain format. Then, the myuser examples can be added afterwards/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've updated this paragraph.

Copy link
Contributor

@DawoudSheraz DawoudSheraz left a comment

Choose a reason for hiding this comment

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

While testing, I came across the following error

Error: Username or Password for User mfe not found in config. Please make sure that the following entries are present in the configuration:
MFE_MYSQL_USERNAME
MFE_MYSQL_PASSWORD

The current code raises the exception if config is not found. It is iterating on all the plugins. Not all plugins would have DB users. Instead of raising, we can log warning and continue.

@Danyal-Faheem Danyal-Faheem changed the title fix: update authentication plugin of MySQL users in v8.4 feat: add do command to update the authentication plugin of MySQL users Sep 9, 2024
@Faraz32123
Copy link
Contributor

Hello @regisb, can u have a look at it for it's final iteration?

tutor/fmt.py Outdated
@@ -46,6 +46,14 @@ def alert(text: str) -> str:
return click.style("⚠️ " + text, fg="yellow", bold=True)


def warning(text: str) -> str:
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the purpose of warning when we already have alert?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alert prints to standard error and this seemed like a situation where we just wanted to display a warning rather than an error.

Copy link
Contributor

Choose a reason for hiding this comment

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

Let's keep an alert. We don't want to write to stdout because it might mangle output from other commands, such as tutor config printvalue/printroot. And if we write to stderr than we are better off just using alert.

tutor/templates/local/docker-compose.yml Outdated Show resolved Hide resolved
@@ -315,6 +317,49 @@ def sqlshell(args: list[str]) -> t.Iterable[tuple[str, str]]:
yield ("lms", command)


@click.command(context_settings={"ignore_unknown_options": True})
@click.option(
Copy link
Contributor

Choose a reason for hiding this comment

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

This should not be an option, but an argument, because it's mandatory. The command signature should not be:

 tutor local do update_mysql_authentication_plugin --users=regis,danyal

but:

tutor local do update_mysql_authentication_plugin regis danyal

Moreover, we should be able to update all users with users=all:

tutor local do update_mysql_authentication_plugin all

tutor/commands/jobs.py Show resolved Hide resolved
fmt.echo_info(
f"Authentication plugin of user {username} will be updated to caching_sha2_password"
)
return f"ALTER USER '{username}'@'{host}' IDENTIFIED with caching_sha2_password BY '{password}';"
Copy link
Contributor

Choose a reason for hiding this comment

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

This function is extremely confusing, and there are many ways in which it can fail. In order to improve it, I'd like to understand if it's possible to automatically detect all mysql users that were created with the legacy authentication plugin?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The only way to do that would be to get the output of a MySQL query inside tutor codebase. I am not aware of any method that we can use to get the output of a command run inside a container inside the tutor codebase.

Copy link
Contributor

Choose a reason for hiding this comment

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

Assuming we could get the output of a mysql query in tutor, what would be that query?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Something similar to this:

select user from mysql.user where plugin='mysql_native_password' and host='%';

We need to limit the host to '%' as Tutor uses that host value by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hey Regis, any thoughts on how we should proceed with this?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure. basically, we need to perform all tasks inside a single templated bash script -- including the mysql --user... part. This bash script can be templated, such that we don't have to pass config values to the script-generating function. I'm sorry I don't have time to think about this more...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So maybe something similar to a stored procedure? Something akin to what we did in #1079?

We could get the names of all the users and update them inside that procedure. Although, I'd have to look into how we would manage passwords then.

@Danyal-Faheem Danyal-Faheem marked this pull request as draft October 28, 2024 15:03
@Danyal-Faheem Danyal-Faheem force-pushed the danyalfaheem/mysql-authentication-plugin-change branch from c872809 to 458c6de Compare November 24, 2024 15:46
@Danyal-Faheem Danyal-Faheem force-pushed the danyalfaheem/mysql-authentication-plugin-change branch from 458c6de to 98d6753 Compare November 29, 2024 15:09
@Danyal-Faheem Danyal-Faheem marked this pull request as ready for review November 29, 2024 15:11
Copy link
Contributor

@DawoudSheraz DawoudSheraz left a comment

Choose a reason for hiding this comment

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

nit: once merged and rebase to sumac, we can add a "warning" in upgrade that users should do the auth plugin updates to ensure future upgrades of MySQL do not break their systems.

@DawoudSheraz DawoudSheraz requested a review from regisb December 6, 2024 14:08
user_uppercase = user.upper()
if not (
f"{user_uppercase}_MYSQL_USERNAME" in config
and f"{user_uppercase}_MYSQL_PASSWORD" in config
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks very fragile. We can't rely on the arbitrary naming of some configuration settings to auto-detect user names.

Copy link
Contributor Author

@Danyal-Faheem Danyal-Faheem Dec 13, 2024

Choose a reason for hiding this comment

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

We found that this was a convention used across the plugins, but I can understand your position here. However, we still need the exact username and password to perform this operation. Therefore, I propose that we can provide an option to provide the exact username and password. Something similar to:

tutor local do update-mysql-authentication-plugin discovery ecommerce --usernames discovery-username,ecommerce-username --passwords discovery-password,ecommerce-password

This isn't very intuitive but since this would be a one time operation, maybe it is fine? Another con I see here is that users will have to have passwords out in the open for headless scripts unless they utilize environment variables.

I am open to any suggestions you might have.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In review
Development

Successfully merging this pull request may close these issues.

5 participants