fix(postgres): 支持旧版 TLS 兼容模式
This commit is contained in:
16 files changed
+537
-20
No files matched your search
@@ -1,6 +1,7 @@
|
||||
use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime};
|
||||
use deadpool_postgres::{ManagerConfig, Pool, PoolError, RecyclingMethod, Runtime};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use openssl::ssl::{SslConnector, SslFiletype, SslMethod, SslVerifyMode};
|
||||
use percent_encoding::percent_decode_str;
|
||||
use rust_decimal::Decimal;
|
||||
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
|
||||
@@ -2117,17 +2118,27 @@ async fn connect_postgres_pool_attempt(
|
||||
// executor's ReconnectAndRetry path (see pool_error_action / do_execute
|
||||
// in query.rs).
|
||||
let mgr_config = ManagerConfig { recycling_method: RecyclingMethod::Fast };
|
||||
let tls_config = postgres_tls_config(
|
||||
&pg_config,
|
||||
&postgres_url.ssl_files,
|
||||
postgres_url.accepts_invalid_certs,
|
||||
postgres_url.verifies_hostname,
|
||||
)?;
|
||||
let mgr = deadpool_postgres::Manager::from_connect(
|
||||
pg_config.clone(),
|
||||
NoticeCapturingConnect { tls: tokio_postgres_rustls::MakeRustlsConnect::new(tls_config) },
|
||||
mgr_config,
|
||||
);
|
||||
let mgr = if postgres_url.legacy_tls {
|
||||
log::info!("PostgreSQL legacy TLS compatibility enabled");
|
||||
let tls = postgres_openssl_connector(
|
||||
&postgres_url.ssl_files,
|
||||
postgres_url.accepts_invalid_certs,
|
||||
postgres_url.verifies_hostname,
|
||||
)?;
|
||||
deadpool_postgres::Manager::from_connect(pg_config.clone(), NoticeCapturingConnect { tls }, mgr_config)
|
||||
} else {
|
||||
let tls_config = postgres_tls_config(
|
||||
&pg_config,
|
||||
&postgres_url.ssl_files,
|
||||
postgres_url.accepts_invalid_certs,
|
||||
postgres_url.verifies_hostname,
|
||||
)?;
|
||||
deadpool_postgres::Manager::from_connect(
|
||||
pg_config.clone(),
|
||||
NoticeCapturingConnect { tls: tokio_postgres_rustls::MakeRustlsConnect::new(tls_config) },
|
||||
mgr_config,
|
||||
)
|
||||
};
|
||||
let pool = Pool::builder(mgr)
|
||||
.max_size(10)
|
||||
.runtime(Runtime::Tokio1)
|
||||
@@ -2264,6 +2275,7 @@ pub struct PostgresCancelContext {
|
||||
pub accepts_invalid_certs: bool,
|
||||
pub verifies_hostname: bool,
|
||||
pub ssl_mode: SslMode,
|
||||
pub legacy_tls: bool,
|
||||
}
|
||||
|
||||
/// Build a TLS cancel context from the connection URL.
|
||||
@@ -2279,6 +2291,7 @@ pub fn build_postgres_cancel_context(url: &str) -> Option<PostgresCancelContext>
|
||||
accepts_invalid_certs: postgres_url.accepts_invalid_certs,
|
||||
verifies_hostname: postgres_url.verifies_hostname,
|
||||
ssl_mode: pg_config.get_ssl_mode(),
|
||||
legacy_tls: postgres_url.legacy_tls,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2293,12 +2306,19 @@ fn make_rustls_connect_from_context(
|
||||
Ok(tokio_postgres_rustls::MakeRustlsConnect::new(tls_config))
|
||||
}
|
||||
|
||||
fn make_openssl_connect_from_context(
|
||||
ctx: &PostgresCancelContext,
|
||||
) -> Result<postgres_openssl::MakeTlsConnector, String> {
|
||||
postgres_openssl_connector(&ctx.ssl_files, ctx.accepts_invalid_certs, ctx.verifies_hostname)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
struct PostgresConnectionUrl {
|
||||
url: String,
|
||||
ssl_files: PostgresSslFiles,
|
||||
accepts_invalid_certs: bool,
|
||||
verifies_hostname: bool,
|
||||
legacy_tls: bool,
|
||||
}
|
||||
|
||||
/// Inject TCP keepalive parameters into the PostgreSQL URL (only when the user has not explicitly specified them).
|
||||
@@ -2331,6 +2351,7 @@ fn postgres_connection_url(url: &str) -> Result<PostgresConnectionUrl, String> {
|
||||
ssl_files: PostgresSslFiles::default(),
|
||||
accepts_invalid_certs: postgres_sslmode_accepts_invalid_certs(pg_config.get_ssl_mode()),
|
||||
verifies_hostname: false,
|
||||
legacy_tls: false,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2341,6 +2362,7 @@ fn postgres_connection_url(url: &str) -> Result<PostgresConnectionUrl, String> {
|
||||
let mut kept_params = Vec::new();
|
||||
let mut accepts_invalid_certs = true;
|
||||
let mut verifies_hostname = false;
|
||||
let mut legacy_tls = false;
|
||||
|
||||
for param in query_string.split('&') {
|
||||
if param.is_empty() {
|
||||
@@ -2352,7 +2374,9 @@ fn postgres_connection_url(url: &str) -> Result<PostgresConnectionUrl, String> {
|
||||
continue;
|
||||
};
|
||||
|
||||
if key.eq_ignore_ascii_case("sslcert")
|
||||
if key.eq_ignore_ascii_case("legacy_tls") {
|
||||
legacy_tls = matches!(value.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on");
|
||||
} else if key.eq_ignore_ascii_case("sslcert")
|
||||
|| key.eq_ignore_ascii_case("sslkey")
|
||||
|| key.eq_ignore_ascii_case("sslrootcert")
|
||||
{
|
||||
@@ -2414,7 +2438,49 @@ fn postgres_connection_url(url: &str) -> Result<PostgresConnectionUrl, String> {
|
||||
sanitized_url.push_str(fragment);
|
||||
}
|
||||
|
||||
Ok(PostgresConnectionUrl { url: sanitized_url, ssl_files, accepts_invalid_certs, verifies_hostname })
|
||||
Ok(PostgresConnectionUrl { url: sanitized_url, ssl_files, accepts_invalid_certs, verifies_hostname, legacy_tls })
|
||||
}
|
||||
|
||||
fn postgres_openssl_connector(
|
||||
ssl_files: &PostgresSslFiles,
|
||||
accepts_invalid_certs: bool,
|
||||
verifies_hostname: bool,
|
||||
) -> Result<postgres_openssl::MakeTlsConnector, String> {
|
||||
let mut builder = SslConnector::builder(SslMethod::tls_client())
|
||||
.map_err(|e| format!("Failed to initialize PostgreSQL legacy TLS: {e}"))?;
|
||||
|
||||
if accepts_invalid_certs {
|
||||
builder.set_verify(SslVerifyMode::NONE);
|
||||
} else {
|
||||
builder.set_verify(SslVerifyMode::PEER);
|
||||
if let Some(path) = ssl_files.sslrootcert.as_deref() {
|
||||
builder
|
||||
.set_ca_file(path)
|
||||
.map_err(|e| format!("sslrootcert: failed to load CA certificate from {path}: {e}"))?;
|
||||
}
|
||||
}
|
||||
|
||||
match (ssl_files.sslcert.as_deref(), ssl_files.sslkey.as_deref()) {
|
||||
(Some(cert_path), Some(key_path)) => {
|
||||
builder
|
||||
.set_certificate_chain_file(cert_path)
|
||||
.map_err(|e| format!("sslcert: failed to load certificate from {cert_path}: {e}"))?;
|
||||
builder
|
||||
.set_private_key_file(key_path, SslFiletype::PEM)
|
||||
.map_err(|e| format!("sslkey: failed to load private key from {key_path}: {e}"))?;
|
||||
builder.check_private_key().map_err(|e| format!("sslkey: private key does not match sslcert: {e}"))?;
|
||||
}
|
||||
(Some(_), None) => return Err("PostgreSQL sslcert requires sslkey".to_string()),
|
||||
(None, Some(_)) => return Err("PostgreSQL sslkey requires sslcert".to_string()),
|
||||
(None, None) => {}
|
||||
}
|
||||
|
||||
let mut connector = postgres_openssl::MakeTlsConnector::new(builder.build());
|
||||
connector.set_callback(move |config, _| {
|
||||
config.set_verify_hostname(verifies_hostname);
|
||||
Ok(())
|
||||
});
|
||||
Ok(connector)
|
||||
}
|
||||
|
||||
fn postgres_tls_config(
|
||||
@@ -6337,8 +6403,25 @@ async fn cancel_postgres_query(
|
||||
) {
|
||||
let cancel_timeout = postgres_cancel_attempt_timeout(cancel_timeout, cancel_context);
|
||||
if let Some(ctx) = cancel_context {
|
||||
match make_rustls_connect_from_context(ctx) {
|
||||
Ok(tls) => match tokio::time::timeout(cancel_timeout, pg_cancel_token.cancel_query(tls)).await {
|
||||
let tls_result = if ctx.legacy_tls {
|
||||
match make_openssl_connect_from_context(ctx) {
|
||||
Ok(tls) => Some(tokio::time::timeout(cancel_timeout, pg_cancel_token.cancel_query(tls)).await),
|
||||
Err(err) => {
|
||||
log::warn!("Failed to build legacy TLS connector for cancel: {err}; falling back to NoTls cancel");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match make_rustls_connect_from_context(ctx) {
|
||||
Ok(tls) => Some(tokio::time::timeout(cancel_timeout, pg_cancel_token.cancel_query(tls)).await),
|
||||
Err(err) => {
|
||||
log::warn!("Failed to build TLS connector for cancel: {err}; falling back to NoTls cancel");
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(result) = tls_result {
|
||||
match result {
|
||||
Ok(Ok(())) => return,
|
||||
Ok(Err(err)) => {
|
||||
log::warn!("Failed to send PostgreSQL TLS cancel request: {err}");
|
||||
@@ -6352,9 +6435,6 @@ async fn cancel_postgres_query(
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::warn!("Failed to build TLS connector for cancel: {err}; falling back to NoTls cancel");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7231,7 +7311,16 @@ pub async fn copy_in(pool: &Pool, sql: &str, data: &[u8]) -> Result<(), String>
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use openssl::asn1::Asn1Time;
|
||||
use openssl::bn::{BigNum, MsbOption};
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::pkey::PKey;
|
||||
use openssl::rsa::Rsa;
|
||||
use openssl::ssl::{Ssl, SslAcceptor, SslMethod, SslVersion};
|
||||
use openssl::x509::extension::{BasicConstraints, KeyUsage, SubjectAlternativeName};
|
||||
use openssl::x509::{X509NameBuilder, X509};
|
||||
use std::cell::Cell;
|
||||
use std::pin::Pin;
|
||||
use std::process::Command;
|
||||
use std::time::Instant;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
@@ -7283,6 +7372,125 @@ mod tests {
|
||||
plain_socket.write_all(&postgres_error_response("identity probe unavailable")).await.unwrap();
|
||||
}
|
||||
|
||||
fn tls12_rsa_certificate_acceptor(cipher_list: &str) -> (SslAcceptor, Vec<u8>) {
|
||||
let key = PKey::from_rsa(Rsa::generate(2048).unwrap()).unwrap();
|
||||
let mut name = X509NameBuilder::new().unwrap();
|
||||
name.append_entry_by_text("CN", "localhost").unwrap();
|
||||
let name = name.build();
|
||||
|
||||
let mut serial = BigNum::new().unwrap();
|
||||
serial.rand(64, MsbOption::MAYBE_ZERO, false).unwrap();
|
||||
let serial = serial.to_asn1_integer().unwrap();
|
||||
let mut certificate = X509::builder().unwrap();
|
||||
certificate.set_version(2).unwrap();
|
||||
certificate.set_serial_number(&serial).unwrap();
|
||||
certificate.set_subject_name(&name).unwrap();
|
||||
certificate.set_issuer_name(&name).unwrap();
|
||||
certificate.set_pubkey(&key).unwrap();
|
||||
certificate.set_not_before(Asn1Time::days_from_now(0).unwrap().as_ref()).unwrap();
|
||||
certificate.set_not_after(Asn1Time::days_from_now(1).unwrap().as_ref()).unwrap();
|
||||
certificate.append_extension(BasicConstraints::new().critical().ca().build().unwrap()).unwrap();
|
||||
certificate
|
||||
.append_extension(KeyUsage::new().digital_signature().key_encipherment().key_cert_sign().build().unwrap())
|
||||
.unwrap();
|
||||
let subject_alt_name =
|
||||
SubjectAlternativeName::new().dns("localhost").build(&certificate.x509v3_context(None, None)).unwrap();
|
||||
certificate.append_extension(subject_alt_name).unwrap();
|
||||
certificate.sign(&key, MessageDigest::sha256()).unwrap();
|
||||
let certificate = certificate.build();
|
||||
let certificate_pem = certificate.to_pem().unwrap();
|
||||
|
||||
let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls_server()).unwrap();
|
||||
acceptor.set_min_proto_version(Some(SslVersion::TLS1_2)).unwrap();
|
||||
acceptor.set_max_proto_version(Some(SslVersion::TLS1_2)).unwrap();
|
||||
acceptor.set_cipher_list(cipher_list).unwrap();
|
||||
acceptor.set_private_key(&key).unwrap();
|
||||
acceptor.set_certificate(&certificate).unwrap();
|
||||
acceptor.check_private_key().unwrap();
|
||||
(acceptor.build(), certificate_pem)
|
||||
}
|
||||
|
||||
async fn accept_tls12_with_acceptor(
|
||||
mut socket: tokio::net::TcpStream,
|
||||
acceptor: &SslAcceptor,
|
||||
) -> Result<tokio_openssl::SslStream<tokio::net::TcpStream>, openssl::ssl::Error> {
|
||||
let mut ssl_request = [0_u8; 8];
|
||||
socket.read_exact(&mut ssl_request).await.unwrap();
|
||||
assert_eq!(ssl_request, [0, 0, 0, 8, 4, 210, 22, 47]);
|
||||
socket.write_all(b"S").await.unwrap();
|
||||
|
||||
let ssl = Ssl::new(acceptor.context()).unwrap();
|
||||
let mut tls_socket = tokio_openssl::SslStream::new(ssl, socket).unwrap();
|
||||
Pin::new(&mut tls_socket).accept().await?;
|
||||
Ok(tls_socket)
|
||||
}
|
||||
|
||||
async fn accept_tls12(
|
||||
socket: tokio::net::TcpStream,
|
||||
cipher_list: &str,
|
||||
) -> Result<tokio_openssl::SslStream<tokio::net::TcpStream>, openssl::ssl::Error> {
|
||||
let (acceptor, _) = tls12_rsa_certificate_acceptor(cipher_list);
|
||||
accept_tls12_with_acceptor(socket, &acceptor).await
|
||||
}
|
||||
|
||||
async fn authenticate_tls12_postgres(tls_socket: &mut tokio_openssl::SslStream<tokio::net::TcpStream>) {
|
||||
let startup_len = tls_socket.read_u32().await.unwrap();
|
||||
assert!(startup_len >= 8);
|
||||
let mut startup = vec![0_u8; startup_len as usize - 4];
|
||||
tls_socket.read_exact(&mut startup).await.unwrap();
|
||||
assert_eq!(&startup[..4], &[0, 3, 0, 0]);
|
||||
tls_socket
|
||||
.write_all(&[
|
||||
b'R', 0, 0, 0, 8, 0, 0, 0, 0, // AuthenticationOk
|
||||
b'K', 0, 0, 0, 12, 0, 0, 0, 1, 0, 0, 0, 2, // BackendKeyData
|
||||
b'Z', 0, 0, 0, 5, b'I', // ReadyForQuery
|
||||
])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut request = [0_u8; 1024];
|
||||
let read = tls_socket.read(&mut request).await.unwrap();
|
||||
assert!(read > 0, "client should issue the best-effort identity query");
|
||||
tls_socket.write_all(&postgres_error_response("identity probe unavailable")).await.unwrap();
|
||||
}
|
||||
|
||||
async fn serve_tls12_postgres(listener: TcpListener, cipher_list: &'static str, expect_handshake: bool) {
|
||||
let (socket, _) = listener.accept().await.unwrap();
|
||||
let handshake = accept_tls12(socket, cipher_list).await;
|
||||
if !expect_handshake {
|
||||
assert!(handshake.is_err(), "rustls unexpectedly negotiated a static RSA cipher suite");
|
||||
return;
|
||||
}
|
||||
let mut tls_socket = handshake.unwrap();
|
||||
authenticate_tls12_postgres(&mut tls_socket).await;
|
||||
}
|
||||
|
||||
async fn serve_tls12_postgres_with_acceptor(listener: TcpListener, acceptor: SslAcceptor, expect_handshake: bool) {
|
||||
let (socket, _) = listener.accept().await.unwrap();
|
||||
let handshake = accept_tls12_with_acceptor(socket, &acceptor).await;
|
||||
if !expect_handshake {
|
||||
assert!(handshake.is_err(), "TLS handshake unexpectedly succeeded");
|
||||
return;
|
||||
}
|
||||
let mut tls_socket = handshake.unwrap();
|
||||
authenticate_tls12_postgres(&mut tls_socket).await;
|
||||
}
|
||||
|
||||
async fn serve_static_rsa_postgres_with_cancel(listener: TcpListener) {
|
||||
let (main_socket, _) = listener.accept().await.unwrap();
|
||||
let mut main_tls = accept_tls12(main_socket, "AES128-GCM-SHA256").await.unwrap();
|
||||
authenticate_tls12_postgres(&mut main_tls).await;
|
||||
|
||||
let (cancel_socket, _) = listener.accept().await.unwrap();
|
||||
let mut cancel_tls = accept_tls12(cancel_socket, "AES128-GCM-SHA256").await.unwrap();
|
||||
let mut cancel_request = [0_u8; 16];
|
||||
cancel_tls.read_exact(&mut cancel_request).await.unwrap();
|
||||
assert_eq!(u32::from_be_bytes(cancel_request[0..4].try_into().unwrap()), 16);
|
||||
assert_eq!(u32::from_be_bytes(cancel_request[4..8].try_into().unwrap()), 80_877_102);
|
||||
assert_eq!(u32::from_be_bytes(cancel_request[8..12].try_into().unwrap()), 1);
|
||||
assert_eq!(u32::from_be_bytes(cancel_request[12..16].try_into().unwrap()), 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn postgres_prefer_retries_plaintext_after_tls_handshake_failure() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
@@ -7295,6 +7503,104 @@ mod tests {
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn postgres_default_tls_rejects_static_rsa_key_exchange() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let server = tokio::spawn(serve_tls12_postgres(listener, "AES128-GCM-SHA256", false));
|
||||
let url = format!("postgres://postgres@127.0.0.1:{port}/postgres?sslmode=require");
|
||||
|
||||
let error = connect_with_optional_local_timezone(&url, Duration::from_secs(2), None)
|
||||
.await
|
||||
.expect_err("rustls must not negotiate static RSA key exchange");
|
||||
assert!(error.to_ascii_lowercase().contains("tls"), "{error}");
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn postgres_legacy_tls_connects_with_static_rsa_key_exchange() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let server = tokio::spawn(serve_tls12_postgres(listener, "AES128-GCM-SHA256", true));
|
||||
let url = format!("postgres://postgres@127.0.0.1:{port}/postgres?sslmode=require&legacy_tls=true");
|
||||
|
||||
let result = connect_with_optional_local_timezone(&url, Duration::from_secs(2), None).await;
|
||||
assert!(result.is_ok(), "legacy TLS should negotiate static RSA: {result:?}");
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn postgres_legacy_tls_verify_ca_uses_configured_root_without_hostname_check() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let (acceptor, certificate_pem) = tls12_rsa_certificate_acceptor("AES128-GCM-SHA256");
|
||||
let root_dir = tempfile::tempdir().unwrap();
|
||||
let root_path = root_dir.path().join("root.pem");
|
||||
std::fs::write(&root_path, certificate_pem).unwrap();
|
||||
let root_path_text = root_path.to_string_lossy();
|
||||
let encoded_root =
|
||||
percent_encoding::utf8_percent_encode(root_path_text.as_ref(), percent_encoding::NON_ALPHANUMERIC);
|
||||
let server = tokio::spawn(serve_tls12_postgres_with_acceptor(listener, acceptor, true));
|
||||
let url = format!(
|
||||
"postgres://postgres@127.0.0.1:{port}/postgres?sslmode=verify-ca&legacy_tls=true&sslrootcert={encoded_root}"
|
||||
);
|
||||
|
||||
let result = connect_with_optional_local_timezone(&url, Duration::from_secs(2), None).await;
|
||||
assert!(result.is_ok(), "verify-ca should trust the configured root without checking the host: {result:?}");
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn postgres_legacy_tls_verify_full_rejects_hostname_mismatch() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let (acceptor, certificate_pem) = tls12_rsa_certificate_acceptor("AES128-GCM-SHA256");
|
||||
let root_dir = tempfile::tempdir().unwrap();
|
||||
let root_path = root_dir.path().join("root.pem");
|
||||
std::fs::write(&root_path, certificate_pem).unwrap();
|
||||
let root_path_text = root_path.to_string_lossy();
|
||||
let encoded_root =
|
||||
percent_encoding::utf8_percent_encode(root_path_text.as_ref(), percent_encoding::NON_ALPHANUMERIC);
|
||||
let server = tokio::spawn(serve_tls12_postgres_with_acceptor(listener, acceptor, false));
|
||||
let url = format!(
|
||||
"postgres://postgres@127.0.0.1:{port}/postgres?sslmode=verify-full&legacy_tls=true&sslrootcert={encoded_root}"
|
||||
);
|
||||
|
||||
let error = connect_with_optional_local_timezone(&url, Duration::from_secs(2), None)
|
||||
.await
|
||||
.expect_err("verify-full must reject a certificate for another host");
|
||||
assert!(error.to_ascii_lowercase().contains("certificate"), "{error}");
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn postgres_default_tls_still_connects_with_ecdhe_rsa() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let server = tokio::spawn(serve_tls12_postgres(listener, "ECDHE-RSA-AES128-GCM-SHA256", true));
|
||||
let url = format!("postgres://postgres@127.0.0.1:{port}/postgres?sslmode=require");
|
||||
|
||||
let result = connect_with_optional_local_timezone(&url, Duration::from_secs(2), None).await;
|
||||
assert!(result.is_ok(), "default TLS should keep negotiating ECDHE-RSA: {result:?}");
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn postgres_legacy_tls_cancel_uses_static_rsa_connector() {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let server = tokio::spawn(serve_static_rsa_postgres_with_cancel(listener));
|
||||
let url = format!("postgres://postgres@127.0.0.1:{port}/postgres?sslmode=require&legacy_tls=true");
|
||||
|
||||
let pool = connect_with_optional_local_timezone(&url, Duration::from_secs(2), None).await.unwrap();
|
||||
let client = pool.get().await.unwrap();
|
||||
let context = build_postgres_cancel_context(&url).unwrap();
|
||||
assert!(context.legacy_tls);
|
||||
cancel_postgres_query(client.cancel_token(), Some(&context), Duration::from_secs(2)).await;
|
||||
|
||||
server.await.unwrap();
|
||||
}
|
||||
|
||||
fn pg_array_binary(element_oid: u32, elements: &[Option<Vec<u8>>]) -> Vec<u8> {
|
||||
let mut raw = Vec::new();
|
||||
raw.extend_from_slice(&1_i32.to_be_bytes());
|
||||
@@ -9038,6 +9344,21 @@ mod tests {
|
||||
assert!(!parsed.verifies_hostname);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postgres_connection_url_extracts_legacy_tls_before_driver_parse() {
|
||||
let parsed =
|
||||
postgres_connection_url("postgres://localhost/db?sslmode=require&legacy_tls=true&application_name=dbx")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(parsed.url, "postgres://localhost/db?sslmode=require&application_name=dbx");
|
||||
assert!(parsed.legacy_tls);
|
||||
tokio_postgres::Config::from_str(&parsed.url).unwrap();
|
||||
|
||||
let parsed = postgres_connection_url("postgres://localhost/db?legacy_tls=false").unwrap();
|
||||
assert_eq!(parsed.url, "postgres://localhost/db");
|
||||
assert!(!parsed.legacy_tls);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn postgres_connection_url_normalizes_channel_binding_require_to_prefer() {
|
||||
let parsed =
|
||||
@@ -9187,6 +9508,7 @@ mod tests {
|
||||
accepts_invalid_certs: true,
|
||||
verifies_hostname: false,
|
||||
ssl_mode: SslMode::Require,
|
||||
legacy_tls: false,
|
||||
})
|
||||
),
|
||||
Duration::from_secs(5)
|
||||
|
||||
Reference in new issue
Block a user