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

Implement support for cleartext authentication plugin #168

Closed

Conversation

wildmaples
Copy link
Contributor

@wildmaples wildmaples commented Mar 20, 2024

Resolves: #157

The cleartext pluggable authentication is a client side plugin that can be set through the --enable-cleartext-plugin flag. We override the auth plugin received from the server inside the client, before building and sending the auth packet.

I'm not sure if people like the variable name enable_cleartext_plugin but I defaulted to that since that's the flag.

} else {
auth_plugin = handshake->auth_plugin;
}

rc = trilogy_build_auth_packet(&builder, conn->socket->opts.username, conn->socket->opts.password,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can refactor this function to trilogy_build_auth_packet(&builder, conn->socket->opts, handshake, auth_plugin) in another PR.

src/protocol.c Outdated
@@ -568,7 +568,9 @@ int trilogy_build_auth_packet(trilogy_builder_t *builder, const char *user, cons

if (pass_len > 0) {
// Fallback to te default unless we have SHA2 requested
Copy link

@gajwani gajwani Mar 20, 2024

Choose a reason for hiding this comment

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

nit: I think this comment should likely be moved down to between line 573/4 and another comment should be added here to add context to the new "cleartext" behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great call, thanks! I didn't add a comment for the cleartext if-block because I think it's pretty straightforward. Happy to add it if maintainers think it's helpful.

auth_plugin = "mysql_clear_password";
} else {
auth_plugin = handshake->auth_plugin;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think there's a difference between enabling the plugin so that it's used of the server switches to it and making it the default advertised by the client.

Is the behavior the same with libmysqlclient that if it's enabled, it also becomes the default that the client advertises? Or does it only allow switching to it there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just so I understand correctly, you're asking if the server will accept the cleartext plugin password without the client informing the server of the plugin?

If so, then I believe that to be the case. According to the libmysqlclient source code / documentation, we don't need to preemptively inform the server that we are using the cleartext plugin before the authentication connection. To put it in another way, if cleartext plugin is enabled, it does become the "default" that the client advertises, in a sense where the client can just send it to the server.

Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if we connect with enable_cleartext_plugin :true but then the server handshake indicates a non-cleartext plugin like caching_sha2_password or whatever? Will this code ignore what the server told us and send the cleartext password anyway? Does that then turn into an auth switch?

(I'm still learning about how these plugins work, so not sure I'm making sense.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, the first handshake we get back from the server will indicate the server's authentication plugin preference. However, according to the MySQL docs on "Determining Authentication Method", the client does not have to use the server's authentication method.

When including authentication reply in the Protocol::HandshakeResponse:, client is not obligated to use the same authentication method that was used by the server in the Protocol::Handshake packet.

As the code is right now, we will send the cleartext password but the server can still respond with an auth switch request, and we will do so accordingly. We think this is OK because the request from the user to use cleartext password has been fulfilled. If the server is still not happy then it's OK for us to do the auth switch.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you have a good way of testing out one of the plugins that needs a cleartext password? I'm curious what plugin is advertised in the handshake for those. Like, if the server wants to use authentication_pam does the handshake advertise the server plugin authentication_pam or the client plugin mysql_clear_password?

Copy link
Contributor

@composerinteralia composerinteralia Apr 3, 2024

Choose a reason for hiding this comment

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

By the way, I should have started by saying thank you for working on this!

@HParker and I found a test server plugin that uses mysql_clear_password as the client plugin, and I was able to enable it in a locally with:

INSTALL PLUGIN cleartext_plugin_server SONAME 'auth_test_plugin.so';
CREATE USER 'cleartext_user'@'localhost' IDENTIFIED WITH cleartext_plugin_server BY 'password';

Then I connected via the command line client and looked at the packets:

mysql --protocol=tcp --ssl-mode=disable -p -u cleartext_user --enable-cleartext-plugin
  1. Server sends handshake, with auth plugin caching_sha2_password
  2. Client sends handshake response, still with auth plugin caching_sha2_password
  3. Server sends auth switch request, now with auth plugin mysql_clear_password
  4. Client sends auth switch response with just the cleartext password and nothing else
  5. Server sends OK and we are connected

That's more what I was expecting. The server is telling the client to use mysql_clear_password, and the --enable-cleartext-plugin option only affects step 4. While a client can choose a different plugin than what the server advertised in the handshake, I don't know how the server handles mysql_clear_password in particular since it is a client-side-only plugin.

Trying to connect to this same user with Trilogy using this PR results in:

Trilogy.new(host: '127.0.0.1', username: 'cleartext_user', password: 'password', enable_cleartext_plugin: true)
  1. Server sends handshake, with auth plugin caching_sha2_password
  2. Client sends handshake response, switching to mysql_clear_password
  3. Server responds with "Access denied"

I'm not totally sure why that fails (including with the fix so we send the password). But maybe we should try to behave more like mysql here?

// Fallback to te default unless we have SHA2 requested
if (!strcmp("caching_sha2_password", auth_plugin)) {
if (!strcmp("mysql_clear_password", auth_plugin)) {
memcpy(auth_response, pass, pass_len);
Copy link
Contributor

Choose a reason for hiding this comment

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

Will this memcpy segfault if pass_len is longer than auth_response?

Copy link
Contributor

Choose a reason for hiding this comment

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

We're also not setting auth_response_len, which we check below to decide whether to write it at all.

err = trilogy_build_auth_packet(&builder, user, pass, strlen(pass), NULL, 45, "mysql_clear_password", scramble, 0);
ASSERT_OK(err);

static const uint8_t expected[] = {
Copy link
Contributor

@composerinteralia composerinteralia Apr 2, 2024

Choose a reason for hiding this comment

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

Should there be a cleartext password in this expected output? I think it's the same as the same bytes as in test_build_auth_packet_cleartext (although a little difficult to visually diff). The missing auth_response_len would explain it, I think.

auth_plugin = "mysql_clear_password";
} else {
auth_plugin = handshake->auth_plugin;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens if we connect with enable_cleartext_plugin :true but then the server handshake indicates a non-cleartext plugin like caching_sha2_password or whatever? Will this code ignore what the server told us and send the cleartext password anyway? Does that then turn into an auth switch?

(I'm still learning about how these plugins work, so not sure I'm making sense.)

@@ -522,6 +522,10 @@ static VALUE rb_trilogy_connect(VALUE self, VALUE encoding, VALUE charset, VALUE
connopt.flags |= TRILOGY_CAPABILITIES_CONNECT_WITH_DB;
}

if (RTEST(rb_hash_aref(opts, ID2SYM(id_enable_cleartext_plugin)))) {
connopt.enable_cleartext_plugin = true;
Copy link
Contributor

@composerinteralia composerinteralia Apr 2, 2024

Choose a reason for hiding this comment

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

I wonder if we should warn or refuse to do this if the appropriate ssl options are not set.

@wildmaples
Copy link
Contributor Author

Thanks for the feedback everyone! Chatted with @composerinteralia on Zoom and closing this in favour of: #171

@wildmaples wildmaples closed this Apr 3, 2024
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.

Support for cleartext password
5 participants