Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .schema/pgdog.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@
"tls_certificate": null,
"tls_client_ca_certificate": null,
"tls_client_required": false,
"tls_client_validate_cn": false,
"tls_private_key": null,
"tls_server_ca_certificate": null,
"tls_verify": "prefer",
Expand Down Expand Up @@ -1126,11 +1125,6 @@
"type": "boolean",
"default": false
},
"tls_client_validate_cn": {
"description": "Validate that the CN of the certificate matches the user's name.\nThis is part of our mTLS implementation. The certificate authority can\nissue certificates that PgDog will validate for both authenticity and authentication.",
"type": "boolean",
"default": false
},
"tls_private_key": {
"description": "Path to the TLS private key PgDog will use to setup TLS connections with clients.\n\nhttps://docs.pgdog.dev/configuration/pgdog.toml/general/#tls_private_key",
"type": [
Expand Down
1 change: 0 additions & 1 deletion integration/tls/pgdog.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
tls_certificate = "integration/tls/server.crt"
tls_private_key = "integration/tls/server.key"
tls_client_ca_certificate = "integration/tls/ca.crt"
tls_client_validate_cn = true

[[databases]]
name = "pgdog"
Expand Down
7 changes: 0 additions & 7 deletions pgdog-config/src/general.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,6 @@ pub struct General {
/// https://docs.pgdog.dev/configuration/pgdog.toml/general/#tls_client_ca_certificate
pub tls_client_ca_certificate: Option<PathBuf>,

/// Validate that the CN of the certificate matches the user's name.
/// This is part of our mTLS implementation. The certificate authority can
/// issue certificates that PgDog will validate for both authenticity and authentication.
#[serde(default)]
pub tls_client_validate_cn: bool,

/// How long to wait for active clients to finish transactions when shutting down.
///
/// _Default:_ `60000`
Expand Down Expand Up @@ -801,7 +795,6 @@ impl Default for General {
tls_verify: Self::default_tls_verify(),
tls_server_ca_certificate: Self::tls_server_ca_certificate(),
tls_client_ca_certificate: Self::tls_client_ca_certificate(),
tls_client_validate_cn: bool::default(),
shutdown_timeout: Self::default_shutdown_timeout(),
shutdown_termination_timeout: Self::default_shutdown_termination_timeout(),
broadcast_address: Self::broadcast_address(),
Expand Down
2 changes: 1 addition & 1 deletion pgdog/src/backend/databases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ impl Databases {

// Launch all clusters
for cluster in self.all().values() {
if cluster.passwords().is_empty() && !cluster.mutual_tls() {
if cluster.passwords().is_empty() && cluster.identity().is_none() {
warn!(
r#"disabling pool for user "{}" and database "{}", password not set"#,
cluster.user(),
Expand Down
9 changes: 0 additions & 9 deletions pgdog/src/backend/pool/cluster.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ pub struct Cluster {
resharding_replication_retry_max_attempts: usize,
resharding_replication_retry_min_delay: Duration,
regex_parser: RegexParser,
mutual_tls: bool,
identity: Option<String>,
}

Expand Down Expand Up @@ -174,7 +173,6 @@ pub struct ClusterConfig<'a> {
pub resharding_replication_retry_min_delay: u64,
pub regex_parser_limit: usize,
pub pub_sub_enabled: bool,
pub mutual_tls: bool,
pub identity: &'a Option<String>,
}

Expand Down Expand Up @@ -238,7 +236,6 @@ impl<'a> ClusterConfig<'a> {
resharding_replication_retry_min_delay: general.resharding_replication_retry_min_delay,
regex_parser_limit: general.regex_parser_limit,
pub_sub_enabled: general.pub_sub_enabled(),
mutual_tls: config.general.tls_client_validate_cn,
identity: &user.identity,
}
}
Expand Down Expand Up @@ -285,7 +282,6 @@ impl Cluster {
resharding_replication_retry_min_delay,
regex_parser_limit,
pub_sub_enabled,
mutual_tls,
identity,
} = config;

Expand Down Expand Up @@ -346,7 +342,6 @@ impl Cluster {
resharding_replication_retry_min_delay,
),
regex_parser: RegexParser::new(regex_parser_limit, query_parser),
mutual_tls,
identity: identity.clone(),
}
}
Expand Down Expand Up @@ -540,10 +535,6 @@ impl Cluster {
&self.multi_tenant
}

pub fn mutual_tls(&self) -> bool {
self.mutual_tls
}

/// Get replication configuration for this cluster.
pub fn replication_sharding_config(&self) -> Option<ReplicationConfig> {
self.replication_sharding
Expand Down
126 changes: 72 additions & 54 deletions pgdog/src/frontend/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,61 @@ impl Client {
}
}

/// Authenticate a client against the configured password(s) using the
/// requested authentication method.
///
/// Returns `false` if no passwords are configured or the credentials the
/// client provided don't match.
async fn check_password(
stream: &mut Stream,
user: &str,
auth_type: &AuthType,
passwords: &[PasswordKind],
) -> Result<bool, Error> {
if passwords.is_empty() {
return Ok(false);
}

let ok = match auth_type {
AuthType::Md5 => {
let md5 = md5::Client::new(
user,
&passwords.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
);
stream.send_flush(&md5.challenge()).await?;
let password = Password::from_bytes(stream.read().await?.to_bytes()?)?;
if let Password::PasswordMessage { response } = password {
md5.check(&response)
} else {
false
}
}

AuthType::Scram => {
stream.send_flush(&Authentication::scram()).await?;

let scram = Server::new(passwords);
let res = scram.handle(stream).await;
matches!(res, Ok(true))
}

AuthType::Plain => {
stream
.send_flush(&Authentication::ClearTextPassword)
.await?;
let response = stream.read().await?;
let response = Password::from_bytes(response.to_bytes()?)?;
passwords
.iter()
.any(|p| Some(p.as_str()) == response.password())
}

AuthType::Trust => true,
};

Ok(ok)
}

/// Create new frontend client from the given TCP stream.
async fn login(
mut stream: Stream,
Expand All @@ -153,7 +208,6 @@ impl Client {
let admin_password = &config.config.admin.password;
let auth_type = &config.config.general.auth_type;
let passthrough = config.config.general.passthrough_auth();
let validate_cn = config.config.general.tls_client_validate_cn;
let id = BackendKeyData::new_client(protocol_version);
let comms = ClientComms::new(&id);

Expand All @@ -180,62 +234,26 @@ impl Client {
} else {
false
}
} else if validate_cn {
// This checks that the certificate CN (common name)
// matches the user identity exactly. If the client is not connecting with TLS,
// this will fail.
//
// This is part of our mTLS implementation.
//
stream.tls_cn() == databases::databases().identity((user, database))
} else if admin {
// The admin database is virtual and never present in the cluster
// map, so authenticate directly against the configured admin password.
let passwords = [PasswordKind::Plain(admin_password.clone())];
Self::check_password(&mut stream, user, auth_type, &passwords).await?
} else {
let passwords = if admin {
Some(vec![PasswordKind::Plain(admin_password.clone())])
} else {
databases::databases()
.passwords((user, database))
.map(|p| p.to_vec())
};

if let Some(passwords) = passwords {
match auth_type {
AuthType::Md5 => {
let md5 = md5::Client::new(
user,
&passwords.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
);
stream.send_flush(&md5.challenge()).await?;
let password = Password::from_bytes(stream.read().await?.to_bytes()?)?;
if let Password::PasswordMessage { response } = password {
md5.check(&response)
} else {
false
}
}

AuthType::Scram => {
stream.send_flush(&Authentication::scram()).await?;

let scram = Server::new(&passwords);
let res = scram.handle(&mut stream).await;
matches!(res, Ok(true))
match databases::databases().cluster((user, database)) {
Ok(cluster) => {
if let Some(identity) = cluster.identity() {
// mTLS authentication: the client certificate identity
// must match the configured user identity.
stream.tls_identity() == Some(identity)
} else {
// Password authentication.
Self::check_password(&mut stream, user, auth_type, cluster.passwords())
.await?
}

AuthType::Plain => {
stream
.send_flush(&Authentication::ClearTextPassword)
.await?;
let response = stream.read().await?;
let response = Password::from_bytes(response.to_bytes()?)?;
passwords
.iter()
.any(|p| Some(p.as_str()) == response.password())
}

AuthType::Trust => true,
}
} else {
false

Err(_) => false,
}
};

Expand Down
6 changes: 3 additions & 3 deletions pgdog/src/frontend/listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::backend::databases::{databases, reload, shutdown};
use crate::config::config;
use crate::frontend::client::query_engine::two_pc::Manager;
use crate::net::messages::{hello::SslReply, NegotiateProtocolVersion, Startup};
use crate::net::tls::{acceptor, peer_cn};
use crate::net::tls::{acceptor, peer_identity};
use crate::net::{self, tweak, Stream};
use crate::sighup::Sighup;
use tokio::net::{TcpListener, TcpStream};
Expand Down Expand Up @@ -197,11 +197,11 @@ impl Listener {
return Ok(());
}
};
let tls_cn = peer_cn(cipher.get_ref().1);
let tls_identity = peer_identity(cipher.get_ref().1);
stream = Stream::tls(
tokio_rustls::TlsStream::Server(cipher),
config.config.memory.net_buffer,
tls_cn,
tls_identity,
);
} else {
stream.send_flush(&SslReply::No).await?;
Expand Down
17 changes: 9 additions & 8 deletions pgdog/src/net/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct Stream {
inner: StreamInner,
io_in_progress: bool,
capacity: usize,
tls_cn: Option<String>,
tls_identity: Option<String>,
}

impl AsyncRead for Stream {
Expand Down Expand Up @@ -101,21 +101,21 @@ impl Stream {
inner: StreamInner::Plain(BufStream::with_capacity(capacity, capacity, stream)),
io_in_progress: false,
capacity,
tls_cn: None,
tls_identity: None,
}
}

/// Wrap an encrypted TCP stream.
pub fn tls(
stream: tokio_rustls::TlsStream<TcpStream>,
capacity: usize,
tls_cn: Option<String>,
tls_identity: Option<String>,
) -> Self {
Self {
inner: StreamInner::Tls(BufStream::with_capacity(capacity, capacity, stream)),
io_in_progress: false,
capacity,
tls_cn,
tls_identity,
}
}

Expand All @@ -125,13 +125,14 @@ impl Stream {
inner: StreamInner::DevNull,
io_in_progress: false,
capacity: 0,
tls_cn: None,
tls_identity: None,
}
}

/// Get the Common Name (CN) from the client's TLS certificate, if any.
pub fn tls_cn(&self) -> Option<&str> {
self.tls_cn.as_deref()
/// Get the hostname identity (SAN dNSName, falling back to Subject CN)
/// from the client's TLS certificate, if any.
pub fn tls_identity(&self) -> Option<&str> {
self.tls_identity.as_deref()
}

/// This is a TLS stream.
Expand Down
Loading
Loading