Skip to content

Commit

Permalink
Merge pull request #171 from wildmaples/cleartext-plugin-auth-switch
Browse files Browse the repository at this point in the history
Implement support for cleartext authentication plugin
  • Loading branch information
composerinteralia authored Apr 5, 2024
2 parents 3fa8359 + e9a4c65 commit 770961e
Show file tree
Hide file tree
Showing 14 changed files with 71 additions and 15 deletions.
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);

rb_global_variable(&Trilogy_AuthPluginError);
Trilogy_AuthPluginError = rb_const_get(Trilogy, rb_intern("AuthPluginError"));

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
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
Binary file not shown.
Binary file added test/auth_plugins/8/auth_test_plugin.so
Binary file not shown.

0 comments on commit 770961e

Please sign in to comment.