fix(meta-srv): use NoTls for disabled and Unix socket Postgres KV backends (#9059)

* fix(meta-srv): use NoTls for disabled and Unix socket Postgres KV backends

Signed-off-by: Tyagiquamar <mohdquamartyagi@gmail.com>

* fix(meta-srv): treat Postgres config as unix socket only when every host is a socket

tokio-postgres dials hostaddr over TCP even when host is a socket path,
and a mixed host list with Require/VerifyFull TLS would otherwise end up
sending plaintext over TCP. is_unix_socket_url now parses via
tokio_postgres::Config and requires no hostaddr and all-Unix hosts.
Adds regression cases for the libpq keyword form, the user@ percent
encoded socket URL, and the mixed host / hostaddr negatives.

Signed-off-by: Tyagiquamar <mohdquamartyagi@gmail.com>

---------

Signed-off-by: Tyagiquamar <mohdquamartyagi@gmail.com>
This commit is contained in:
Mohd Quamar Tyagi
2026-09-29 00:07:49 +00:00
committed by GitHub
parent 3c0e2a8d55
commit dd2c1d1aca
+108 -12
View File
@@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#[cfg(unix)]
use std::str::FromStr;
use common_error::ext::BoxedError;
use common_meta::election::ElectionRef;
use common_meta::election::rds::postgres::{ElectionPgClient, PgElection};
@@ -20,10 +23,13 @@ use common_meta::kv_backend::rds::PgStore;
use common_meta::kv_backend::rds::postgres::{
TlsMode as PgTlsMode, TlsOption as PgTlsOption, create_postgres_tls_connector,
};
use common_telemetry::warn;
use deadpool_postgres::{Config, Runtime};
use servers::tls::TlsOption;
use snafu::{OptionExt, ResultExt};
use tokio_postgres::NoTls;
#[cfg(unix)]
use tokio_postgres::config::Host;
use crate::error::{self, Result};
@@ -60,23 +66,57 @@ pub async fn create_postgres_pool(
})?;
cfg.url = Some(postgres_url.clone());
let pool = if let Some(tls_config) = tls_config {
let pg_tls_config = convert_tls_option(&tls_config);
let tls_connector =
create_postgres_tls_connector(&pg_tls_config).map_err(|e| error::Error::Other {
source: BoxedError::new(e),
location: snafu::Location::new(file!(), line!(), 0),
})?;
cfg.create_pool(Some(Runtime::Tokio1), tls_connector)
.context(error::CreatePostgresPoolSnafu)?
} else {
cfg.create_pool(Some(Runtime::Tokio1), NoTls)
.context(error::CreatePostgresPoolSnafu)?
let is_unix_socket = is_unix_socket_url(postgres_url);
if is_unix_socket
&& matches!(tls_config.as_ref(), Some(t) if t.mode != servers::tls::TlsMode::Disable)
{
warn!(
"TLS is not supported for Unix domain socket PostgreSQL connections, falling back to NoTls"
);
}
let pool = match tls_config {
Some(tls_config)
if tls_config.mode != servers::tls::TlsMode::Disable && !is_unix_socket =>
{
let pg_tls_config = convert_tls_option(&tls_config);
let tls_connector =
create_postgres_tls_connector(&pg_tls_config).map_err(|e| error::Error::Other {
source: BoxedError::new(e),
location: snafu::Location::new(file!(), line!(), 0),
})?;
cfg.create_pool(Some(Runtime::Tokio1), tls_connector)
.context(error::CreatePostgresPoolSnafu)?
}
_ => cfg
.create_pool(Some(Runtime::Tokio1), NoTls)
.context(error::CreatePostgresPoolSnafu)?,
};
Ok(pool)
}
#[cfg(unix)]
fn is_unix_socket_url(url: &str) -> bool {
let Ok(cfg) = tokio_postgres::Config::from_str(url) else {
return false;
};
// tokio-postgres dials `hostaddr` over TCP even when `host` is a socket path,
// so treat the config as a socket only when every host is a Unix socket and
// no `hostaddr` is set.
cfg.get_hostaddrs().is_empty()
&& !cfg.get_hosts().is_empty()
&& cfg
.get_hosts()
.iter()
.all(|host| matches!(host, Host::Unix(_)))
}
#[cfg(not(unix))]
fn is_unix_socket_url(_: &str) -> bool {
false
}
/// Builds a Postgres-backed metadata [`KvBackendRef`].
///
/// * `store_addrs` - Postgres connection URLs; only the first address is used.
@@ -150,3 +190,59 @@ pub async fn build_postgres_election(
.await
.context(error::KvBackendSnafu)
}
#[cfg(test)]
mod tests {
use super::is_unix_socket_url;
#[test]
fn detects_postgres_unix_socket_url() {
#[cfg(unix)]
{
// libpq keyword-value form (issue #7734)
assert!(is_unix_socket_url(
"host=/var/run/postgresql dbname=greptime user=greptime password=secret"
));
// standard postgres URL with percent-encoded unix socket directory
assert!(is_unix_socket_url(
"postgresql://user:pw@%2Fvar%2Frun%2Fpostgresql/mydb"
));
assert!(is_unix_socket_url(
"postgresql://user@%2Fvar%2Frun%2Fpostgresql/db"
));
// postgres URL with socket dir in query param
assert!(is_unix_socket_url(
"postgresql:///mydb?host=%2Fvar%2Frun%2Fpostgresql"
));
assert!(is_unix_socket_url(
"postgresql://user:secret@/mydb?host=%2Fvar%2Frun%2Fpostgresql"
));
// TCP URLs should not be classified as unix socket
assert!(!is_unix_socket_url(
"postgresql://user:pw@localhost:5432/mydb"
));
assert!(!is_unix_socket_url("postgresql://user@localhost/db"));
assert!(!is_unix_socket_url(
"host=127.0.0.1 port=5432 dbname=greptime user=greptime password=secret"
));
// mixed socket and TCP hosts must not be treated as a socket-only config:
// a Require/VerifyFull TLS config would otherwise end up in plaintext over TCP
assert!(!is_unix_socket_url(
"host=/var/run/postgresql,db.example.com"
));
// tokio-postgres dials `hostaddr` over TCP even when `host` is a socket path
assert!(!is_unix_socket_url("host=/tmp hostaddr=10.0.0.5"));
}
#[cfg(not(unix))]
{
assert!(!is_unix_socket_url(
"host=/var/run/postgresql dbname=greptime user=greptime password=secret"
));
assert!(!is_unix_socket_url(
"postgresql://user:pw@localhost:5432/mydb"
));
}
}
}