Skip to content

Commit d021cbd

Browse files
authored
Update README and improve handling of ssl_mode settings for MySQL and MariaDB versions (brianmario#1306)
Print the client version number in SSL warning. Add comments to explain the ssl mode setting function. Add MariaDB Connector/C 3.x to the relevant SSL mode code path. With thanks to Jun Aruga for identifying this issue and reviewing changes.
1 parent 6528137 commit d021cbd

File tree

3 files changed

+128
-37
lines changed

3 files changed

+128
-37
lines changed

README.md

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ This may be needed if you deploy to a system where these libraries
6565
are located somewhere different than on your build system.
6666
This overrides any rpath calculated by default or by the options above.
6767

68+
* `--with-openssl-dir[=/path/to/openssl]` - Specify the directory where OpenSSL
69+
is installed. In most cases, the Ruby runtime and MySQL client libraries will
70+
link against a system-installed OpenSSL library and this option is not needed.
71+
Use this option when non-default library paths are needed.
72+
6873
* `--with-sanitize[=address,cfi,integer,memory,thread,undefined]` -
6974
Enable sanitizers for Clang / GCC. If no argument is given, try to enable
7075
all sanitizers or fail if none are available. If a command-separated list of
@@ -89,13 +94,48 @@ the library file `libmysqlclient.so` but is missing the header file `mysql.h`
8994

9095
### Mac OS X
9196

92-
You may use MacPorts, Homebrew, or a native MySQL installer package. The most
97+
You may use Homebew, MacPorts, or a native MySQL installer package. The most
9398
common paths will be automatically searched. If you want to select a specific
9499
MySQL directory, use the `--with-mysql-dir` or `--with-mysql-config` options above.
95100

96101
If you have not done so already, you will need to install the XCode select tools by running
97102
`xcode-select --install`.
98103

104+
Later versions of MacOS no longer distribute a linkable OpenSSL library. It is
105+
common to use Homebrew or MacPorts to install OpenSSL. Make sure that both the
106+
Ruby runtime and MySQL client libraries are compiled with the same OpenSSL
107+
family, 1.0 or 1.1 or 3.0, since only one can be loaded at runtime.
108+
109+
``` sh
110+
$ brew install openssl@1.1
111+
$ gem install mysql2 -- --with-openssl-dir=$(brew --prefix openssl@1.1)
112+
113+
or
114+
115+
$ sudo port install openssl11
116+
```
117+
118+
Since most Ruby projects use Bundler, you can set build options in the Bundler
119+
config rather than manually installing a global mysql2 gem. This example shows
120+
how to set build arguments with [Bundler config](https://bundler.io/man/bundle-config.1.html):
121+
122+
``` sh
123+
$ bundle config --local build.mysql2 -- --with-openssl-dir=$(brew --prefix openssl@1.1)
124+
```
125+
126+
Another helpful trick is to use the same OpenSSL library that your Ruby was
127+
built with, if it was built with an alternate OpenSSL path. This example finds
128+
the argument `--with-openssl-dir=/some/path` from the Ruby build and adds that
129+
to the [Bundler config](https://bundler.io/man/bundle-config.1.html):
130+
131+
``` sh
132+
$ bundle config --local build.mysql2 -- $(ruby -r rbconfig -e 'puts RbConfig::CONFIG["configure_args"]' | xargs -n1 | grep with-openssl-dir)
133+
```
134+
135+
Note the additional double dashes (`--`) these separate command-line arguments
136+
that `gem` or `bundler` interpret from the addiitonal arguments that are passed
137+
to the mysql2 build process.
138+
99139
### Windows
100140

101141
Make sure that you have Ruby and the DevKit compilers installed. We recommend
@@ -205,7 +245,7 @@ result = statement.execute(1, "CA", :as => :array)
205245

206246
Session Tracking information can be accessed with
207247

208-
```ruby
248+
``` ruby
209249
c = Mysql2::Client.new(
210250
host: "127.0.0.1",
211251
username: "root",
@@ -261,19 +301,13 @@ type of connection to make, with special interpretation you should be aware of:
261301
* An IPv4 or IPv6 address will result in a TCP connection.
262302
* Any other value will be looked up as a hostname for a TCP connection.
263303

264-
### SSL options
265-
266-
Setting any of the following options will enable an SSL connection, but only if
267-
your MySQL client library and server have been compiled with SSL support.
268-
MySQL client library defaults will be used for any parameters that are left out
269-
or set to nil. Relative paths are allowed, and may be required by managed
270-
hosting providers such as Heroku.
271-
272-
For MySQL versions 5.7.11 and higher, use `:ssl_mode` to prefer or require an
273-
SSL connection and certificate validation. For earlier versions of MySQL, use
274-
the `:sslverify` boolean. For details on each of the `:ssl_mode` options, see
275-
[https://dev.mysql.com/doc/refman/8.0/en/connection-options.html](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-mode).
304+
### SSL/TLS options
276305

306+
Setting any of the following options will enable an SSL/TLS connection, but
307+
only if your MySQL client library and server have been compiled with SSL
308+
support. MySQL client library defaults will be used for any parameters that are
309+
left out or set to nil. Relative paths are allowed, and may be required by
310+
managed hosting providers such as Heroku.
277311

278312
``` ruby
279313
Mysql2::Client.new(
@@ -284,10 +318,28 @@ Mysql2::Client.new(
284318
:sslcapath => '/path/to/cacerts',
285319
:sslcipher => 'DHE-RSA-AES256-SHA',
286320
:sslverify => true, # Removed in MySQL 8.0
287-
:ssl_mode = :disabled / :preferred / :required / :verify_ca / :verify_identity,
321+
:ssl_mode => :disabled / :preferred / :required / :verify_ca / :verify_identity,
288322
)
289323
```
290324

325+
For MySQL versions 5.7.11 and higher, use `:ssl_mode` to prefer or require an
326+
SSL connection and certificate validation. For earlier versions of MySQL, use
327+
the `:sslverify` boolean. For details on each of the `:ssl_mode` options, see
328+
[https://dev.mysql.com/doc/refman/8.0/en/connection-options.html](https://dev.mysql.com/doc/refman/8.0/en/connection-options.html#option_general_ssl-mode).
329+
330+
The `:ssl_mode` option will also set the appropriate MariaDB connection flags:
331+
332+
| `:ssl_mode` | MariaDB option value |
333+
| --- | --- |
334+
| `:disabled` | MYSQL_OPT_SSL_ENFORCE = 0 |
335+
| `:required` | MYSQL_OPT_SSL_ENFORCE = 1 |
336+
| `:verify_identity` | MYSQL_OPT_SSL_VERIFY_SERVER_CERT = 1 |
337+
338+
MariaDB does not support the `:preferred` or `:verify_ca` options. For more
339+
information about SSL/TLS in MariaDB, see
340+
[https://mariadb.com/kb/en/securing-connections-for-client-and-server/](https://mariadb.com/kb/en/securing-connections-for-client-and-server/)
341+
and [https://mariadb.com/kb/en/mysql_optionsv/#tls-options](https://mariadb.com/kb/en/mysql_optionsv/#tls-options)
342+
291343
### Secure auth
292344

293345
Starting with MySQL 5.6.5, secure_auth is enabled by default on servers (it was disabled by default prior to this).
@@ -334,7 +386,7 @@ In this example, the compression flag is negated with `-COMPRESS`.
334386
Active Record typically reads its configuration from a file named `database.yml` or an environment variable `DATABASE_URL`.
335387
Use the value `mysql2` as the protocol name. For example:
336388

337-
``` shell
389+
``` sh
338390
DATABASE_URL=mysql2://sql_user:sql_pass@sql_host_name:port/sql_db_name?option1=value1&option2=value2
339391
```
340392

@@ -393,7 +445,7 @@ end
393445

394446
Yields:
395447

396-
```ruby
448+
``` ruby
397449
{"1"=>1}
398450
{"2"=>2}
399451
next_result: Unknown column 'A' in 'field list' (Mysql2::Error)
@@ -573,14 +625,16 @@ As for field values themselves, I'm workin on it - but expect that soon.
573625

574626
This gem is tested with the following Ruby versions on Linux and Mac OS X:
575627

576-
* Ruby MRI 2.0.0, 2.1.x, 2.2.x, 2.3.x, 2.4.x, 2.5.x, 2.6.x
628+
* Ruby MRI 2.0 through 2.7 (all versions to date)
629+
* Ruby MRI 3.0, 3.1, 3.2 (all versions to date)
577630
* Rubinius 2.x and 3.x do work but may fail under some workloads
578631

579632
This gem is tested with the following MySQL and MariaDB versions:
580633

581634
* MySQL 5.5, 5.6, 5.7, 8.0
582-
* MySQL Connector/C 6.0 and 6.1 (primarily on Windows)
583-
* MariaDB 5.5, 10.0, 10.1, 10.2, 10.3
635+
* MySQL Connector/C 6.0, 6.1, 8.0 (primarily on Windows)
636+
* MariaDB 5.5, 10.x, with a focus on 10.6 LTS and 10.11 LTS
637+
* MariaDB Connector/C 2.x, 3.x
584638

585639
### Ruby on Rails / Active Record
586640

ext/mysql2/client.c

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -120,53 +120,80 @@ struct nogvl_select_db_args {
120120

121121
static VALUE rb_set_ssl_mode_option(VALUE self, VALUE setting) {
122122
unsigned long version = mysql_get_client_version();
123+
const char *version_str = mysql_get_client_info();
123124

124-
if (version < 50630 || (version >= 50700 && version < 50703)) {
125-
rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.6.36+, 5.7.11+, 8.0+" );
125+
/* Warn about versions that are known to be incomplete; these are pretty
126+
* ancient, we want people to upgrade if they need SSL/TLS to work
127+
*
128+
* MySQL 5.x before 5.6.30 -- ssl_mode introduced but not fully working until 5.6.36)
129+
* MySQL 5.7 before 5.7.3 -- ssl_mode introduced but not fully working until 5.7.11)
130+
*/
131+
if ((version >= 50000 && version < 50630) || (version >= 50700 && version < 50703)) {
132+
rb_warn("Your mysql client library version %s does not support setting ssl_mode; full support comes with 5.6.36+, 5.7.11+, 8.0+", version_str);
126133
return Qnil;
127134
}
135+
136+
/* For these versions, map from the options we're exposing to Ruby to the constant available:
137+
* ssl_mode: :verify_identity to MYSQL_OPT_SSL_VERIFY_SERVER_CERT = 1
138+
* ssl_mode: :required to MYSQL_OPT_SSL_ENFORCE = 1
139+
* ssl_mode: :disabled to MYSQL_OPT_SSL_ENFORCE = 0
140+
*/
128141
#if defined(HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT) || defined(HAVE_CONST_MYSQL_OPT_SSL_ENFORCE)
129142
GET_CLIENT(self);
130-
int val = NUM2INT( setting );
131-
// Either MySQL 5.7.3 - 5.7.10, or Connector/C 6.1.3 - 6.1.x, or MariaDB 10.x and later
132-
if ((version >= 50703 && version < 50711) || (version >= 60103 && version < 60200) || version >= 100000) {
143+
int val = NUM2INT(setting);
144+
145+
/* Expected code path for MariaDB 10.x and MariaDB Connector/C 3.x
146+
* Workaround code path for MySQL 5.7.3 - 5.7.10 and MySQL Connector/C 6.1.3 - 6.1.x
147+
*/
148+
if (version >= 100000 // MariaDB (all versions numbered 10.x)
149+
|| (version >= 30000 && version < 40000) // MariaDB Connector/C (all versions numbered 3.x)
150+
|| (version >= 50703 && version < 50711) // Workaround for MySQL 5.7.3 - 5.7.10
151+
|| (version >= 60103 && version < 60200)) { // Workaround for MySQL Connector/C 6.1.3 - 6.1.x
133152
#ifdef HAVE_CONST_MYSQL_OPT_SSL_VERIFY_SERVER_CERT
134153
if (val == SSL_MODE_VERIFY_IDENTITY) {
135154
my_bool b = 1;
136-
int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &b );
155+
int result = mysql_options(wrapper->client, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &b);
137156
return INT2NUM(result);
138157
}
139158
#endif
140159
#ifdef HAVE_CONST_MYSQL_OPT_SSL_ENFORCE
141160
if (val == SSL_MODE_DISABLED || val == SSL_MODE_REQUIRED) {
142-
my_bool b = ( val == SSL_MODE_REQUIRED );
143-
int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b );
161+
my_bool b = (val == SSL_MODE_REQUIRED);
162+
int result = mysql_options(wrapper->client, MYSQL_OPT_SSL_ENFORCE, &b);
144163
return INT2NUM(result);
145164
}
146165
#endif
147-
rb_warn( "Your mysql client library does not support ssl_mode %d.", val );
166+
rb_warn("Your mysql client library version %s does not support ssl_mode %d", version_str, val);
148167
return Qnil;
149168
} else {
150-
rb_warn( "Your mysql client library does not support ssl_mode as expected." );
169+
rb_warn("Your mysql client library version %s does not support ssl_mode as expected", version_str);
151170
return Qnil;
152171
}
153172
#endif
173+
174+
/* For other versions -- known to be MySQL 5.6.36+, 5.7.11+, 8.0+
175+
* pass the value of the argument to MYSQL_OPT_SSL_MODE -- note the code
176+
* mapping from atoms / constants is in the MySQL::Client Ruby class
177+
*/
154178
#ifdef FULL_SSL_MODE_SUPPORT
155179
GET_CLIENT(self);
156-
int val = NUM2INT( setting );
180+
int val = NUM2INT(setting);
157181

158182
if (val != SSL_MODE_DISABLED && val != SSL_MODE_PREFERRED && val != SSL_MODE_REQUIRED && val != SSL_MODE_VERIFY_CA && val != SSL_MODE_VERIFY_IDENTITY) {
159183
rb_raise(cMysql2Error, "ssl_mode= takes DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY, you passed: %d", val );
160184
}
161-
int result = mysql_options( wrapper->client, MYSQL_OPT_SSL_MODE, &val );
185+
int result = mysql_options(wrapper->client, MYSQL_OPT_SSL_MODE, &val);
162186

163187
return INT2NUM(result);
164188
#endif
189+
190+
// Warn if we get this far
165191
#ifdef NO_SSL_MODE_SUPPORT
166-
rb_warn( "Your mysql client library does not support setting ssl_mode; full support comes with 5.7.11." );
192+
rb_warn("Your mysql client library does not support setting ssl_mode");
167193
return Qnil;
168194
#endif
169195
}
196+
170197
/*
171198
* non-blocking mysql_*() functions that we won't be wrapping since
172199
* they do not appear to hit the network nor issue any interruptible

spec/mysql2/client_spec.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,16 +166,26 @@ def connect(*args)
166166
new_client(option_overrides)
167167
end
168168

169-
%i[disabled preferred required verify_ca verify_identity].each do |ssl_mode|
169+
# 'preferred' or 'verify_ca' are only in MySQL 5.6.36+, 5.7.11+, 8.0+
170+
version = Mysql2::Client.info
171+
ssl_modes = case version
172+
when 50636...50700, 50711...50800, 80000...90000
173+
%i[disabled preferred required verifa_ca verify_identity]
174+
else
175+
%i[disabled required verify_identity]
176+
end
177+
178+
# MySQL and MariaDB and all versions of Connector/C
179+
ssl_modes.each do |ssl_mode|
170180
it "should set ssl_mode option #{ssl_mode}" do
171181
options = {
172182
ssl_mode: ssl_mode,
173183
}
174184
options.merge!(option_overrides)
175-
# Relax the matching condition by checking if an error is not raised.
176-
# TODO: Verify warnings by checking stderr.
177185
expect do
178-
new_client(options)
186+
expect do
187+
new_client(options)
188+
end.not_to output(/does not support ssl_mode/).to_stderr
179189
end.not_to raise_error
180190
end
181191
end

0 commit comments

Comments
 (0)