Skip to content

Commit f04318d

Browse files
committed
Use postgres-native-tls to connect to the database with TLS
`diesel-async` by default does not support TLS directly. Instead it offers the `custom_setup` option which allows its users to choose their TLS stack between `native-tls`, `openssl` and `rustls`. This change implements a `custom_setup` hook based on `postgres-native-tls`, whose dependencies are mostly already in our lockfile anyway. The code was mostly adapted from the official example code in `diesel-async`. Before `diesel-async` we relied on the regular `diesel` connection implementation, which uses `pg-sys` under the hood, which uses `libpq`, which uses OpenSSL for TLS. In other words: this slightly changes how TLS is handled for the database connections. Specifically, on OSX the server certificate is rejected as "not trusted" due to a long validity period. On production this should not be relevant, but during local development against the staging database this can cause issues, thus I've included a code comment with a temporary workaround for these cases.
1 parent ed2e1a7 commit f04318d

File tree

5 files changed

+66
-5
lines changed

5 files changed

+66
-5
lines changed

Cargo.lock

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,13 @@ tikv-jemallocator = { version = "=0.6.0", features = ['unprefixed_malloc_on_supp
9191
lettre = { version = "=0.11.7", default-features = false, features = ["file-transport", "smtp-transport", "native-tls", "hostname", "builder"] }
9292
minijinja = "=2.1.0"
9393
mockall = "=0.13.0"
94+
native-tls = "=0.2.12"
9495
oauth2 = "=4.4.2"
9596
object_store = { version = "=0.10.2", features = ["aws"] }
9697
p256 = "=0.13.2"
9798
parking_lot = "=0.12.3"
9899
paste = "=1.0.15"
100+
postgres-native-tls = "=0.5.0"
99101
prometheus = { version = "=0.13.4", default-features = false }
100102
quick-xml = "=0.36.1"
101103
rand = "=0.8.5"
@@ -113,6 +115,7 @@ tar = "=0.4.41"
113115
tempfile = "=3.10.1"
114116
thiserror = "=1.0.63"
115117
tokio = { version = "=1.39.2", features = ["net", "signal", "io-std", "io-util", "rt-multi-thread", "macros", "process"]}
118+
tokio-postgres = "=0.7.11"
116119
toml = "=0.8.15"
117120
tower = "=0.4.13"
118121
tower-http = { version = "=0.5.2", features = ["add-extension", "fs", "catch-panic", "timeout", "compression-full"] }

src/app.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Application-wide components in a struct accessible from each request
22
33
use crate::config;
4-
use crate::db::{connection_url, ConnectionConfig};
4+
use crate::db::{connection_url, make_manager_config, ConnectionConfig};
55
use std::ops::Deref;
66
use std::sync::Arc;
77

@@ -88,7 +88,7 @@ impl App {
8888
};
8989

9090
let url = connection_url(&config.db, config.db.primary.url.expose_secret());
91-
let manager = AsyncDieselConnectionManager::<AsyncPgConnection>::new(url);
91+
let manager = AsyncDieselConnectionManager::new_with_config(url, make_manager_config());
9292

9393
DeadpoolPool::builder(manager)
9494
.runtime(Runtime::Tokio1)
@@ -108,7 +108,7 @@ impl App {
108108
};
109109

110110
let url = connection_url(&config.db, pool_config.url.expose_secret());
111-
let manager = AsyncDieselConnectionManager::<AsyncPgConnection>::new(url);
111+
let manager = AsyncDieselConnectionManager::new_with_config(url, make_manager_config());
112112

113113
let pool = DeadpoolPool::builder(manager)
114114
.runtime(Runtime::Tokio1)

src/bin/background-worker.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extern crate tracing;
1515

1616
use anyhow::Context;
1717
use crates_io::cloudfront::CloudFront;
18+
use crates_io::db::make_manager_config;
1819
use crates_io::fastly::Fastly;
1920
use crates_io::storage::Storage;
2021
use crates_io::team_repo::TeamRepoImpl;
@@ -27,7 +28,6 @@ use crates_io_index::RepositoryConfig;
2728
use crates_io_worker::Runner;
2829
use diesel_async::pooled_connection::deadpool::Pool;
2930
use diesel_async::pooled_connection::AsyncDieselConnectionManager;
30-
use diesel_async::AsyncPgConnection;
3131
use reqwest::Client;
3232
use secrecy::ExposeSecret;
3333
use std::sync::Arc;
@@ -82,7 +82,7 @@ fn main() -> anyhow::Result<()> {
8282
let fastly = Fastly::from_environment(client.clone());
8383
let team_repo = TeamRepoImpl::default();
8484

85-
let manager = AsyncDieselConnectionManager::<AsyncPgConnection>::new(db_url);
85+
let manager = AsyncDieselConnectionManager::new_with_config(db_url, make_manager_config());
8686
let deadpool = Pool::builder(manager).max_size(10).build().unwrap();
8787

8888
let environment = Environment::builder()

src/db.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use diesel::{Connection, ConnectionResult, PgConnection, QueryResult};
22
use diesel_async::pooled_connection::deadpool::{Hook, HookError};
3+
use diesel_async::pooled_connection::ManagerConfig;
34
use diesel_async::{AsyncPgConnection, RunQueryDsl};
5+
use native_tls::TlsConnector;
6+
use postgres_native_tls::MakeTlsConnector;
47
use secrecy::ExposeSecret;
58
use std::time::Duration;
69
use url::Url;
@@ -43,6 +46,45 @@ fn maybe_append_url_param(url: &mut Url, key: &str, value: &str) {
4346
}
4447
}
4548

49+
/// Create a new [ManagerConfig] for the database connection pool, which can
50+
/// be used with [diesel_async::pooled_connection::AsyncDieselConnectionManager::new_with_config()].
51+
pub fn make_manager_config() -> ManagerConfig<AsyncPgConnection> {
52+
let mut manager_config = ManagerConfig::default();
53+
manager_config.custom_setup = Box::new(|url| Box::pin(establish_async_connection(url)));
54+
manager_config
55+
}
56+
57+
/// Establish a new database connection with the given URL.
58+
///
59+
/// Adapted from <https://github.com/weiznich/diesel_async/blob/v0.5.0/examples/postgres/pooled-with-rustls/src/main.rs>.
60+
async fn establish_async_connection(url: &str) -> ConnectionResult<AsyncPgConnection> {
61+
use diesel::ConnectionError::BadConnection;
62+
63+
let connector = TlsConnector::builder()
64+
// The TLS certificate of our current database server has a long validity
65+
// period and OSX rejects such certificates as "not trusted". If you run
66+
// into "Certificate was not trusted" errors during local development,
67+
// you may consider temporarily (!) enabling the following instruction.
68+
//
69+
// See also https://github.com/sfackler/rust-native-tls/issues/143.
70+
//
71+
// .danger_accept_invalid_certs(true)
72+
.build()
73+
.map_err(|err| BadConnection(err.to_string()))?;
74+
75+
let connector = MakeTlsConnector::new(connector);
76+
let result = tokio_postgres::connect(url, connector).await;
77+
let (client, conn) = result.map_err(|err| BadConnection(err.to_string()))?;
78+
79+
tokio::spawn(async move {
80+
if let Err(e) = conn.await {
81+
eprintln!("Database connection: {e}");
82+
}
83+
});
84+
85+
AsyncPgConnection::try_from(client).await
86+
}
87+
4688
#[derive(Debug, Clone, Copy)]
4789
pub struct ConnectionConfig {
4890
pub statement_timeout: Duration,

0 commit comments

Comments
 (0)