Skip to content

Commit fb145cb

Browse files
johnnylarnergadomski
authored andcommitted
feat(pgstac, server): add tls support
1 parent 4a3c3c3 commit fb145cb

File tree

9 files changed

+192
-17
lines changed

9 files changed

+192
-17
lines changed

cli/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ default = ["gdal", "geoparquet", "pgstac"]
1616
duckdb = ["dep:stac-duckdb", "dep:duckdb"]
1717
gdal = ["stac/gdal"]
1818
geoparquet = ["stac/geoparquet-compression"]
19-
pgstac = ["stac-server/pgstac"]
19+
pgstac = ["stac-server/pgstac", "dep:pgstac"]
2020
python = ["dep:pyo3", "pgstac", "geoparquet"]
2121

2222
[dependencies]
2323
axum = "0.7"
2424
clap = { version = "4", features = ["derive"] }
2525
duckdb = { version = "1", optional = true } # We have this dependency only to allow us to bundle it
2626
object_store = "0.11"
27+
pgstac = { version = "0.1", path = "../pgstac", optional = true }
2728
pyo3 = { version = "0.22", optional = true }
2829
reqwest = "0.12"
2930
serde = "1"

cli/src/args/serve.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ where
177177
enum Backend {
178178
Memory(MemoryBackend),
179179
#[cfg(feature = "pgstac")]
180-
Pgstac(stac_server::PgstacBackend),
180+
Pgstac(stac_server::PgstacBackend<pgstac::MakeRustlsConnect>),
181181
}
182182

183183
impl Backend {

pgstac/Cargo.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,23 @@ license = "MIT OR Apache-2.0"
1010
keywords = ["geospatial", "stac", "metadata", "raster", "database"]
1111
categories = ["database", "data-structures", "science"]
1212

13+
[features]
14+
tls = ["dep:rustls", "dep:tokio-postgres-rustls", "dep:webpki-roots"]
15+
1316
[dependencies]
1417
geojson = "0.24"
18+
rustls = { version = "0.23", optional = true, features = [
19+
"ring",
20+
"std",
21+
], default-features = false }
1522
serde = "1"
1623
serde_json = "1"
1724
stac = { version = "0.9.0", path = "../core" }
1825
stac-api = { version = "0.5.0", path = "../api" }
1926
thiserror = "1"
2027
tokio-postgres = { version = "0.7", features = ["with-serde_json-1"] }
28+
tokio-postgres-rustls = { version = "0.12", optional = true }
29+
webpki-roots = { version = "0.26", optional = true }
2130

2231
[dev-dependencies]
2332
pgstac-test = { path = "pgstac-test" }

pgstac/src/client.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ impl<'a, C: GenericClient> Client<'a, C> {
249249
}
250250

251251
#[cfg(test)]
252-
mod tests {
252+
pub(crate) mod tests {
253253
use super::Client;
254254
use geojson::{Geometry, Value};
255255
use pgstac_test::pgstac_test;
@@ -264,7 +264,7 @@ mod tests {
264264
// the code generated by `pgstac_test`.
265265
//
266266
// There's got to be a better way.
267-
static MUTEX: Mutex<()> = Mutex::new(());
267+
pub(crate) static MUTEX: Mutex<()> = Mutex::new(());
268268

269269
fn longmont() -> Geometry {
270270
Geometry::new(Value::Point(vec![-105.1019, 40.1672]))

pgstac/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,12 @@
3636

3737
mod client;
3838
mod page;
39+
#[cfg(feature = "tls")]
40+
mod tls;
3941

4042
pub use {client::Client, page::Page};
43+
#[cfg(feature = "tls")]
44+
pub use {tls::make_unverified_tls, tokio_postgres_rustls::MakeRustlsConnect};
4145

4246
/// Crate-specific error enum.
4347
#[derive(Debug, thiserror::Error)]

pgstac/src/tls.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use rustls::{
2+
client::danger::{ServerCertVerified, ServerCertVerifier},
3+
crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider},
4+
pki_types::{CertificateDer, ServerName, UnixTime},
5+
ClientConfig, Error, RootCertStore,
6+
};
7+
use std::sync::Arc;
8+
use tokio_postgres_rustls::MakeRustlsConnect;
9+
use webpki_roots::TLS_SERVER_ROOTS;
10+
11+
/// Make an unverified tls.
12+
///
13+
/// # Examples
14+
///
15+
/// ```
16+
/// #[cfg(feature = "tls")]
17+
/// {
18+
/// let tls = pgstac::make_unverified_tls();
19+
/// }
20+
/// ```
21+
pub fn make_unverified_tls() -> MakeRustlsConnect {
22+
let mut root_cert_store = RootCertStore::empty();
23+
root_cert_store.extend(TLS_SERVER_ROOTS.iter().cloned());
24+
25+
let verifier = DummyTlsVerifier::default();
26+
let config = ClientConfig::builder()
27+
.dangerous()
28+
.with_custom_certificate_verifier(Arc::new(verifier))
29+
.with_no_client_auth();
30+
MakeRustlsConnect::new(config)
31+
}
32+
33+
// A TLS verifier copied from the `sqlx` library
34+
// [here](https://github.com/launchbadge/sqlx/blob/a892ebc6e283f443145f92bbc7fce4ae44547331/sqlx-core/src/net/tls/tls_rustls.rs#L208)
35+
// This verifier _does not_ verify certificates, but instead decrypts TLS
36+
// connections using default cipher codes
37+
#[derive(Debug)]
38+
struct DummyTlsVerifier {
39+
provider: Arc<CryptoProvider>,
40+
}
41+
42+
impl ServerCertVerifier for DummyTlsVerifier {
43+
fn verify_server_cert(
44+
&self,
45+
_end_entity: &CertificateDer<'_>,
46+
_intermediates: &[CertificateDer<'_>],
47+
_server_name: &ServerName<'_>,
48+
_ocsp_response: &[u8],
49+
_now: UnixTime,
50+
) -> Result<ServerCertVerified, Error> {
51+
Ok(ServerCertVerified::assertion())
52+
}
53+
54+
fn verify_tls12_signature(
55+
&self,
56+
message: &[u8],
57+
cert: &CertificateDer<'_>,
58+
dss: &rustls::DigitallySignedStruct,
59+
) -> Result<rustls::client::danger::HandshakeSignatureValid, Error> {
60+
verify_tls12_signature(
61+
message,
62+
cert,
63+
dss,
64+
&self.provider.signature_verification_algorithms,
65+
)
66+
}
67+
68+
fn verify_tls13_signature(
69+
&self,
70+
message: &[u8],
71+
cert: &CertificateDer<'_>,
72+
dss: &rustls::DigitallySignedStruct,
73+
) -> Result<rustls::client::danger::HandshakeSignatureValid, Error> {
74+
verify_tls13_signature(
75+
message,
76+
cert,
77+
dss,
78+
&self.provider.signature_verification_algorithms,
79+
)
80+
}
81+
82+
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
83+
self.provider
84+
.signature_verification_algorithms
85+
.supported_schemes()
86+
}
87+
}
88+
89+
impl Default for DummyTlsVerifier {
90+
fn default() -> Self {
91+
Self {
92+
provider: Arc::new(rustls::crypto::ring::default_provider()),
93+
}
94+
}
95+
}
96+
97+
#[cfg(test)]
98+
mod tests {
99+
use crate::client::tests::MUTEX;
100+
101+
#[tokio::test]
102+
async fn connect() {
103+
let _mutex = MUTEX.lock().unwrap();
104+
let config = std::env::var("PGSTAC_RS_TEST_DB")
105+
.unwrap_or("postgresql://username:password@localhost:5432/postgis".to_string());
106+
let tls = super::make_unverified_tls();
107+
let (_, _) = tokio_postgres::connect(&config, tls).await.unwrap();
108+
}
109+
}

server/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66

77
## [Unreleased]
88

9+
- Make TLS the default for `PgstacBackend` ([#383](https://github.com/stac-utils/stac-rs/pull/383))
10+
911
### Removed
1012

1113
- **stac-async** dependency ([#369](https://github.com/stac-utils/stac-rs/pull/369))
@@ -44,4 +46,4 @@ Initial release.
4446
[0.1.1]: https://github.com/stac-utils/stac-rs/compare/stac-server-v0.1.0..stac-server-v0.1.1
4547
[0.1.0]: https://github.com/stac-utils/stac-rs/releases/tag/stac-server-v0.1.0
4648

47-
<!-- markdownlint-disable-file MD024 -->
49+
<!-- markdownlint-disable-file MD024 -->

server/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ bb8-postgres = { version = "0.8", optional = true }
2222
bytes = { version = "1", optional = true }
2323
http = "1"
2424
mime = { version = "0.3", optional = true }
25-
pgstac = { version = "0.1", path = "../pgstac", optional = true }
25+
pgstac = { version = "0.1", path = "../pgstac", features = [
26+
"tls",
27+
], optional = true }
2628
serde = "1"
2729
serde_json = "1"
2830
serde_urlencoded = "0.7"

server/src/backend/pgstac.rs

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,33 @@ use crate::{Backend, Error, Result};
22
use bb8::Pool;
33
use bb8_postgres::PostgresConnectionManager;
44
use pgstac::Client;
5+
use pgstac::MakeRustlsConnect;
56
use serde_json::Map;
67
use stac::{Collection, Item};
78
use stac_api::{ItemCollection, Items, Search};
8-
use tokio_postgres::tls::NoTls;
9+
use tokio_postgres::{
10+
tls::{MakeTlsConnect, TlsConnect},
11+
Socket,
12+
};
913

1014
/// A backend for a [pgstac](https://github.com/stac-utils/pgstac) database.
1115
#[derive(Clone, Debug)]
12-
pub struct PgstacBackend {
13-
pool: Pool<PostgresConnectionManager<NoTls>>,
16+
pub struct PgstacBackend<Tls>
17+
where
18+
Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
19+
<Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
20+
<Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
21+
<<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
22+
{
23+
pool: Pool<PostgresConnectionManager<Tls>>,
1424
}
1525

16-
impl PgstacBackend {
26+
impl PgstacBackend<MakeRustlsConnect> {
1727
/// Creates a new PgstacBackend from a string-like configuration.
1828
///
29+
/// This will use an unverified tls. To provide your own tls, use
30+
/// [PgstacBackend::new_from_stringlike_and_tls].
31+
///
1932
/// # Examples
2033
///
2134
/// ```no_run
@@ -24,18 +37,53 @@ impl PgstacBackend {
2437
/// let backend = PgstacBackend::new_from_stringlike("postgresql://username:password@localhost:5432/postgis").await.unwrap();
2538
/// # })
2639
/// ```
27-
pub async fn new_from_stringlike(params: impl ToString) -> Result<PgstacBackend> {
28-
let connection_manager = PostgresConnectionManager::new_from_stringlike(params, NoTls)?;
29-
let pool = Pool::builder().build(connection_manager).await?;
30-
Ok(PgstacBackend::new(pool))
40+
pub async fn new_from_stringlike(
41+
params: impl ToString,
42+
) -> Result<PgstacBackend<MakeRustlsConnect>> {
43+
let tls = pgstac::make_unverified_tls();
44+
PgstacBackend::new_from_stringlike_and_tls(params, tls).await
3145
}
46+
}
3247

33-
fn new(pool: Pool<PostgresConnectionManager<NoTls>>) -> PgstacBackend {
34-
PgstacBackend { pool }
48+
impl<Tls> PgstacBackend<Tls>
49+
where
50+
Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
51+
<Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
52+
<Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
53+
<<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
54+
{
55+
/// Creates a new PgstacBackend from a string-like configuration and a tls.
56+
///
57+
/// # Examples
58+
///
59+
/// ```no_run
60+
/// use stac_server::PgstacBackend;
61+
///
62+
/// let tls = pgstac::make_unverified_tls();
63+
/// # tokio_test::block_on(async {
64+
/// let backend = PgstacBackend::new_from_stringlike_and_tls(
65+
/// "postgresql://username:password@localhost:5432/postgis",
66+
/// tls
67+
/// ).await.unwrap();
68+
/// # })
69+
/// ```
70+
pub async fn new_from_stringlike_and_tls(
71+
params: impl ToString,
72+
tls: Tls,
73+
) -> Result<PgstacBackend<Tls>> {
74+
let connection_manager = PostgresConnectionManager::new_from_stringlike(params, tls)?;
75+
let pool = Pool::builder().build(connection_manager).await?;
76+
Ok(PgstacBackend { pool })
3577
}
3678
}
3779

38-
impl Backend for PgstacBackend {
80+
impl<Tls> Backend for PgstacBackend<Tls>
81+
where
82+
Tls: MakeTlsConnect<Socket> + Clone + Send + Sync + 'static,
83+
<Tls as MakeTlsConnect<Socket>>::Stream: Send + Sync,
84+
<Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
85+
<<Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
86+
{
3987
fn has_item_search(&self) -> bool {
4088
true
4189
}

0 commit comments

Comments
 (0)