RMariaDB icon indicating copy to clipboard operation
RMariaDB copied to clipboard

Enabling secure (SSL/TLS) client connections, an example in documentation or a vignette would be nice

Open akkce opened this issue 4 years ago • 13 comments

The documentation could use an example of how to enable SSL/TLS connectivity without authentication (i.e., transport encryption). I would expect that this is fairly common scenario where the MariaDB server is configured to require secure transport via the require-secure-transport system variable.

In the legacy RMySQL library this is done with the via sslmode argument. It's not clear from the documentation how to do this with RMariaDB. The ssl.key, ssl.cert, ssl.ca and ssl.capath arguments all appear to deal with two-way TLS authentication. The client.flags argument appears to reference a CLIENT_SECURE_CONNECTION flag in the underlying C connector which appears to do what I want, however there doesn't seem to be any CLIENT_SECURE_CONNECTION flag documented in the connector API's documentation on RMariaDB's website.

I'm able to successfully connect to my particular database using RMySQL:

library("RMySQL")

# connect to MySQL database
mysql_dbconn <- dbConnect(
    drv = RMySQL::MySQL(),
    dbname = "internal_equities",
    user = "MYSUSERNAME",
    password = "MYPASSWORD",
    sslmode = "require")

I'm sure I've missed something obvious so if someone has a working configuration please share it.

I'd also love to see the documentation include an example and the client.flags documentation updated if it is indeed the case that the CLIENT_SECURE_CONNECTION flag is no longer used by the connector.

Thanks

akkce avatar Dec 29 '21 21:12 akkce

Thanks. Looking at the code in RMySQL, the sslmode argument doesn't seem to be picked up anywhere. There's no explicit arguments, and the ellipsis is unused: https://github.com/r-dbi/RMySQL/blob/49a859b775414dbb5deb629623a01da7297e3a6d/R/connection.R#L66-L104 . Is the package perhaps relying on a different mechanism?

What operating system are you using? What error message are you seeing?

krlmlr avatar Jan 03 '22 06:01 krlmlr

Are you by any chance using a .mylogin.cnf file with hard-coded password? https://github.com/r-dbi/RMariaDB/issues/156

krlmlr avatar Jan 03 '22 06:01 krlmlr

Kirill,

Thanks for getting back to me. I appreciate all your hard work on this library.

Server: Debian 11.2 and MariaDB 10.5.12 Client: Windows 10 1909, R 4.12, DBI 1.1.1, RMariaDB 1.2.0

The SQL server is configured to with a SSL certificate issued from my organization's Certificate Authority (which is trusted by client). require-secure-transport is enabled as well.

I get this error from the client when connecting with the following snippet:

library("RMariaDB") library("askpass") con <- dbConnect( drv = RMariaDB::MariaDB(), username = "MYUSERNAME", password = askpass(), dbname = "MYDATABASE", host = "MYFQDN", port = "3306" )

Error: Failed to connect: Access denied for user 'MYUSERNAME'@'10.233.224.136' (using password: YES)

I was able to get an ODBC() based connection working today by enabling the Force TLS Use option.

I feel like I'm missing something simple in my configuration but I'm not seeing it.

Thank you.

Sent with ProtonMail Secure Email.

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ On Sunday, January 2nd, 2022 at 9:24 PM, Kirill Müller @.***> wrote:

Are you by any chance using a .mylogin.cnf file with hard-coded password? #156

— Reply to this email directly, view it on GitHub, or unsubscribe. Triage notifications on the go with GitHub Mobile for iOS or Android. You are receiving this because you authored the thread.Message ID: @.***>

akkce avatar Jan 04 '22 23:01 akkce

Thanks. I understand that connectivity works for RMySQL and ODBC, but not for RMariaDB. I looked at the code to establish the connection, from the arguments you supply it's virtually identical. The only difference between RMariaDB and RMySQL is the client library used.

Can you please double-check which of the following still works for connecting with RMySQL:

library("RMySQL")

# connect to MySQL database
mysql_dbconn <- dbConnect(
    drv = RMySQL::MySQL(),
    dbname = "internal_equities",
    user = "MYSUSERNAME",
    password = "MYPASSWORD"
)
library("RMySQL")

# connect to MySQL database
mysql_dbconn <- dbConnect(
    drv = RMySQL::MySQL(),
    dbname = "internal_equities",
    user = "MYSUSERNAME"
)

krlmlr avatar Jan 05 '22 03:01 krlmlr

OK. My apologizes. I have an important correction.

The above referenced RMySQL code does NOT work remotely from my Windows client, with or without the sslmode argument. I was conflating it with the server environment where Unix sockets are used to connect (confirmed with dbGetInfo()).

Server-side it looks I'm using R 4.0.4 which is the version included in the Debian repositories. I was unable to test RMariaDB on the server since it appears that it is not available for this version of R. I'd rather not upgrade the version of R running there but that can be arranged if possible.

On the Windows client, the only method I have been successful with is by using an ODBC() connection. That might just be the best path forward.

Thanks again.

Sent with ProtonMail Secure Email.

‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐ On Tuesday, January 4th, 2022 at 6:12 PM, Kirill Müller @.***> wrote:

Thanks. I understand that connectivity works for RMySQL and ODBC, but not for RMariaDB. I looked at the code to establish the connection, from the arguments you supply it's virtually identical. The only difference between RMariaDB and RMySQL is the client library used.

Can you please double-check which of the following still works for connecting with RMySQL:

library(

"

RMySQL

"

)

connect to MySQL database

mysql_dbconn

<-

dbConnect(

drv

=

RMySQL

::

MySQL(),

dbname

=

"

internal_equities

"

,

user

=

"

MYSUSERNAME

"

,

password

=

"

MYPASSWORD

"

)

library(

"

RMySQL

"

)

connect to MySQL database

mysql_dbconn

<-

dbConnect(

drv

=

RMySQL

::

MySQL(),

dbname

=

"

internal_equities

"

,

user

=

"

MYSUSERNAME

"

)

— Reply to this email directly, view it on GitHub, or unsubscribe. Triage notifications on the go with GitHub Mobile for iOS or Android. You are receiving this because you authored the thread.Message ID: @.***>

akkce avatar Jan 05 '22 23:01 akkce

Thanks. I advise to use the same database setup for both development and production. Using ODBC for dev and RMariaDB or RMySQL for prod might lead to headaches later on, depending on the complexity of the data queried.

I'm happy to review your configuration over a short call, I'm curious myself what might lead to those problems. Please get in touch via e-mail if you're interested.

krlmlr avatar Jan 06 '22 06:01 krlmlr

After quite a bit of trial and error plus digging into the MariaDB C Connector docs, I was able to get the following to work. This will connect to an Amazon Aurora RDS that is configured to enforce SSL:

First, create an option files named something like ~/my.cnf. This is necessary because the RMariaDB library currently only sets custom options that are read from a file. The contents of the file should be:

[client]
ssl-enforce=1
ssl-verify-server-cert=0

Then, create an R script as follows that references this file:

library(RMariaDB)
library(DBI)

con <- dbConnect(RMariaDB::MariaDB(),
   default.file = normalizePath("~/my.cnf"),
   host="<replace with your hostname>",
   dbname="<your db name>",
   username="<your db user>",
   password="<your db password>",
   port=<your db  port>,
   timeout=20,
   mysql = FALSE,
)

dbListTables(con)
dbDisconnect(con)

The mysql = FALSE line is also important even if you are connecting to a MySQL database. The options are MariaDB connector options, but this does still seem to work when connecting to MySQL.

Hopefully this helps someone with similar problems. It'd be nice if this was better documented as I had to try quite a few combinations of settings to find the one that worked.

schroedad avatar Oct 27 '23 01:10 schroedad

It would be helpful if the documentation included information on how to configure client options directly in the code. For instance, how to enable SSL while disabling server certificate verification, as opposed to the current method of using a file, as shown in the example above.

ipimpat avatar Nov 08 '23 15:11 ipimpat

Thanks. Curious to know why mysql = FALSE is important. What fails if you omit it?

krlmlr avatar Nov 08 '23 17:11 krlmlr

@krlmlr I'm not sure, but I think the configuration parameters in my.cnf are specific to MariaDB and so potentially don't load or apply. When I remove that statement, it won't connect.

schroedad avatar Nov 08 '23 18:11 schroedad

@ipimpat It's not currently possible due to the way the library is written. Would require a change to the code to allow setting those parameters via code rather than via a file.

schroedad avatar Nov 08 '23 18:11 schroedad

This is weird because the mysql argument only controls the returned class. What do you mean by "it won't connect" -- is there a timeout, or an error message?

What else do we need besides the ssl.* arguments to dbConnect() to make SSL fully configurable without files?

krlmlr avatar Nov 08 '23 22:11 krlmlr

What else do we need besides the ssl.* arguments to dbConnect() to make SSL fully configurable without files?

What about including all the same options as the command line client?

$ mysql --help | grep ssl
  --ssl               Enable SSL for connection (automatically enabled with
  --ssl-ca=name       CA file in PEM format (check OpenSSL docs, implies
                      --ssl).
  --ssl-capath=name   CA directory (check OpenSSL docs, implies --ssl).
  --ssl-cert=name     X509 cert in PEM format (implies --ssl).
  --ssl-cipher=name   SSL cipher to use (implies --ssl).
  --ssl-key=name      X509 key in PEM format (implies --ssl).
  --ssl-crl=name      Certificate revocation list (implies --ssl).
  --ssl-crlpath=name  Certificate revocation list path (implies --ssl).
  --ssl-verify-server-cert 
ssl                               FALSE
ssl-ca                            (No default value)
ssl-capath                        (No default value)
ssl-cert                          (No default value)
ssl-cipher                        (No default value)
ssl-key                           (No default value)
ssl-crl                           (No default value)
ssl-crlpath                       (No default value)
ssl-verify-server-cert            FALSE

However, the most important options are --ssl and --ssl-verify-server-cert.

ipimpat avatar Feb 19 '24 18:02 ipimpat

After spending quite a bit of time getting this to finally work with the MariaDB Connector/C, I have determined the following simple trick (long story, but I upgraded to AWS AL2023 and they only provide the MariaDB Connector/C library).

Note: just setting client.flag=CLIENT_SSL enables SSL when using the Native MySQL C Connector library whereas it does not enable SSL when using the MariaDB Connector/C library.

What I have found is that just setting ssl.ciper="" causes mysql_ssl_set() to get called which then enables using SSL (this currently works with RMariaDB v1.3.1):

con <- dbConnect(RMariaDB::MariaDB(),
   host="<replace with your hostname>",
   dbname="<your db name>",
   username="<your db user>",
   password="<your db password>",
   port=<your db  port>,
   ssl.cipher="",
   client.flag = CLIENT_SSL,
)

Alternatively, if you do specify a default file, you can just set the flag ssl-enforce=1, and it also works fine. Same if you use group=, you can just add ssl-enforce=1 to your group definition.

Interestingly, setting the cipher to an empty string seems to be harmless and still ends up using a cipher from the server's returned capabilities. This can be verified by querying the server and checking the session cipher:

> dbGetQuery(sqlCon, 'show status where variable_name = "ssl_cipher"')
  Variable_name                  Value
1    Ssl_cipher TLS_AES_256_GCM_SHA384

The interesting thing is the the Native MySQL C Connector library works just fine with only the client.flag=CLIENT_SSL. I believe their library must actually look at the client_flag for CLIENT_SSL in their version mysql_real_connect() and then enable it, whereas the MariaDB Connector-C library is definitely not doing this and is expecting the caller to properly interpret and set the options before calling mysql_real_connect().

NOTE: @krlmlr you have actually broken enabling SSL using the client.flag in #319 since you removed the call to mysql_ssl_set(). This was the one thing that was very useful that mysql_ssl_set() was doing:

  char enable= 1;
  return (mysql_optionsv(mysql, MYSQL_OPT_SSL_ENFORCE, &enable) |

So, to fix this, what really needs happen is the following code should be added to DbConnection::connect():

+  if (client_flag & CLIENT_SSL) {
+    my_bool use_ssl_ = 1;
+    mysql_options(this->pConn_, MYSQL_OPT_SSL_ENFORCE, (void *)&use_ssl_);
+  }
   if (!Rf_isNull(ssl_key)) {
     mysql_options(this->pConn_, MYSQL_OPT_SSL_KEY,    cpp11::as_cpp<std::string>(ssl_key).c_str());
   }

You may additionally want to add some similar logic for CLIENT_SSL_VERIFY_SERVER_CERT as I do not see this getting propagated anywher in the MariaDB Connector/C either:

+  // Set SSL options
+  if (client_flag & CLIENT_SSL) {
+    my_bool use_ssl_ = 1;
+    mysql_options(this->pConn_, MYSQL_OPT_SSL_ENFORCE, (void *)&use_ssl_);
+  }
+  if (client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) {
+    my_bool verify_server_cert_ = 1;
+    mysql_options(this->pConn_, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (void *)&verify_server_cert_);
+  }
   if (!Rf_isNull(ssl_key)) {
     mysql_options(this->pConn_, MYSQL_OPT_SSL_KEY,    cpp11::as_cpp<std::string>(ssl_key).c_str());
   }

d-hansen avatar Mar 28 '24 06:03 d-hansen

Fixed in #322.

krlmlr avatar Apr 01 '24 16:04 krlmlr