Repository: blackbeam/mysql_async (language: rust)

Issue:
SSL/TLS does not seem to be supported
SSL/TLS query parameters don't seem to be supported. Something like `mysql_async::Pool::new("mysql://myuser:password@127.0.0.1/db?sslMode=VERIFY_CA")` would fail because it wouldn't recognize the `ssl-mode` query parameter. The error looks like:

```
thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: UnknownParameter { param: "ssl-mode" }', /Users/caleb/.cargo/registry/src/github.com-1ecc6299db9ec823/mysql_async-0.30.0/src/conn/pool/mod.rs:124:41
```


It seems like the parsing is just not wired up properly because there is a struct `opts.ssl_opts`.




Discussion / hints:
Hi.

Regarding SSL query params - not sure about the reason, but support is not implemented, sorry. You'll have to use OptsBuilder for now. 

Regarding `sslMode` - it seem to be a libmysqlclient feature, but `mysql_async` is a pure-rust driver that doesn't depend on it:
If `opts.ssl_opts` is given then you should assume `REQUIRED` + `VERIFY_IDENTITY` + `VERIFY_CA`.
* `with_danger_skip_domain_validation` turns off `VERIFY_IDENTITY`
* `with_danger_accept_invalid_certs`  turns off `VERIFY_CA`
Otherwise - `DISABLED`

Fix patch:
diff --git a/README.md b/README.md
index 42e3202a..aad81f35 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,7 @@ as well as `native-tls`-based TLS support.
     mysql_async = { version = "*", default-features = false, features = ["rustls-tls"] }
 
 *   `tracing` – enables instrumentation via `tracing` package.
+
     Primary operations (`query`, `prepare`, `exec`) are instrumented at `INFO` level.
     Remaining operations, incl. `get_conn`, are instrumented at `DEBUG` level.
     Also at `DEBUG`, the SQL queries and parameters are added to the `query`, `prepare`
@@ -110,6 +111,10 @@ SSL support comes in two flavors:
     -   it, most likely, won't work on windows, at least with default server certs,
         generated by the MySql installer.
 
+## Connection URL parameters
+
+There is a set of url-parameters supported by the driver (see documentation on [`Opts`]).
+
 ## Example
 
 ```rust
diff --git a/src/lib.rs b/src/lib.rs
index 72393cb1..2e0a6e09 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -79,6 +79,20 @@
 //!     [dependencies]
 //!     mysql_async = { version = "*", default-features = false, features = ["rustls-tls"] }
 //!
+//! *   `tracing` – enables instrumentation via `tracing` package.
+//!
+//!     Primary operations (`query`, `prepare`, `exec`) are instrumented at `INFO` level.
+//!     Remaining operations, incl. `get_conn`, are instrumented at `DEBUG` level.
+//!     Also at `DEBUG`, the SQL queries and parameters are added to the `query`, `prepare`
+//!     and `exec` spans.
+//!
+//!     **Example:**
+//!
+//!     ```toml
+//!     [dependencies]
+//!     mysql_async = { version = "*", features = ["tracing"] }
+//!     ```
+//!
 //! [myslqcommonfeatures]: https://github.com/blackbeam/rust_mysql_common#crate-features
 //!
 //! # TLS/SSL Support
@@ -96,6 +110,10 @@
 //!     -   it, most likely, won't work on windows, at least with default server certs,
 //!         generated by the MySql installer.
 //!
+//! # Connection URL parameters
+//!
+//! There is a set of url-parameters supported by the driver (see documentation on [`Opts`]).
+//!
 //! # Example
 //!
 //! ```rust
diff --git a/src/opts/mod.rs b/src/opts/mod.rs
index b8386ee4..a74a6fc0 100644
--- a/src/opts/mod.rs
+++ b/src/opts/mod.rs
@@ -608,7 +608,27 @@ impl Opts {
         self.inner.mysql_opts.stmt_cache_size
     }
 
-    /// Driver will require SSL connection if this opts isn't `None` (default to `None`).
+    /// Driver will require SSL connection if this opts isn't `None` (defaults to `None`).
+    ///
+    /// # Connection URL parameters
+    ///
+    /// Note that for securty reasons:
+    ///
+    /// * CA and IDENTITY verifications are opt-out
+    /// * there is no way to give an idenity or root certs via query URL
+    ///
+    /// URL Parameters:
+    ///
+    /// *   `require_ssl: bool` (defaults to `false`) – requires SSL with default [`SslOpts`]
+    /// *   `verify_ca: bool` (defaults to `true`) – requires server Certificate Authority (CA)
+    ///     certificate validation against the configured CA certificates.
+    ///     Makes no sence if  `require_ssl` equals `false`.
+    /// *   `verify_identity: bool` (defaults to `true`) – perform host name identity verification
+    ///     by checking the host name the client uses for connecting to the server against
+    ///     the identity in the certificate that the server sends to the client.
+    ///     Makes no sence if  `require_ssl` equals `false`.
+    ///
+    ///
     pub fn ssl_opts(&self) -> Option<&SslOpts> {
         self.inner.mysql_opts.ssl_opts.as_ref()
     }
@@ -1078,6 +1098,10 @@ fn mysqlopts_from_url(url: &Url) -> std::result::Result<MysqlOpts, UrlError> {
     let (mut opts, query_pairs): (MysqlOpts, _) = from_url_basic(url)?;
     let mut pool_min = DEFAULT_POOL_CONSTRAINTS.min;
     let mut pool_max = DEFAULT_POOL_CONSTRAINTS.max;
+
+    let mut skip_domain_validation = false;
+    let mut accept_invalid_certs = false;
+
     for (key, value) in query_pairs {
         if key == "pool_min" {
             match usize::from_str(&*value) {
@@ -1240,6 +1264,40 @@ fn mysqlopts_from_url(url: &Url) -> std::result::Result<MysqlOpts, UrlError> {
                     value,
                 });
             }
+        } else if key == "require_ssl" {
+            match bool::from_str(&*value) {
+                Ok(x) => opts.ssl_opts = x.then(SslOpts::default),
+                _ => {
+                    return Err(UrlError::InvalidParamValue {
+                        param: "require_ssl".into(),
+                        value,
+                    });
+                }
+            }
+        } else if key == "verify_ca" {
+            match bool::from_str(&*value) {
+                Ok(x) => {
+                    accept_invalid_certs = !x;
+                }
+                _ => {
+                    return Err(UrlError::InvalidParamValue {
+                        param: "verify_ca".into(),
+                        value,
+                    });
+                }
+            }
+        } else if key == "verify_identity" {
+            match bool::from_str(&*value) {
+                Ok(x) => {
+                    skip_domain_validation = !x;
+                }
+                _ => {
+                    return Err(UrlError::InvalidParamValue {
+                        param: "verify_identity".into(),
+                        value,
+                    });
+                }
+            }
         } else {
             return Err(UrlError::UnknownParameter { param: key });
         }
@@ -1254,6 +1312,11 @@ fn mysqlopts_from_url(url: &Url) -> std::result::Result<MysqlOpts, UrlError> {
         });
     }
 
+    if let Some(ref mut ssl_opts) = opts.ssl_opts.as_mut() {
+        ssl_opts.accept_invalid_certs = accept_invalid_certs;
+        ssl_opts.skip_domain_validation = skip_domain_validation;
+    }
+
     Ok(opts)
 }
 
@@ -1276,7 +1339,7 @@ impl<'a> TryFrom<&'a str> for Opts {
 #[cfg(test)]
 mod test {
     use super::{HostPortOrUrl, MysqlOpts, Opts, Url};
-    use crate::error::UrlError::InvalidParamValue;
+    use crate::{error::UrlError::InvalidParamValue, SslOpts};
 
     use std::str::FromStr;
 
@@ -1345,6 +1408,41 @@ mod test {
         assert_eq!(opts.ip_or_hostname(), "[::1]");
     }
 
+    #[test]
+    fn should_parse_ssl_params() {
+        const URL1: &str = "mysql://localhost/foo?require_ssl=false";
+        let opts = Opts::from_url(URL1).unwrap();
+        assert_eq!(opts.ssl_opts(), None);
+
+        const URL2: &str = "mysql://localhost/foo?require_ssl=true";
+        let opts = Opts::from_url(URL2).unwrap();
+        assert_eq!(opts.ssl_opts(), Some(&SslOpts::default()));
+
+        const URL3: &str = "mysql://localhost/foo?require_ssl=true&verify_ca=false";
+        let opts = Opts::from_url(URL3).unwrap();
+        assert_eq!(
+            opts.ssl_opts(),
+            Some(&SslOpts::default().with_danger_accept_invalid_certs(true))
+        );
+
+        const URL4: &str =
+            "mysql://localhost/foo?require_ssl=true&verify_ca=false&verify_identity=false";
+        let opts = Opts::from_url(URL4).unwrap();
+        assert_eq!(
+            opts.ssl_opts(),
+            Some(
+                &SslOpts::default()
+                    .with_danger_accept_invalid_certs(true)
+                    .with_danger_skip_domain_validation(true)
+            )
+        );
+
+        const URL5: &str =
+            "mysql://localhost/foo?require_ssl=false&verify_ca=false&verify_identity=false";
+        let opts = Opts::from_url(URL5).unwrap();
+        assert_eq!(opts.ssl_opts(), None);
+    }
+
     #[test]
     #[should_panic]
     fn should_panic_on_invalid_url() {


Test patch: