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 #171

Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
[[ "$MYSQL_VERSION" == "8.0" ]] && $(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot < docker-entrypoint-initdb.d/caching_sha2_password_user.sql
$(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot < docker-entrypoint-initdb.d/native_password_user.sql
$(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot < docker-entrypoint-initdb.d/x509_user.sql
$(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot < docker-entrypoint-initdb.d/cleartext_user.sql
- name: Install dependencies
run: |
cd contrib/ruby
Expand Down
16 changes: 14 additions & 2 deletions contrib/ruby/ext/trilogy-ruby/cext.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
VALUE Trilogy_CastError;
static VALUE Trilogy_BaseConnectionError, Trilogy_ProtocolError, Trilogy_SSLError, Trilogy_QueryError,
Trilogy_ConnectionClosedError,
Trilogy_TimeoutError, Trilogy_SyscallError, Trilogy_Result, Trilogy_EOFError;
Trilogy_TimeoutError, Trilogy_SyscallError, Trilogy_Result, Trilogy_EOFError, Trilogy_AuthPluginError;

static ID id_socket, id_host, id_port, id_username, id_password, id_found_rows, id_connect_timeout, id_read_timeout,
id_write_timeout, id_keepalive_enabled, id_keepalive_idle, id_keepalive_interval, id_keepalive_count,
id_ivar_affected_rows, id_ivar_fields, id_ivar_last_insert_id, id_ivar_rows, id_ivar_query_time, id_password,
id_database, id_ssl_ca, id_ssl_capath, id_ssl_cert, id_ssl_cipher, id_ssl_crl, id_ssl_crlpath, id_ssl_key,
id_database, id_enable_cleartext_plugin, id_ssl_ca, id_ssl_capath, id_ssl_cert, id_ssl_cipher, id_ssl_crl, id_ssl_crlpath, id_ssl_key,
id_ssl_mode, id_tls_ciphersuites, id_tls_min_version, id_tls_max_version, id_multi_statement, id_multi_result,
id_from_code, id_from_errno, id_connection_options, id_max_allowed_packet;

Expand Down Expand Up @@ -156,6 +156,10 @@ static void handle_trilogy_error(struct trilogy_ctx *ctx, int rc, const char *ms
rb_raise(Trilogy_EOFError, "%" PRIsVALUE ": TRILOGY_CLOSED_CONNECTION", rbmsg);
}

case TRILOGY_AUTH_PLUGIN_ERROR: {
rb_raise(Trilogy_AuthPluginError, "%" PRIsVALUE ": TRILOGY_AUTH_PLUGIN_ERROR", rbmsg);
}

case TRILOGY_UNSUPPORTED: {
rb_raise(Trilogy_BaseConnectionError, "%" PRIsVALUE ": TRILOGY_UNSUPPORTED", rbmsg);
}
Expand Down Expand Up @@ -531,6 +535,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;
}

if (RTEST(rb_hash_aref(opts, ID2SYM(id_found_rows)))) {
connopt.flags |= TRILOGY_CAPABILITIES_FOUND_ROWS;
}
Expand Down Expand Up @@ -1202,6 +1210,9 @@ RUBY_FUNC_EXPORTED void Init_cext(void)
Trilogy_EOFError = rb_const_get(Trilogy, rb_intern("EOFError"));
rb_global_variable(&Trilogy_EOFError);

Trilogy_AuthPluginError = rb_const_get(Trilogy, rb_intern("AuthPluginError"));
rb_global_variable(&Trilogy_AuthPluginError);
composerinteralia marked this conversation as resolved.
Show resolved Hide resolved

id_socket = rb_intern("socket");
id_host = rb_intern("host");
id_port = rb_intern("port");
Expand All @@ -1217,6 +1228,7 @@ RUBY_FUNC_EXPORTED void Init_cext(void)
id_keepalive_count = rb_intern("keepalive_count");
id_keepalive_interval = rb_intern("keepalive_interval");
id_database = rb_intern("database");
id_enable_cleartext_plugin = rb_intern("enable_cleartext_plugin");
id_ssl_ca = rb_intern("ssl_ca");
id_ssl_capath = rb_intern("ssl_capath");
id_ssl_cert = rb_intern("ssl_cert");
Expand Down
5 changes: 5 additions & 0 deletions contrib/ruby/lib/trilogy/error.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,9 @@ class ConnectionClosed < IOError
# attempted on a socket which previously encountered an error.
class EOFError < BaseConnectionError
end

# Occurs when the server request an auth switch to an incompatible
# authentication plugin
class AuthPluginError < Trilogy::BaseConnectionError
end
end
16 changes: 16 additions & 0 deletions contrib/ruby/test/auth_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require "test_helper"

class AuthTest < TrilogyTest
Copy link
Collaborator

Choose a reason for hiding this comment

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

Does it make sense for the mysql_native_password and caching_sha2_password connection tests from client_test.rb to live in this test suite too?

Copy link
Contributor

Choose a reason for hiding this comment

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

Done!

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, I could only add it for native password. We don't use cache_sha2 for mysql 5 so the ruby test fails there. I'm going to revisit this after this PR.

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 we already have that test elsewhere, but we can sort it out later. We might want to move all connection-related stuff into something like ConnectionTest.

def test_cleartext_auth_plugin
client = new_tcp_client username: "cleartext_user", password: "password", enable_cleartext_plugin: true
refute_nil client
ensure
ensure_closed client
end

def test_cleartext_auth_plugin_disabled
assert_raises Trilogy::AuthPluginError do
new_tcp_client username: "cleartext_user", password: "password"
end
end
end
3 changes: 2 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ services:
volumes:
- "db-data:/var/lib/mysql"
- ./docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
- ./test/auth_plugins/${MYSQL_VERSION}/auth_test_plugin.so:/usr/lib/mysql/plugin/auth_test_plugin.so
app:
image: ghcr.io/trilogy-libraries/trilogy/ci-app:distro-${DISTRIBUTION_SLUG}-ruby-${RUBY_VERSION}-mysql-${MYSQL_VERSION}
privileged: true
Expand All @@ -24,7 +25,7 @@ services:
args:
- DISTRIBUTION=${DISTRIBUTION}
- RUBY_VERSION=${RUBY_VERSION}
cache_from:
cache_from:
- ghcr.io/trilogy-libraries/trilogy/ci-app:distro-${DISTRIBUTION_SLUG}-ruby-${RUBY_VERSION}-mysql-${MYSQL_VERSION}
environment:
MYSQL_HOST: db.local
Expand Down
4 changes: 4 additions & 0 deletions docker-entrypoint-initdb.d/cleartext_user.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
INSTALL PLUGIN cleartext_plugin_server SONAME 'auth_test_plugin.so';
CREATE USER 'cleartext_user'@'%';
GRANT ALL PRIVILEGES ON test.* TO 'cleartext_user'@'%';
ALTER USER 'cleartext_user'@'%' IDENTIFIED WITH cleartext_plugin_server BY 'password';
3 changes: 2 additions & 1 deletion inc/trilogy/error.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
XX(TRILOGY_AUTH_SWITCH, -19) \
XX(TRILOGY_MAX_PACKET_EXCEEDED, -20) \
XX(TRILOGY_UNKNOWN_TYPE, -21) \
XX(TRILOGY_TIMEOUT, -22)
XX(TRILOGY_TIMEOUT, -22) \
XX(TRILOGY_AUTH_PLUGIN_ERROR, -23)

enum {
#define XX(name, code) name = code,
Expand Down
4 changes: 3 additions & 1 deletion inc/trilogy/protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -443,14 +443,16 @@ int trilogy_build_auth_packet(trilogy_builder_t *builder, const char *user, cons
* pass_len - The length of password in bytes.
* auth_plugin - Plugin authentication mechanism that the server requested.
* scramble - The scramble value received from the server.
* enable_cleartext_plugin - Send cleartext password if requested by server.
*
* Return values:
* TRILOGY_OK - The packet was successfully built and written to the
* builder's internal buffer.
* TRILOGY_SYSERR - A system error occurred, check errno.
* TRILOGY_AUTH_PLUGIN_ERROR - The server requested auth plugin is not supported.
*/
int trilogy_build_auth_switch_response_packet(trilogy_builder_t *builder, const char *pass, size_t pass_len,
const char *auth_plugin, const char *scramble);
const char *auth_plugin, const char *scramble, const bool enable_cleartext_plugin);

/* trilogy_build_change_db_packet - Build a change database command packet. This
* command will change the default database for the connection.
Expand Down
2 changes: 2 additions & 0 deletions inc/trilogy/socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ typedef struct {
uint16_t keepalive_count;
uint16_t keepalive_interval;

bool enable_cleartext_plugin;

TRILOGY_CAPABILITIES_t flags;

size_t max_allowed_packet;
Expand Down
4 changes: 2 additions & 2 deletions script/cibuild
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ output_fold() {

# Only echo the tags when in CI_MODE
if [ "$CI_MODE" ]; then
echo "::group::{$label}"
echo "::group::${label}"
fi

# run the remaining arguments. If the command exits non-0, the `||` will
Expand All @@ -34,7 +34,7 @@ output_fold() {

function cleanup() {
echo
echo "::group::{Shutting down services...}"
echo "::group::Shutting down services..."
docker compose down -v
echo "::endgroup::"
}
Expand Down
7 changes: 4 additions & 3 deletions src/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,9 @@ static int read_auth_switch_packet(trilogy_conn_t *conn, trilogy_handshake_t *ha
}

if (strcmp("mysql_native_password", auth_switch_packet.auth_plugin) &&
strcmp("caching_sha2_password", auth_switch_packet.auth_plugin)) {
// Only support native password & caching sha2 password here.
strcmp("caching_sha2_password", auth_switch_packet.auth_plugin) &&
strcmp("mysql_clear_password", auth_switch_packet.auth_plugin)) {
// Only support native password, caching sha2 and cleartext password here.
return TRILOGY_PROTOCOL_VIOLATION;
}

Expand Down Expand Up @@ -391,7 +392,7 @@ int trilogy_auth_switch_send(trilogy_conn_t *conn, const trilogy_handshake_t *ha

rc = trilogy_build_auth_switch_response_packet(&builder, conn->socket->opts.password,
conn->socket->opts.password_len, handshake->auth_plugin,
handshake->scramble);
handshake->scramble, conn->socket->opts.enable_cleartext_plugin);

if (rc < 0) {
return rc;
Expand Down
21 changes: 16 additions & 5 deletions src/protocol.c
Original file line number Diff line number Diff line change
Expand Up @@ -609,21 +609,32 @@ int trilogy_build_auth_clear_password(trilogy_builder_t *builder, const char *pa
}

int trilogy_build_auth_switch_response_packet(trilogy_builder_t *builder, const char *pass, size_t pass_len,
const char *auth_plugin, const char *scramble)
const char *auth_plugin, const char *scramble, const bool enable_cleartext_plugin)
{
int rc = TRILOGY_OK;
unsigned int auth_response_len = 0;
uint8_t auth_response[EVP_MAX_MD_SIZE];

if (pass_len > 0) {
if (!strcmp("caching_sha2_password", auth_plugin)) {
trilogy_pack_scramble_sha2_hash(scramble, pass, pass_len, auth_response, &auth_response_len);
if (!strcmp("mysql_clear_password", auth_plugin)) {
if (enable_cleartext_plugin) {
CHECKED(trilogy_builder_write_buffer(builder, pass, pass_len));
} else {
return TRILOGY_AUTH_PLUGIN_ERROR;
}
} else {
trilogy_pack_scramble_native_hash(scramble, pass, pass_len, auth_response, &auth_response_len);
if (!strcmp("caching_sha2_password", auth_plugin)) {
trilogy_pack_scramble_sha2_hash(scramble, pass, pass_len, auth_response, &auth_response_len);
} else if (!strcmp("mysql_native_password", auth_plugin)) {
trilogy_pack_scramble_native_hash(scramble, pass, pass_len, auth_response, &auth_response_len);
} else {
return TRILOGY_AUTH_PLUGIN_ERROR;
}

CHECKED(trilogy_builder_write_buffer(builder, auth_response, auth_response_len));
}
}

CHECKED(trilogy_builder_write_buffer(builder, auth_response, auth_response_len));
trilogy_builder_finalize(builder);

return TRILOGY_OK;
Expand Down
Binary file added test/auth_plugins/5.7/auth_test_plugin.so
Copy link
Contributor

Choose a reason for hiding this comment

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

This probably shouldn't be committed?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we be building our own MySQL image instead of committing the plugins?

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 we can and should, but that would be quite a big detour from the original intent of this PR. I suggest we land as-is then I can spend some time refactoring the test setup.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Follow up sounds good to me 👍 Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

This has been addressed here: #174

Binary file not shown.
Binary file added test/auth_plugins/8/auth_test_plugin.so
Binary file not shown.
Loading