Skip to content

Can't enable SSL with MariaDB driver library #1182

Closed
@vakuum

Description

@vakuum

It is currently not possible to enable SSL through the ssl_mode parameter when using the MariaDB driver library.

This is critical because you can't connect to a MySQL server with a user that requires SSL.

How to reproduce?

Versions:

$ lsb_release -d
Description:    Ubuntu 20.04.2 LTS

$ ruby --version
ruby 2.6.6p146 (2020-03-31 revision 67876) [x86_64-linux]

$ gem --version
3.0.9

$ bundle --version
Bundler version 1.17.3

$ dpkg --list mysql-server-8.0
ii  mysql-server-8.0 8.0.23-0ubuntu0.20.04.1 amd64        MySQL database server binaries and system database setup

Create a MySQL database user with "REQUIRE SSL":

> CREATE USER IF NOT EXISTS test@127.0.0.1
IDENTIFIED WITH mysql_native_password BY 'test'
REQUIRE SSL;

Create a test script:

$ vim Gemfile
source 'https://rubygems.org'
gem 'mysql2'

$ vim test.rb
require 'mysql2'
client = Mysql2::Client.new(host: '127.0.0.1', username: 'test', password: 'test', ssl_mode: :required)
puts client.query('SHOW STATUS LIKE "Ssl_cipher"').first.inspect

🔴 Test with the MariaDB driver library

Install the driver library and the mysql2 gem:

$ sudo apt-get install libmariadb-dev-compat

$ dpkg --list libmariadb-dev-compat
...
ii  libmariadb-dev-compat:amd64 1:10.3.25-0ubuntu0.20.04.1 amd64        MariaDB Connector/C, compatibility symlinks

$ bundle exec gem uninstall mysql2
Successfully uninstalled mysql2-0.5.3

$ bundle
...
Installing mysql2 0.5.3 with native extensions
...

$ ldd $(bundle show mysql2)/lib/mysql2/mysql2.so
	...
	libmariadb.so.3 => /usr/lib/x86_64-linux-gnu/libmariadb.so.3 (0x00007efe06293000)
	...

Run the test script:

$ bundle exec ruby test.rb 
/home/clemens/.rvm/gems/ruby-2.6.6/gems/mysql2-0.5.3/lib/mysql2/client.rb:51: warning: Your mysql client library does not support ssl_mode as expected.
Traceback (most recent call last):
        3: from test.rb:2:in `<main>'
        2: from test.rb:2:in `new'
        1: from /home/clemens/.rvm/gems/ruby-2.6.6/gems/mysql2-0.5.3/lib/mysql2/client.rb:90:in `initialize'
/home/clemens/.rvm/gems/ruby-2.6.6/gems/mysql2-0.5.3/lib/mysql2/client.rb:90:in `connect': Access denied for user 'test'@'localhost' (using password: YES) (Mysql2::Error::ConnectionError)

🟢 Test with the MySQL driver library

Install the driver library and the mysql2 gem:

$ sudo apt-get install libmysqlclient-dev

$ dpkg --list libmysqlclient-dev
...
ii  libmysqlclient-dev 8.0.23-0ubuntu0.20.04.1 amd64        MySQL database development files

$ bundle exec gem uninstall mysql2
Successfully uninstalled mysql2-0.5.3

$ bundle
...
Installing mysql2 0.5.3 with native extensions
...

$ ldd $(bundle show mysql2)/lib/mysql2/mysql2.so
	...
	libmysqlclient.so.21 => /usr/lib/x86_64-linux-gnu/libmysqlclient.so.21 (0x00007f0ac5a7b000)
	...

Run the test script:

$ bundle exec ruby test.rb
{"Variable_name"=>"Ssl_cipher", "Value"=>"TLS_AES_256_GCM_SHA384"}

🟢 Solution?

With the MariaDB driver, the "mysql_get_client_version" at https://github.com/brianmario/mysql2/blob/0.5.3/ext/mysql2/client.c#L107 returns 100325 and HAVE_CONST_MYSQL_OPT_SSL_ENFORCE at https://github.com/brianmario/mysql2/blob/0.5.3/ext/mysql2/client.c#L113 is defined. The version that "mysql_get_client_version" returns is calculated at https://github.com/mariadb-corporation/mariadb-connector-c/blob/v3.1.12/CMakeLists.txt#L173.

With the MySQL driver, the "mysql_get_client_version" at https://github.com/brianmario/mysql2/blob/0.5.3/ext/mysql2/client.c#L107 returns 80023 and FULL_SSL_MODE_SUPPORT at https://github.com/brianmario/mysql2/blob/0.5.3/ext/mysql2/client.c#L131 is defined.

After adding a new version range for the MariaDB driver to https://github.com/brianmario/mysql2/blob/0.5.3/ext/mysql2/client.c#L117 the ssl_mode is correctly set and SSL is used for the database connection.

1. Create a patched mysql2 gem

$ git clone https://github.com/brianmario/mysql2.git

$ cd mysql2

$ git checkout 0.5.3

$ vim ext/mysql2/client.c
...
  if ((version >= 50703 && version < 50711) || (version >= 60103 && version < 60200) || (version >= 100200 && version < 110000)) {
...

$ git diff
diff --git a/ext/mysql2/client.c b/ext/mysql2/client.c
index 13fd2dd..e484fa0 100644
--- a/ext/mysql2/client.c
+++ b/ext/mysql2/client.c
@@ -114,7 +114,7 @@ static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) {
   GET_CLIENT(self);
   int val = NUM2INT( setting );
   // Either MySQL 5.7.3 - 5.7.10, or Connector/C 6.1.3 - 6.1.x
-  if ((version >= 50703 && version < 50711) || (version >= 60103 && version < 60200)) {
+  if ((version >= 50703 && version < 50711) || (version >= 60103 && version < 60200) || (version >= 100200 && version < 110000)) {
     if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) {
       my_bool b = ( val == SSL_MODE_REQUIRED );
       int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b );

$ gem build mysql2

$ cp mysql2-0.5.3.gem /tmp

2. Install the MariaDB driver library and the patched mysql2 gem

$ sudo apt-get install libmariadb-dev-compat

$ dpkg --list libmariadb-dev-compat
...
ii  libmariadb-dev-compat:amd64 1:10.3.25-0ubuntu0.20.04.1 amd64        MariaDB Connector/C, compatibility symlinks

$ bundle exec gem uninstall mysql2
Successfully uninstalled mysql2-0.5.3

$ gem install /tmp/mysql2-0.5.3.gem
...
Successfully installed mysql2-0.5.3
...

$ ldd $(bundle show mysql2)/lib/mysql2/mysql2.so
	...
	libmariadb.so.3 => /usr/lib/x86_64-linux-gnu/libmariadb.so.3 (0x00007f4576d6e000)
	...

3. Run the test script

$ bundle exec ruby test.rb
{"Variable_name"=>"Ssl_cipher", "Value"=>"TLS_AES_256_GCM_SHA384"}

Activity

junaruga

junaruga commented on Apr 10, 2021

@junaruga
Contributor

@vakuum thank you for the detailed report and the investigation!

When the libmariadb-dev-compat:amd64 1:10.3.25 returns the client version 100325, why is the patch || (version >= 100200 && version < 110000)? For example why not || (version >= 100100 && version < 110000). I do not understand it.

We have CI cases on GitHub Actions. Could you sent the PR for your patch, adding your case to the GitHub Actions as one case in build.yml - include syntax?

# Allow failure due to Mysql2::Error: Unknown system variable 'session_track_system_variables'.
- {os: ubuntu-16.04, ruby: 2.4, db: mariadb10.0, allow-failure: true}
# Comment out due to ci/setup.sh stucking.
# - {os: ubuntu-18.04, ruby: 2.4, db: mariadb10.1}
# `service mysql restart` fails.
- {os: ubuntu-20.04, ruby: 2.4, db: mariadb10.3, allow-failure: true}
- {os: ubuntu-18.04, ruby: 2.4, db: mysql57}
# Allow failure due to the issue #1165.
- {os: ubuntu-20.04, ruby: 2.4, db: mysql80, allow-failure: true}
- {os: ubuntu-18.04, ruby: 'head', db: '', allow-failure: true}

You can use ubuntu-20.04 on it. Maybe you see a test failure with the mysql-server-8.0 (#1165), but you can set allow-failure: true on the CI.

junaruga

junaruga commented on Apr 10, 2021

@junaruga
Contributor

Maybe we can modify the comment in rb_set_ssl_mode_option like this.

// Either MySQL 5.7.3 - 5.7.10, Connector/C 6.1.3 - 6.1.x, or MariaDB n.n.n - n.n.n
junaruga

junaruga commented on Apr 10, 2021

@junaruga
Contributor

When the libmariadb-dev-compat:amd64 1:10.3.25 returns the client version 100325, why is the patch || (version >= 100200 && version < 110000)? For example why not || (version >= 100100 && version < 110000). I do not understand it.

I guess the || (version >= 100200 && version < 110000) means "MariaDB 10.2.0 - (< 11)". If the range of the versions in the MariaDB client is right, the comment could be like this.

// Either MySQL 5.7.3 - 5.7.10, Connector/C 6.1.3 - 6.1.x, or MariaDB 10.2.0 - (< 11).
added 2 commits that reference this issue on Apr 10, 2021
junaruga

junaruga commented on Apr 10, 2021

@junaruga
Contributor

@vakuum Do you know how can we enable HAVE_CONST_MYSQL_OPT_SSL_ENFORCE used in client.c? I think it is necessary to reproduce the warning warning: Your mysql client library does not support ssl_mode as expected.?

junaruga

junaruga commented on Apr 10, 2021

@junaruga
Contributor

@sodabrew Do you know how to enable the macro HAVE_CONST_MYSQL_OPT_SSL_ENFORCE related to #1186 ?

I see you worked on this kind of things in the past. ff05239

junaruga

junaruga commented on Apr 10, 2021

@junaruga
Contributor

This one?

https://dev.mysql.com/doc/c-api/5.7/en/mysql-options.html

MYSQL_OPT_SSL_ENFORCE (argument type: my_bool *)
Whether to require the connection to use SSL. If enabled and an encrypted connection cannot be established, the connection attempt fails.
This option is deprecated as of MySQL 5.7.11 and is removed in MySQL 8.0. Instead, use MYSQL_OPT_SSL_MODE with a value of SSL_MODE_REQUIRED.
junaruga

junaruga commented on Apr 10, 2021

@junaruga
Contributor

I got a feedback at #1186 . Maybe we should not add the new CI case: DB server: MySQL, and DB client: MariaDB for now. Instead of that, we need to add a new unit test that can detect this issue. Because on the current unit tests on the current CI cases, this issue (the warning) is not detected.

junaruga

junaruga commented on Apr 11, 2021

@junaruga
Contributor

I captured the compiler's flags used for ext/mysql2/client.c by a patch #1187 . I just checked the following 3 cases. And I could not find the HAVE_CONST_MYSQL_OPT_SSL_ENFORCE macro. Possibly there is no CI case enabling the macro right now on CI. It's better to add the case on CI, isn't it?

GitHub - Build Ruby 3.0 case.

gcc -I. -I/opt/hostedtoolcache/Ruby/3.0.1/x64/include/ruby-3.0.0/x86_64-linux -I/opt/hostedtoolcache/Ruby/3.0.1/x64/include/ruby-3.0.0/ruby/backward -I/opt/hostedtoolcache/Ruby/3.0.1/x64/include/ruby-3.0.0 -I../../../../ext/mysql2 -I/usr/include/mysql -DHAVE_RB_ABSINT_SIZE -DHAVE_RB_ABSINT_SINGLEBIT_P -DHAVE_RB_WAIT_FOR_SINGLE_FD -DHAVE_MYSQL_H -DHAVE_ERRMSG_H -DHAVE_CONST_SSL_MODE_DISABLED -DHAVE_CONST_SSL_MODE_PREFERRED -DHAVE_CONST_SSL_MODE_REQUIRED -DHAVE_CONST_SSL_MODE_VERIFY_CA -DHAVE_CONST_SSL_MODE_VERIFY_IDENTITY -DHAVE_MYSQL_NET_VIO -DHAVE_ST_NET_VIO -DHAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN -DHAVE_CONST_SERVER_QUERY_NO_GOOD_INDEX_USED -DHAVE_CONST_SERVER_QUERY_NO_INDEX_USED -DHAVE_CONST_SERVER_QUERY_WAS_SLOW -DHAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_ON -DHAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_OFF -DHAVE_TYPE_MY_BOOL -I/opt/hostedtoolcache/Ruby/3.0.1/x64/include -DENABLE_PATH_CHECK=0   -fPIC -O3 -ggdb3 -Wall -Wextra -Wdeprecated-declarations -Wduplicated-cond -Wimplicit-function-declaration -Wimplicit-int -Wmisleading-indentation -Wpointer-arith -Wwrite-strings -Wimplicit-fallthrough=0 -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-packed-bitfield-compat -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wsuggest-attribute=format -Wsuggest-attribute=noreturn -Wunused-variable  -fPIC -DFULL_SSL_MODE_SUPPORT -Wno-bad-function-cast -Wno-conditional-uninitialized -Wno-covered-switch-default -Wno-declaration-after-statement -Wno-disabled-macro-expansion -Wno-documentation-unknown-command -Wno-missing-field-initializers -Wno-missing-variable-declarations -Wno-padded -Wno-reserved-id-macro -Wno-sign-conversion -Wno-static-in-inline -Wno-switch-enum -Wno-undef -Wno-unreachable-code -Wno-used-but-marked-unused  -o client.o -c ../../../../ext/mysql2/client.c

GitHub - Container CentOS 7 Ruby 2.0.0 case.

gcc -I. -I/usr/include -I/usr/include/ruby/backward -I/usr/include -I../../../../ext/mysql2 -I/usr/include/mysql -DHAVE_RB_WAIT_FOR_SINGLE_FD -DHAVE_MYSQL_H -DHAVE_ERRMSG_H -DHAVE_MYSQL_NET_VIO -DHAVE_ST_NET_VIO -DHAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN -DHAVE_CONST_SERVER_QUERY_NO_GOOD_INDEX_USED -DHAVE_CONST_SERVER_QUERY_NO_INDEX_USED -DHAVE_CONST_SERVER_QUERY_WAS_SLOW -DHAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_ON -DHAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_OFF -DHAVE_TYPE_MY_BOOL    -fPIC -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -mtune=generic -fPIC -DNO_SSL_MODE_SUPPORT -Wno-bad-function-cast -Wno-conditional-uninitialized -Wno-covered-switch-default -Wno-declaration-after-statement -Wno-disabled-macro-expansion -Wno-documentation-unknown-command -Wno-missing-field-initializers -Wno-missing-variable-declarations -Wno-padded -Wno-reserved-id-macro -Wno-sign-conversion -Wno-static-in-inline -Wno-switch-enum -Wno-undef -Wno-unreachable-code -Wno-used-but-marked-unused -m64 -o client.o -c ../../../../ext/mysql2/client.c

Travis Ruby 2.4 DB=mariadb10.3 case.

gcc -I. -I/home/travis/.rvm/rubies/ruby-2.4.10/include/ruby-2.4.0/x86_64-linux -I/home/travis/.rvm/rubies/ruby-2.4.10/include/ruby-2.4.0/ruby/backward -I/home/travis/.rvm/rubies/ruby-2.4.10/include/ruby-2.4.0 -I../../../../ext/mysql2 -I/usr/include/mysql -DHAVE_RB_ABSINT_SIZE -DHAVE_RB_ABSINT_SINGLEBIT_P -DHAVE_RB_WAIT_FOR_SINGLE_FD -DHAVE_MYSQL_H -DHAVE_ERRMSG_H -DHAVE_MYSQL_NET_VIO -DHAVE_ST_NET_VIO -DHAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN -DHAVE_CONST_SERVER_QUERY_NO_GOOD_INDEX_USED -DHAVE_CONST_SERVER_QUERY_NO_INDEX_USED -DHAVE_CONST_SERVER_QUERY_WAS_SLOW -DHAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_ON -DHAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_OFF -DHAVE_TYPE_MY_BOOL    -fPIC -O3 -fno-fast-math -ggdb3 -Wall -Wextra -Wno-unused-parameter -Wno-parentheses -Wno-long-long -Wno-missing-field-initializers -Wno-tautological-compare -Wno-parentheses-equality -Wno-constant-logical-operand -Wno-self-assign -Wunused-variable -Wimplicit-int -Wpointer-arith -Wwrite-strings -Wdeclaration-after-statement -Wimplicit-function-declaration -Wdeprecated-declarations -Wno-packed-bitfield-compat -Wsuggest-attribute=noreturn -Wsuggest-attribute=format  -fPIC -DNO_SSL_MODE_SUPPORT -Wno-bad-function-cast -Wno-conditional-uninitialized -Wno-covered-switch-default -Wno-declaration-after-statement -Wno-disabled-macro-expansion -Wno-documentation-unknown-command -Wno-missing-field-initializers -Wno-missing-variable-declarations -Wno-padded -Wno-reserved-id-macro -Wno-sign-conversion -Wno-static-in-inline -Wno-switch-enum -Wno-undef -Wno-unreachable-code -Wno-used-but-marked-unused  -o client.o -c ../../../../ext/mysql2/client.c
vakuum

vakuum commented on Apr 11, 2021

@vakuum
Author

@junaruga The macro HAVE_CONST_MYSQL_OPT_SSL_ENFORCE is defined in the generated Makefile when using the MariaDB driver:

$ grep HAVE_CONST_MYSQL_OPT_SSL_ENFORCE -r $(bundle show mysql2)
...
/home/clemens/.rvm/gems/ruby-2.6.6@test/gems/mysql2-0.5.3/ext/mysql2/Makefile:CPPFLAGS = -DHAVE_RB_ABSINT_SIZE -DHAVE_RB_ABSINT_SINGLEBIT_P -DHAVE_RB_WAIT_FOR_SINGLE_FD -DHAVE_MYSQL_H -DHAVE_ERRMSG_H -DHAVE_CONST_MYSQL_OPT_SSL_ENFORCE -DHAVE_MYSQL_NET_PVIO -DHAVE_ST_NET_PVIO -DHAVE_CONST_MYSQL_ENABLE_CLEARTEXT_PLUGIN -DHAVE_CONST_SERVER_QUERY_NO_GOOD_INDEX_USED -DHAVE_CONST_SERVER_QUERY_NO_INDEX_USED -DHAVE_CONST_SERVER_QUERY_WAS_SLOW -DHAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_ON -DHAVE_CONST_MYSQL_OPTION_MULTI_STATEMENTS_OFF -DHAVE_TYPE_MY_BOOL  $(DEFS) $(cppflags)

The following spec fails without the change of https://github.com/brianmario/mysql2/blob/0.5.3/ext/mysql2/client.c#L117 and the warning "Your mysql client library does not support ssl_mode as expected" is also printed:

$ sudo apt-get install libmariadb-dev-compat

$ git clone https://github.com/brianmario/mysql2.git

$ cd mysql2

$ vim spec/mysql2/client_spec.rb
...

$ git diff
diff --git a/spec/mysql2/client_spec.rb b/spec/mysql2/client_spec.rb
index 5e330f6..38df666 100644
--- a/spec/mysql2/client_spec.rb
+++ b/spec/mysql2/client_spec.rb
@@ -1122,4 +1122,8 @@ RSpec.describe Mysql2::Client do # rubocop:disable Metrics/BlockLength
   it "should respond to #encoding" do
     expect(@client).to respond_to(:encoding)
   end
+
+  it 'should use SSL' do
+    expect(new_client(ssl_mode: :required).query('SHOW STATUS LIKE "Ssl_cipher"').first['Value']).to_not eq('')
+  end
 end

$ bundle

$ bundle exec rake clean

$ bundle exec rake compile

$ ldd tmp/x86_64-linux/stage/lib/mysql2/mysql2.so
        ...
        libmariadb.so.3 => /usr/lib/x86_64-linux-gnu/libmariadb.so.3 (0x00007f484af74000)
        ...

$ bundle exec rake
...
Mysql2::Client
  ...
/home/clemens/mysql2/lib/mysql2/client.rb:51: warning: Your mysql client library does not support ssl_mode as expected.
  should use SSL (FAILED - 1)
  ...

  1) Mysql2::Client should use SSL
     Failure/Error: expect(new_client(ssl_mode: :required).query('SHOW STATUS LIKE "Ssl_cipher"').first['Value']).to_not eq('')

       expected: value != ""
            got: ""

       (compared using ==)
     # ./spec/mysql2/client_spec.rb:1127:in `block (2 levels) in <top (required)>'
  ...

After adding the new version range to https://github.com/brianmario/mysql2/blob/0.5.3/ext/mysql2/client.c#L117 the spec turns green.

junaruga

junaruga commented on Apr 11, 2021

@junaruga
Contributor

@vakuum Thanks for the info.

OK. You see the macro definition. I also can see the macro on my local. The defined macro The macro in the Makefile CPPFLAGS (right?) is used as a compiler's flags.
So, if you run the following command. You can see the macro on the command line to compile client.c.

bundle exec rake clean
MAKE="make V=1" bundle exec rake compile

I found the reason. How the macro is defined, and used.

First the maccos are checked from the MySQL/MariaDB client library's header.

add_ssl_defines(mysql_h)

Then,

all_modes_found = %w[SSL_MODE_DISABLED SSL_MODE_PREFERRED SSL_MODE_REQUIRED SSL_MODE_VERIFY_CA SSL_MODE_VERIFY_IDENTITY].inject(true) do |m, ssl_mode|
m && have_const(ssl_mode, header)
end
$CFLAGS << ' -DFULL_SSL_MODE_SUPPORT' if all_modes_found
# if we only have ssl toggle (--ssl,--disable-ssl) from 5.7.3 to 5.7.10
has_no_support = all_modes_found ? false : !have_const('MYSQL_OPT_SSL_ENFORCE', header)
$CFLAGS << ' -DNO_SSL_MODE_SUPPORT' if has_no_support

When have_const('MYSQL_OPT_SSL_ENFORCE', header) executes and the have_const returns true, "maybe" the HAVE_CONST_MYSQL_OPT_SSL_ENFORCE is set to the Makefile internally by Ruby's mkmf library.

For the above GitHub - Build Ruby 3.0 case, I found the -DFULL_SSL_MODE_SUPPORT in the commands to compile client.c.
For the above GitHub - Container CentOS 7 Ruby 2.0.0 case and Travis Ruby 2.4 DB=mariadb10.3 case, I found the -DNO_SSL_MODE_SUPPORT in the commands to compile client.c.

And seeing the logic in the extconf.rb, for both cases, the have_const('MYSQL_OPT_SSL_ENFORCE', header) is not executed. That's why we do not see the macro HAVE_CONST_MYSQL_OPT_SSL_ENFORCE on the current CI cases.

25 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @dbussink@junaruga@vakuum

        Issue actions

          Can't enable SSL with MariaDB driver library · Issue #1182 · brianmario/mysql2