feat: select and save CA bundles per PostgreSQL connection
This commit is contained in:
@@ -53,19 +53,15 @@ pub struct QueryResult {
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub fn connect(connection: &str) -> Result<Self> {
|
||||
Self::connect_postgres(connection.parse()?)
|
||||
pub fn connect(connection: &str, root_cert: Option<&Path>) -> Result<Self> {
|
||||
Self::connect_postgres(connection.parse()?, root_cert)
|
||||
}
|
||||
|
||||
pub fn connect_postgres(mut config: Config) -> Result<Self> {
|
||||
pub fn connect_postgres(mut config: Config, root_cert: Option<&Path>) -> Result<Self> {
|
||||
if config.get_connect_timeout().is_none() {
|
||||
config.connect_timeout(Duration::from_secs(5));
|
||||
}
|
||||
let root_cert = std::env::var_os("PGSSLROOTCERT");
|
||||
let tls = MakeTlsConnector::new(postgres_tls(
|
||||
config.get_ssl_mode(),
|
||||
root_cert.as_deref().map(Path::new),
|
||||
)?);
|
||||
let tls = MakeTlsConnector::new(postgres_tls(config.get_ssl_mode(), root_cert)?);
|
||||
let mut client = config.connect(tls)?;
|
||||
// Views can invoke functions: enforce read-only on the server, not just in the UI.
|
||||
client.batch_execute(
|
||||
@@ -284,14 +280,14 @@ fn postgres_tls(mode: SslMode, root_cert: Option<&Path>) -> Result<TlsConnector>
|
||||
let mut tls = TlsConnector::builder();
|
||||
if let Some(path) = root_cert {
|
||||
if mode != SslMode::Require {
|
||||
return Err("PGSSLROOTCERT requires Require TLS (sslmode=require); plaintext fallback is not allowed.".into());
|
||||
return Err("A CA bundle requires Require TLS (sslmode=require); plaintext fallback is not allowed.".into());
|
||||
}
|
||||
let pem = std::fs::read(path)
|
||||
.map_err(|error| format!("Cannot read PGSSLROOTCERT CA file: {error}"))?;
|
||||
.map_err(|error| format!("Cannot read CA bundle: {error}"))?;
|
||||
let certificates = Certificate::stack_from_pem(&pem)
|
||||
.map_err(|error| format!("Invalid PGSSLROOTCERT PEM CA bundle: {error}"))?;
|
||||
.map_err(|error| format!("Invalid PEM CA bundle: {error}"))?;
|
||||
if certificates.is_empty() {
|
||||
return Err("PGSSLROOTCERT must contain at least one PEM CA certificate.".into());
|
||||
return Err("CA bundle must contain at least one PEM CA certificate.".into());
|
||||
}
|
||||
// An explicit bundle replaces system roots; certificate and hostname checks stay enabled.
|
||||
tls.disable_built_in_roots(true);
|
||||
@@ -333,16 +329,14 @@ mod tests {
|
||||
fn explicit_ca_bundle_fails_closed_on_insecure_modes_and_invalid_files() -> Result<()> {
|
||||
let bundle = tempfile::NamedTempFile::new()?;
|
||||
for mode in [SslMode::Prefer, SslMode::Disable] {
|
||||
let error = postgres_tls(mode, Some(bundle.path())).unwrap_err();
|
||||
assert!(error.to_string().contains("sslmode=require"));
|
||||
assert!(postgres_tls(mode, Some(bundle.path())).is_err());
|
||||
}
|
||||
for contents in ["", "not a PEM certificate", "-----BEGIN CERTIFICATE-----\ninvalid\n-----END CERTIFICATE-----"] {
|
||||
std::fs::write(bundle.path(), contents)?;
|
||||
assert!(postgres_tls(SslMode::Require, Some(bundle.path())).is_err());
|
||||
}
|
||||
let missing = bundle.path().with_extension("missing");
|
||||
let error = postgres_tls(SslMode::Require, Some(&missing)).unwrap_err();
|
||||
assert!(error.to_string().contains("Cannot read PGSSLROOTCERT"));
|
||||
assert!(postgres_tls(SslMode::Require, Some(&missing)).is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -396,7 +390,7 @@ mod tests {
|
||||
CREATE VIEW {schema}.sample_view AS SELECT id FROM {schema}.\"odd\"\"table\";
|
||||
CREATE TABLE {schema}.empty_table (id integer);"
|
||||
))?;
|
||||
let mut db = Database::connect(&connection)?;
|
||||
let mut db = Database::connect(&connection, None)?;
|
||||
let relations = db.relations()?;
|
||||
let table = relations
|
||||
.iter()
|
||||
|
||||
Reference in New Issue
Block a user