Skip to content

Commit 9f6e8b2

Browse files
committed
Add support to client SSL PEM cert and key and root CA
1 parent d989632 commit 9f6e8b2

3 files changed

Lines changed: 180 additions & 0 deletions

File tree

src/database/mod.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ pub struct ConnectOptions {
6363
/// Schema search path (PostgreSQL only)
6464
pub(crate) schema_search_path: Option<String>,
6565
pub(crate) test_before_acquire: bool,
66+
/// Sets whether or with what priority a secure SSL TCP/IP connection will be negotiated
67+
pub(crate) ssl_mode: Option<SslMode>,
68+
/// Sets the SSL client certificate as a PEM-encoded byte slice.
69+
pub(crate) ssl_client_cert: Option<Vec<u8>>,
70+
/// Sets the SSL client key as a PEM-encoded byte slice
71+
pub(crate) ssl_client_key: Option<Vec<u8>>,
72+
/// Sets PEM encoded trusted SSL Certificate Authorities (CA).
73+
pub(crate) ssl_root_cert: Option<Vec<u8>>,
6674
/// Only establish connections to the DB as needed. If set to `true`, the db connection will
6775
/// be created using SQLx's [connect_lazy](https://docs.rs/sqlx/latest/sqlx/struct.Pool.html#method.connect_lazy)
6876
/// method.
@@ -148,6 +156,29 @@ where
148156
}
149157
}
150158

159+
#[derive(Debug, Clone, Copy)]
160+
/// Options for controlling the level of protection provided for MySQL or PostgreSQL SSL connections.
161+
pub enum SslMode {
162+
/// I don't care about security, and I don't want to pay the overhead of encryption.
163+
/// This corresponds to postgres `sslmode=disable` and mysql `ssl-mode=DISABLED`.
164+
Disable,
165+
/// I don't care about encryption, but I wish to pay the overhead of encryption if the server supports it.
166+
/// This corresponds to postgres `sslmode=prefer` and mysql `ssl-mode=PREFERRED`.
167+
/// This is the default.
168+
Prefer,
169+
/// I want my data to be encrypted, and I accept the overhead. I trust that the network will make sure I always connect to the server I want.
170+
/// This corresponds to postgres `sslmode=require` and mysql `ssl-mode=REQUIRED`.
171+
Require,
172+
/// I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server that I trust.
173+
/// like `Self::Require`, but additionally verify the server Certificate Authority (CA) certificate against the configured CA certificates.
174+
/// This corresponds to postgres `sslmode=verify-ca` and mysql `ssl-mode=VERIFY_CA`.
175+
VerifyCa,
176+
/// I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server I trust, and that it's the one I specify.
177+
/// like `Self::VerifyCa`, but additionally 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.
178+
/// This corresponds to postgres `sslmode=verify-full` and mysql `ssl-mode=VERIFY_IDENTITY`.
179+
VerifyIdentity,
180+
}
181+
151182
impl ConnectOptions {
152183
/// Create new [ConnectOptions] for a [Database] by passing in a URI string
153184
pub fn new<T>(url: T) -> Self
@@ -170,6 +201,10 @@ impl ConnectOptions {
170201
schema_search_path: None,
171202
test_before_acquire: true,
172203
connect_lazy: false,
204+
ssl_mode: None,
205+
ssl_client_cert: None,
206+
ssl_client_key: None,
207+
ssl_root_cert: None,
173208
}
174209
}
175210

@@ -311,6 +346,99 @@ impl ConnectOptions {
311346
self
312347
}
313348

349+
/// Sets whether or with what priority a secure SSL TCP/IP connection will be negotiated
350+
/// with the server.
351+
///
352+
/// By default, the SSL mode is [`Prefer`](SSLMode::Prefer), and the client will
353+
/// first attempt an SSL connection but fallback to a non-SSL connection on failure.
354+
///
355+
/// Ignored for Unix domain socket communication.
356+
///
357+
/// # Example
358+
///
359+
/// ```rust
360+
/// # use sea_orm::database::{ConnectOptions, SSLMode};
361+
/// let options = ConnectOptions::new().ssl_mode(SSLMode::Require);
362+
/// ```
363+
pub fn ssl_mode(&mut self, mode: SslMode) -> &mut Self {
364+
self.ssl_mode = Some(mode);
365+
self
366+
}
367+
368+
/// Sets the SSL client certificate as a PEM-encoded byte slice.
369+
///
370+
/// This should be an ASCII-encoded blob that starts with `-----BEGIN CERTIFICATE-----`.
371+
///
372+
/// # Example
373+
/// Note: embedding SSL certificates and keys in the binary is not advised.
374+
/// This is for illustration purposes only.
375+
///
376+
/// ```rust
377+
/// # use sea_orm::database::{ConnectOptions, SSLMode};
378+
///
379+
/// const CERT: &[u8] = b"\
380+
/// -----BEGIN CERTIFICATE-----
381+
/// <Certificate data here.>
382+
/// -----END CERTIFICATE-----";
383+
///
384+
/// let options = ConnectOptions::new()
385+
/// // Providing a CA certificate with less than VerifyCa is pointless
386+
/// .ssl_mode(SSLMode::VerifyCa)
387+
/// .ssl_client_cert_pem(CERT);
388+
/// ```
389+
pub fn ssl_client_cert_pem(&mut self, cert: Vec<u8>) -> &mut Self {
390+
self.ssl_client_cert = Some(cert);
391+
self
392+
}
393+
394+
/// Sets the SSL client key as a PEM-encoded byte slice.
395+
///
396+
/// This should be an ASCII-encoded blob that starts with `-----BEGIN PRIVATE KEY-----`.
397+
///
398+
/// # Example
399+
/// Note: embedding SSL certificates and keys in the binary is not advised.
400+
/// This is for illustration purposes only.
401+
///
402+
/// ```rust
403+
/// # use sea_orm::database::{ConnectOptions, SSLMode};
404+
///
405+
/// const KEY: &[u8] = b"\
406+
/// -----BEGIN PRIVATE KEY-----
407+
/// <Private key data here.>
408+
/// -----END PRIVATE KEY-----";
409+
///
410+
/// let options = ConnectOptions::new()
411+
/// // Providing a CA certificate with less than VerifyCa is pointless
412+
/// .ssl_mode(SSLMode::VerifyCa)
413+
/// .ssl_client_key_pem(KEY);
414+
/// ```
415+
pub fn ssl_client_key_pem(&mut self, key: Vec<u8>) -> &mut Self {
416+
self.ssl_client_key = Some(key);
417+
self
418+
}
419+
420+
/// Sets PEM encoded trusted SSL Certificate Authorities (CA).
421+
///
422+
/// # Example
423+
///
424+
/// ```rust
425+
/// # use sea_orm::database::{ConnectOptions, SSLMode};
426+
///
427+
/// const CERT: &[u8] = b"\
428+
/// -----BEGIN CERTIFICATE-----
429+
/// <Certificate data here.>
430+
/// -----END CERTIFICATE-----";
431+
///
432+
/// let options = ConnectOptions::new()
433+
/// // Providing a CA certificate with less than VerifyCa is pointless
434+
/// .ssl_mode(SSLMode::VerifyCa)
435+
/// .ssl_root_cert_from_pem(vec![]);
436+
/// ```
437+
pub fn ssl_root_cert_from_pem(mut self, pem_certificate: Vec<u8>) -> Self {
438+
self.ssl_root_cert = Some(pem_certificate);
439+
self
440+
}
441+
314442
/// If set to `true`, the db connection pool will be created using SQLx's
315443
/// [connect_lazy](https://docs.rs/sqlx/latest/sqlx/struct.Pool.html#method.connect_lazy) method.
316444
pub fn connect_lazy(&mut self, value: bool) -> &mut Self {

src/driver/sqlx_mysql.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@ impl SqlxMySqlConnector {
7676
);
7777
}
7878
}
79+
if let Some(ssl_mode) = options.ssl_mode {
80+
opt = opt.ssl_mode(ssl_mode.into());
81+
}
82+
if let Some(cert_keyclient_cert) = &options.ssl_client_cert {
83+
opt = opt.ssl_client_cert_from_pem(cert_keyclient_cert);
84+
}
85+
if let Some(cert_keyclient_key) = &options.ssl_client_key {
86+
opt = opt.ssl_client_key_from_pem(cert_keyclient_key);
87+
}
88+
if let Some(cert_keyclient_ca) = &options.ssl_root_cert {
89+
opt = opt.ssl_ca_from_pem(cert_keyclient_ca.clone());
90+
}
7991
let pool = if options.connect_lazy {
8092
options.sqlx_pool_options().connect_lazy_with(opt)
8193
} else {
@@ -321,6 +333,18 @@ impl
321333
}
322334
}
323335

336+
impl From<crate::SslMode> for sqlx::mysql::MySqlSslMode {
337+
fn from(mode: crate::SslMode) -> Self {
338+
match mode {
339+
crate::SslMode::Disable => Self::Disabled,
340+
crate::SslMode::Prefer => Self::Preferred,
341+
crate::SslMode::Require => Self::Required,
342+
crate::SslMode::VerifyCa => Self::VerifyCa,
343+
crate::SslMode::VerifyIdentity => Self::VerifyIdentity,
344+
}
345+
}
346+
}
347+
324348
impl crate::DatabaseTransaction {
325349
pub(crate) async fn new_mysql(
326350
inner: PoolConnection<sqlx::MySql>,

src/driver/sqlx_postgres.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,22 @@ impl SqlxPostgresConnector {
9494
}
9595
string
9696
});
97+
if let Some(ssl_mode) = options.ssl_mode {
98+
opt = opt.ssl_mode(ssl_mode.into());
99+
}
100+
if let Some(cert_keyclient_cert) = &options.ssl_client_cert {
101+
opt = opt.ssl_client_cert_from_pem(cert_keyclient_cert);
102+
}
103+
if let Some(cert_keyclient_key) = &options.ssl_client_key {
104+
opt = opt.ssl_client_key_from_pem(cert_keyclient_key);
105+
}
106+
if let Some(cert_keyclient_root_cert) = &options.ssl_root_cert {
107+
opt = opt.ssl_root_cert_from_pem(cert_keyclient_root_cert.clone());
108+
}
109+
let set_search_path_sql = options
110+
.schema_search_path
111+
.as_ref()
112+
.map(|schema| format!("SET search_path = {schema}"));
97113
let lazy = options.connect_lazy;
98114
let mut pool_options = options.sqlx_pool_options();
99115
if let Some(sql) = set_search_path_sql {
@@ -353,6 +369,18 @@ impl
353369
}
354370
}
355371

372+
impl From<crate::SslMode> for sqlx::postgres::PgSslMode {
373+
fn from(mode: crate::SslMode) -> Self {
374+
match mode {
375+
crate::SslMode::Disable => Self::Disable,
376+
crate::SslMode::Prefer => Self::Prefer,
377+
crate::SslMode::Require => Self::Require,
378+
crate::SslMode::VerifyCa => Self::VerifyCa,
379+
crate::SslMode::VerifyIdentity => Self::VerifyFull,
380+
}
381+
}
382+
}
383+
356384
impl crate::DatabaseTransaction {
357385
pub(crate) async fn new_postgres(
358386
inner: PoolConnection<sqlx::Postgres>,

0 commit comments

Comments
 (0)