Skip to content

Commit c073a4c

Browse files
committed
Eager parse certificate. Better but I kinda hate it
Signed-off-by: itowlson <ivan.towlson@fermyon.com>
1 parent 0ea7f1d commit c073a4c

3 files changed

Lines changed: 46 additions & 12 deletions

File tree

crates/factor-outbound-pg/src/client.rs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,27 @@ pub trait ClientFactory: Default + Send + Sync + 'static {
2323
/// The type of client produced by `get_client`.
2424
type Client: Client;
2525
/// Gets a client from the factory.
26-
async fn get_client(&self, address: &str, root_ca: Option<&String>) -> Result<Self::Client>;
26+
async fn get_client(
27+
&self,
28+
address: &str,
29+
root_ca: Option<HashableCertificate>,
30+
) -> Result<Self::Client>;
31+
}
32+
33+
#[derive(Clone)]
34+
pub struct HashableCertificate {
35+
certificate: native_tls::Certificate,
36+
hash: String,
37+
}
38+
39+
impl HashableCertificate {
40+
pub fn from_pem(text: &str) -> anyhow::Result<Self> {
41+
let cert_bytes = text.as_bytes();
42+
let hash = spin_common::sha256::hex_digest_from_bytes(cert_bytes);
43+
let certificate =
44+
native_tls::Certificate::from_pem(cert_bytes).context("invalid root certificate")?;
45+
Ok(Self { certificate, hash })
46+
}
2747
}
2848

2949
/// A `ClientFactory` that uses a connection pool per address.
@@ -43,8 +63,16 @@ impl Default for PooledTokioClientFactory {
4363
impl ClientFactory for PooledTokioClientFactory {
4464
type Client = deadpool_postgres::Object;
4565

46-
async fn get_client(&self, address: &str, root_ca: Option<&String>) -> Result<Self::Client> {
47-
let pool_key = (address.to_string(), root_ca.cloned());
66+
async fn get_client(
67+
&self,
68+
address: &str,
69+
root_ca: Option<HashableCertificate>,
70+
) -> Result<Self::Client> {
71+
let (root_ca, root_ca_hash) = match root_ca {
72+
None => (None, None),
73+
Some(HashableCertificate { certificate, hash }) => (Some(certificate), Some(hash)),
74+
};
75+
let pool_key = (address.to_string(), root_ca_hash);
4876
let pool = self
4977
.pools
5078
.try_get_with_by_ref(&pool_key, || create_connection_pool(address, root_ca))
@@ -58,7 +86,7 @@ impl ClientFactory for PooledTokioClientFactory {
5886
/// Creates a Postgres connection pool for the given address.
5987
fn create_connection_pool(
6088
address: &str,
61-
root_ca: Option<&String>,
89+
root_ca: Option<native_tls::Certificate>,
6290
) -> Result<deadpool_postgres::Pool> {
6391
let config = address
6492
.parse::<tokio_postgres::Config>()
@@ -75,8 +103,7 @@ fn create_connection_pool(
75103
} else {
76104
let mut builder = TlsConnector::builder();
77105
if let Some(root_ca) = root_ca {
78-
let cert_bytes = root_ca.as_bytes();
79-
builder.add_root_certificate(native_tls::Certificate::from_pem(cert_bytes)?);
106+
builder.add_root_certificate(root_ca);
80107
}
81108
let connector = MakeTlsConnector::new(builder.build()?);
82109
deadpool_postgres::Manager::from_config(config, connector, mgr_config)

crates/factor-outbound-pg/src/host.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ use tracing::field::Empty;
1010
use tracing::instrument;
1111
use tracing::Level;
1212

13-
use crate::client::{Client, ClientFactory};
13+
use crate::client::{Client, ClientFactory, HashableCertificate};
1414
use crate::InstanceState;
1515

1616
impl<CF: ClientFactory> InstanceState<CF> {
1717
async fn open_connection<Conn: 'static>(
1818
&mut self,
1919
address: &str,
20-
root_ca: Option<&String>,
20+
root_ca: Option<HashableCertificate>,
2121
) -> Result<Resource<Conn>, v4::Error> {
2222
self.connections
2323
.push(
@@ -143,7 +143,7 @@ impl<CF: ClientFactory> v3::HostConnection for InstanceState<CF> {
143143

144144
pub(crate) struct ConnectionBuilder {
145145
address: String,
146-
root_ca: Option<String>,
146+
root_ca: Option<HashableCertificate>,
147147
}
148148

149149
impl<CF: ClientFactory> v4::HostConnectionBuilder for InstanceState<CF> {
@@ -165,11 +165,13 @@ impl<CF: ClientFactory> v4::HostConnectionBuilder for InstanceState<CF> {
165165
self_: Resource<v4::ConnectionBuilder>,
166166
certificate: String,
167167
) -> Result<(), v4::Error> {
168+
let root_ca = HashableCertificate::from_pem(&certificate)
169+
.map_err(|e| v4::Error::Other(format!("invalid root certificate: {e}")))?;
168170
let builder = self
169171
.builders
170172
.get_mut(self_.rep())
171173
.ok_or_else(|| v4::Error::ConnectionFailed("no builder found".into()))?;
172-
builder.root_ca = Some(certificate);
174+
builder.root_ca = Some(root_ca);
173175
Ok(())
174176
}
175177

@@ -184,7 +186,7 @@ impl<CF: ClientFactory> v4::HostConnectionBuilder for InstanceState<CF> {
184186
// borrow checker gets pedantic here, so we need to outsmart it
185187
let address = builder.address.clone();
186188
let root_ca = builder.root_ca.clone();
187-
let conn = self.open_connection(&address, root_ca.as_ref()).await;
189+
let conn = self.open_connection(&address, root_ca).await;
188190
conn
189191
}
190192

crates/factor-outbound-pg/tests/factor_test.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use anyhow::{bail, Result};
22
use spin_factor_outbound_networking::OutboundNetworkingFactor;
33
use spin_factor_outbound_pg::client::Client;
44
use spin_factor_outbound_pg::client::ClientFactory;
5+
use spin_factor_outbound_pg::client::HashableCertificate;
56
use spin_factor_outbound_pg::OutboundPgFactor;
67
use spin_factor_variables::VariablesFactor;
78
use spin_factors::{anyhow, RuntimeFactors};
@@ -112,7 +113,11 @@ pub struct MockClient {}
112113
#[async_trait]
113114
impl ClientFactory for MockClientFactory {
114115
type Client = MockClient;
115-
async fn get_client(&self, _address: &str, _root_ca: Option<&String>) -> Result<Self::Client> {
116+
async fn get_client(
117+
&self,
118+
_address: &str,
119+
_root_ca: Option<HashableCertificate>,
120+
) -> Result<Self::Client> {
116121
Ok(MockClient {})
117122
}
118123
}

0 commit comments

Comments
 (0)