Skip to content

Conversation

@ramnes
Copy link
Contributor

@ramnes ramnes commented Dec 6, 2025

Hey team,

Here is some work around #1069 and #1071 to allow a discussion based on an actual implementation.

I think the changes here are pretty obvious:

  • Renames;
  • A Credential can have multiple passwords now;
  • Added OnAuthSuccess and OnAuthFailure to the new AuthenticationHandler.

One side effect of the multiple passwords per user is that we can't have Xor update inplace anymore, because otherwise we would XOR the password we received every time we compare it to a new hash in the list of hashes to try.

About hashes – I moved the Credential passwords hash at comparison-time to avoid paying the full price for every connection. Let's say you have 1000 passwords to match against but match after 5 comparisons: it's better to hash only 5 passwords, not 1000.

@ramnes ramnes force-pushed the ramnes/credentials-passwords branch from 6ec779a to fb6703a Compare December 6, 2025 00:26
@ramnes ramnes changed the title Replace CredentialProvider with a more powerful AuthHandler Implement AuthenticationHandler for custom auth mechanisms Dec 6, 2025
@ramnes ramnes force-pushed the ramnes/credentials-passwords branch from fb6703a to 55d7dfb Compare December 6, 2025 00:30
@lance6716
Copy link
Collaborator

(This PR is a bit large than the other one you opened, I'll take a look later)

@ramnes
Copy link
Contributor Author

ramnes commented Dec 6, 2025

Yeah, the PR is a bit too large for my taste too. :)

I didn’t mean for it to be merged as-is, more as a starting point for discussion around the issues I opened. Happy to split it up once we have a clearer idea of what we want to do for each one!

Copy link
Collaborator

@lance6716 lance6716 left a comment

Choose a reason for hiding this comment

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

6 / 11 viewed, will review later

authPluginName = optionalAuthPluginName[0]
}

if !isAuthMethodSupported(authPluginName) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

seems in the old code we also support AUTH_CLEAR_PASSWORD. @dveeden Should we change isAuthMethodSupported to add that?

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 can also remove this check entirely if it doesn't make sense in that context, as there was no check before. Feel free to tell me.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it's better to remove the check. And let's wait @dveeden until all comments are resolved to understand if isAuthMethodSupported should also be updated

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we probably should support AUTH_CLEAR_PASSWORD.

Note that this is a client side plugin.

The usecase for this is to have the client send the cleartext password to the server which then allows the server to use this to authenticate against LDAP (via binding, a hash stored in ldap doesn't need this) or via PAM or anything else that is custom.

Note that on the client one needs to use --enable-cleartext-plugin or use set it via an option in their apps.

The risk with cleartext is obvious. It should be used only over TLS connections.

Note that caching_sha2_password has two options:

  • RSA keypair
  • TLS connection.

With a RSA keypair the public key has to be specified or you have to enable an option to fetch it from the server (which is insecure). I don't think this should be used.

With caching_sha2_password and an empty cache the password is transmitted in cleartext over a TLS connection. We should make sure we don't accept it over a non-TLS connection (except for UNIX domain socket)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dveeden Do you mean we should add AUTH_CLEAR_PASSWORD in isAuthMethodSupported, or outside of it? Do you think we should keep this check here?

@ramnes ramnes force-pushed the ramnes/credentials-passwords branch 4 times, most recently from 9720dc2 to 12a0543 Compare December 12, 2025 16:23
Copy link
Collaborator

@lance6716 lance6716 left a comment

Choose a reason for hiding this comment

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

rest lgtm

func (c *Conn) acquirePassword() error {
if c.credential.Password != "" {
func (c *Conn) acquireCredential() error {
if len(c.credential.Passwords) > 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems we can also use hasEmptyPassword here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, I'm not sure to understand why you would want to use hasEmptyPassword here.

We can't replace len(c.credential.Passwords) > 0 with c.credential.hasEmptyPassword.

The only scenario I could imagine would be to make the check more explicit with something like

if len(c.credential.Passwords) > 0 || c.credential.hasEmptyPassword() {
	return nil
}

but then it would be redundant, because if hasEmptyPassword() returns true, then len(Passwords) >) is true as well.

What do you have in mind?

authPluginName = optionalAuthPluginName[0]
}

if !isAuthMethodSupported(authPluginName) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it's better to remove the check. And let's wait @dveeden until all comments are resolved to understand if isAuthMethodSupported should also be updated

@dveeden dveeden self-requested a review December 15, 2025 07:41
Copy link
Collaborator

@dveeden dveeden left a comment

Choose a reason for hiding this comment

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

Seems ok on first glance.

authPluginName = optionalAuthPluginName[0]
}

if !isAuthMethodSupported(authPluginName) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we probably should support AUTH_CLEAR_PASSWORD.

Note that this is a client side plugin.

The usecase for this is to have the client send the cleartext password to the server which then allows the server to use this to authenticate against LDAP (via binding, a hash stored in ldap doesn't need this) or via PAM or anything else that is custom.

Note that on the client one needs to use --enable-cleartext-plugin or use set it via an option in their apps.

The risk with cleartext is obvious. It should be used only over TLS connections.

Note that caching_sha2_password has two options:

  • RSA keypair
  • TLS connection.

With a RSA keypair the public key has to be specified or you have to enable an option to fetch it from the server (which is insecure). I don't think this should be used.

With caching_sha2_password and an empty cache the password is transmitted in cleartext over a TLS connection. We should make sure we don't accept it over a non-TLS connection (except for UNIX domain socket)

@dveeden
Copy link
Collaborator

dveeden commented Dec 15, 2025

Could we mix authentication methods? E.g. a user having two authentication methods:

  1. Caching_sha2_password with hash abcdefg
  2. Auth_socket linked to username jdoe

This could be useful for migrating from one to another. Or when a authmethod has limitations (like auth_socket). Or it could be used for MFA

https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_multi_factor_authentication_methods.html

@ramnes
Copy link
Contributor Author

ramnes commented Dec 16, 2025

I’m not entirely sure, but I don’t think this is feasible. As discussed in #1069:

Playing a little more with this, I think the only way to allow for multiple passwords is actually to keep GetCredential as-is, but instead modify Credential so that it holds multiple passwords with a Passwords []string field.

My understanding of the MySQL protocol is that it's impossible to handle authentication against multiple auth plugins for a single user on the wire, even if the database implementation would support it.

For additional context: I previously tried to implement a server that stored credentials for both mysql_native_password and caching_sha2_password, with the goal of allowing any client to connect: older clients that only support mysql_native_password (and may lack newer auth capabilities), as well as newer clients that only support caching_sha2_password. I couldn’t make this work reliably.

From what I remember of these experiments, the only potentially viable strategy was for the server to announce mysql_native_password in the initial handshake and then request an auth switch to caching_sha2_password when needed, since the server speaks first in the MySQL handshake / authentication protocol. However, IIRC, when a client literally couldn't perform native password authentication (e.g. because it doesn’t have the plugin at all, such as the client installed with brew install mysql), the connection was failing before an AuthSwitchRequest could even be processed. The client couldn't be "rescued" by switching.

Given that, I’d prefer not to allow multiple authentication plugin names per user if we can’t guarantee that this will work end-to-end. Exposing this would likely be misleading for developers playing with go-mysql.

MFA is out-of-scope for me here.

@dveeden
Copy link
Collaborator

dveeden commented Dec 16, 2025

@ramnes Yes, I agree with that.

Note that newer MySQL clients are able to do both native and caching_sha2. There was a macOS specific issue that broke that, but that was fixed: Homebrew/homebrew-core#201853

Newer MySQL versions did remove the server part of mysql_native_password and moved the client part to a loadable module (which is auto loaded).

@ramnes ramnes force-pushed the ramnes/credentials-passwords branch from 12a0543 to 5c8bd7c Compare December 16, 2025 09:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants