Skip to content

Commit

Permalink
client ssl certs (#7)
Browse files Browse the repository at this point in the history
* WIP: add support for client ssl certs

See sqlpage/SQLPage#299

* add support for mysql ssl client certificates

* fmt

* fix pg doctest

* fix mysql doctest

* document the new ssl client cert options

* improve error messages on invalid ssl key and add tests

* add tests

* switch to checkout v4

* use rust-cache@v2 everywhere
  • Loading branch information
lovasoa authored May 4, 2024
1 parent a9c4f4a commit 58b9a75
Show file tree
Hide file tree
Showing 23 changed files with 484 additions and 74 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ jobs:
name: Publish
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- run: |
cargo publish ${ARGS} --package sqlx-rt-oldapi
cargo publish ${ARGS} --package sqlx-core-oldapi
Expand Down
37 changes: 26 additions & 11 deletions .github/workflows/sqlx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
name: Format
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: cargo fmt --all -- --check

check:
Expand All @@ -22,8 +22,8 @@ jobs:
runtime: [async-std, tokio, actix]
tls: [native-tls, rustls]
steps:
- uses: actions/checkout@v3
- uses: Swatinem/rust-cache@dd05243424bd5c0e585e4b55eb2d7615cdd32f1f
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- run:
cargo check
--manifest-path sqlx-core/Cargo.toml
Expand Down Expand Up @@ -51,7 +51,7 @@ jobs:
rustls
]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- run:
cargo test
Expand Down Expand Up @@ -79,7 +79,7 @@ jobs:
# bin: target/debug/cargo-sqlx

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
- run:
cargo build
Expand All @@ -101,7 +101,7 @@ jobs:
tls: [native-tls, rustls]
needs: check
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- run: mkdir /tmp/sqlite3-lib && wget -O /tmp/sqlite3-lib/ipaddr.so https://github.com/nalgeon/sqlean/releases/download/0.15.2/ipaddr.so
- uses: Swatinem/rust-cache@v2
- run:
Expand All @@ -125,7 +125,7 @@ jobs:
tls: [native-tls, rustls]
needs: check
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions-rs/toolchain@v1
with:
Expand Down Expand Up @@ -170,11 +170,26 @@ jobs:
--no-default-features
--features any,postgres,macros,migrate,all-types,runtime-${{ matrix.runtime }}-${{ matrix.tls }}
env:
DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=.%2Ftests%2Fcerts%2Fca.crt
DATABASE_URL: postgres://postgres:password@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=./tests/certs/ca.crt
# FIXME: needed to disable `ltree` tests in Postgres 9.6
# but `PgLTree` should just fall back to text format
RUSTFLAGS: --cfg postgres_${{ matrix.postgres }}

postgres_ssl_client_cert:
name: Postgres with SSL client cert
runs-on: ubuntu-22.04
needs: check
steps:
- uses: actions/checkout@v4
- uses: Swatinem/rust-cache@v2
with:
key: linux-postgres-ssl-client-cert
- run: docker compose up --wait postgres_16
working-directory: tests
- run: cargo test --no-default-features --features any,postgres,macros,all-types,runtime-actix-rustls
env:
DATABASE_URL: postgres://postgres@localhost:5432/sqlx?sslmode=verify-ca&sslrootcert=./tests/certs/ca.crt&sslcert=./tests/certs/client.crt&sslkey=./tests/keys/client.key

mysql:
name: MySQL
runs-on: ubuntu-22.04
Expand All @@ -185,7 +200,7 @@ jobs:
tls: [native-tls, rustls]
needs: check
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions-rs/toolchain@v1
with:
Expand Down Expand Up @@ -236,7 +251,7 @@ jobs:
tls: [native-tls, rustls]
needs: check
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions-rs/toolchain@v1
with:
Expand Down Expand Up @@ -276,7 +291,7 @@ jobs:
tls: [native-tls, rustls]
needs: check
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- uses: actions-rs/toolchain@v1
with:
Expand Down
6 changes: 6 additions & 0 deletions sqlx-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ impl Error {
pub(crate) fn config(err: impl StdError + Send + Sync + 'static) -> Self {
Error::Configuration(err.into())
}

#[allow(dead_code)]
#[inline]
pub(crate) fn tls<T: Into<BoxDynError>>(err: T) -> Self {
Error::Tls(err.into())
}
}

pub(crate) fn mismatched_types<DB: Database, T: Type<DB>>(ty: &DB::TypeInfo) -> BoxDynError {
Expand Down
2 changes: 1 addition & 1 deletion sqlx-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! Not intended to be used directly.
#![recursion_limit = "512"]
#![warn(future_incompatible, rust_2018_idioms)]
#![allow(clippy::needless_doctest_main, clippy::type_complexity)]
#![allow(clippy::needless_doctest_main, clippy::type_complexity, dead_code)]
// See `clippy.toml` at the workspace root
#![deny(clippy::disallowed_methods)]
//
Expand Down
22 changes: 12 additions & 10 deletions sqlx-core/src/mysql/connection/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::mysql::connection::MySqlStream;
use crate::mysql::protocol::connect::SslRequest;
use crate::mysql::protocol::Capabilities;
use crate::mysql::{MySqlConnectOptions, MySqlSslMode};
use crate::net::TlsConfig;

pub(super) async fn maybe_upgrade(
stream: &mut MySqlStream,
Expand Down Expand Up @@ -45,16 +46,17 @@ async fn upgrade(stream: &mut MySqlStream, options: &MySqlConnectOptions) -> Res
options.ssl_mode,
MySqlSslMode::VerifyCa | MySqlSslMode::VerifyIdentity
);
let accept_invalid_host_names = !matches!(options.ssl_mode, MySqlSslMode::VerifyIdentity);

stream
.upgrade(
&options.host,
accept_invalid_certs,
accept_invalid_host_names,
options.ssl_ca.as_ref(),
)
.await?;
let accept_invalid_hostnames = !matches!(options.ssl_mode, MySqlSslMode::VerifyIdentity);

let tls_config = TlsConfig {
accept_invalid_certs,
hostname: &options.host,
accept_invalid_hostnames,
root_cert_path: options.ssl_ca.as_ref(),
client_cert_path: options.ssl_client_cert.as_ref(),
client_key_path: options.ssl_client_key.as_ref(),
};
stream.upgrade(tls_config).await?;

Ok(true)
}
36 changes: 36 additions & 0 deletions sqlx-core/src/mysql/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ pub use ssl_mode::MySqlSslMode;
/// |---------|-------|-----------|
/// | `ssl-mode` | `PREFERRED` | Determines whether or with what priority a secure SSL TCP/IP connection will be negotiated. See [`MySqlSslMode`]. |
/// | `ssl-ca` | `None` | Sets the name of a file containing a list of trusted SSL Certificate Authorities. |
/// | `ssl-cert` | `None` | Sets the name of a file containing a client SSL certificate to authenticate the connection to the server |
/// | `ssl-key` | `None` | Sets the name of a file containing a secret SSL key for the client certificate. |
/// | `statement-cache-capacity` | `100` | The maximum number of prepared statements stored in the cache. Set to `0` to disable. |
/// | `socket` | `None` | Path to the unix domain socket, which will be used instead of TCP if set. |
///
Expand Down Expand Up @@ -61,6 +63,8 @@ pub struct MySqlConnectOptions {
pub(crate) database: Option<String>,
pub(crate) ssl_mode: MySqlSslMode,
pub(crate) ssl_ca: Option<CertificateInput>,
pub(crate) ssl_client_cert: Option<CertificateInput>,
pub(crate) ssl_client_key: Option<CertificateInput>,
pub(crate) statement_cache_capacity: usize,
pub(crate) charset: String,
pub(crate) collation: Option<String>,
Expand Down Expand Up @@ -88,6 +92,8 @@ impl MySqlConnectOptions {
collation: None,
ssl_mode: MySqlSslMode::Preferred,
ssl_ca: None,
ssl_client_cert: None,
ssl_client_key: None,
statement_cache_capacity: 100,
log_settings: Default::default(),
pipes_as_concat: true,
Expand Down Expand Up @@ -186,6 +192,36 @@ impl MySqlConnectOptions {
self
}

/// Sets the name of a file containing SSL client certificate.
///
/// # Example
///
/// ```rust
/// # use sqlx_core_oldapi::mysql::{MySqlSslMode, MySqlConnectOptions};
/// let options = MySqlConnectOptions::new()
/// .ssl_mode(MySqlSslMode::VerifyCa)
/// .ssl_client_cert("path/to/client.crt");
/// ```
pub fn ssl_client_cert(mut self, cert: impl AsRef<Path>) -> Self {
self.ssl_client_cert = Some(CertificateInput::File(cert.as_ref().to_path_buf()));
self
}

/// Sets the name of a file containing SSL client key.
///
/// # Example
///
/// ```rust
/// # use sqlx_core_oldapi::mysql::{MySqlSslMode, MySqlConnectOptions};
/// let options = MySqlConnectOptions::new()
/// .ssl_mode(MySqlSslMode::VerifyCa)
/// .ssl_client_key("path/to/client.key");
/// ```
pub fn ssl_client_key(mut self, key: impl AsRef<Path>) -> Self {
self.ssl_client_key = Some(CertificateInput::File(key.as_ref().to_path_buf()));
self
}

/// Sets the capacity of the connection's statement cache in a number of stored
/// distinct statements. Caching is handled using LRU, meaning when the
/// amount of queries hits the defined limit, the oldest statement will get
Expand Down
8 changes: 6 additions & 2 deletions sqlx-core/src/mysql/options/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ impl FromStr for MySqlConnectOptions {

for (key, value) in url.query_pairs().into_iter() {
match &*key {
"ssl-mode" => {
"sslmode" | "ssl-mode" => {
options = options.ssl_mode(value.parse().map_err(Error::config)?);
}

"ssl-ca" => {
"sslca" | "ssl-ca" => {
options = options.ssl_ca(&*value);
}

Expand All @@ -68,6 +68,10 @@ impl FromStr for MySqlConnectOptions {
options = options.socket(&*value);
}

"sslcert" | "ssl-cert" => options = options.ssl_client_cert(&*value),

"sslkey" | "ssl-key" => options = options.ssl_client_key(&*value),

_ => {}
}
}
Expand Down
2 changes: 2 additions & 0 deletions sqlx-core/src/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pub use socket::Socket;
pub use tls::CertificateInput;

Check warning on line 6 in sqlx-core/src/net/mod.rs

View workflow job for this annotation

GitHub Actions / MSSQL (2019, actix, native-tls)

unused import: `tls::CertificateInput`

Check warning on line 6 in sqlx-core/src/net/mod.rs

View workflow job for this annotation

GitHub Actions / MSSQL (2017, tokio, native-tls)

unused import: `tls::CertificateInput`

Check warning on line 6 in sqlx-core/src/net/mod.rs

View workflow job for this annotation

GitHub Actions / MSSQL (2017, async-std, rustls)

unused import: `tls::CertificateInput`

Check warning on line 6 in sqlx-core/src/net/mod.rs

View workflow job for this annotation

GitHub Actions / MSSQL (2019, actix, rustls)

unused import: `tls::CertificateInput`

Check warning on line 6 in sqlx-core/src/net/mod.rs

View workflow job for this annotation

GitHub Actions / MSSQL (2017, tokio, rustls)

unused import: `tls::CertificateInput`

Check warning on line 6 in sqlx-core/src/net/mod.rs

View workflow job for this annotation

GitHub Actions / MSSQL (2017, actix, native-tls)

unused import: `tls::CertificateInput`

Check warning on line 6 in sqlx-core/src/net/mod.rs

View workflow job for this annotation

GitHub Actions / MSSQL (2017, actix, rustls)

unused import: `tls::CertificateInput`

Check warning on line 6 in sqlx-core/src/net/mod.rs

View workflow job for this annotation

GitHub Actions / MSSQL (2017, async-std, native-tls)

unused import: `tls::CertificateInput`
#[allow(unused_imports)]
pub use tls::MaybeTlsStream;
#[allow(unused_imports)]
pub use tls::TlsConfig;

#[cfg(feature = "_rt-async-std")]
type PollReadBuf<'a> = [u8];
Expand Down
Loading

0 comments on commit 58b9a75

Please sign in to comment.