Skip to content

Commit 319acb5

Browse files
zxaoslevkk
andauthored
add tls_client_required setting (#587)
* add tls_client_required setting * reword TLS error message to be more consistent Co-authored-by: Lev Kokotov <levkk@users.noreply.github.com> * warn if TLS should be enforced * format fixes --------- Co-authored-by: Lev Kokotov <levkk@users.noreply.github.com>
1 parent 4855256 commit 319acb5

11 files changed

Lines changed: 98 additions & 22 deletions

File tree

example.pgdog.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ tls_certificate = "relative/or/absolute/path/to/certificate.pem"
106106
# Path to PEM-encoded TLS certificate private key
107107
tls_private_key = "relative/or/absolute/path/to/private_key.pem"
108108

109+
# Require clients to use TLS when connecting
110+
#
111+
# Default: false
112+
tls_client_required = false
113+
109114
# TLS mode for Postgres server connections.
110115
#
111116
# Default: disabled

integration/rust/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
pub mod setup;
2+
pub mod utils;

integration/rust/src/utils.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use super::setup::admin_sqlx;
2+
use sqlx::{Executor, Row};
3+
4+
pub async fn assert_setting_str(name: &str, expected: &str) {
5+
let admin = admin_sqlx().await;
6+
let rows = admin.fetch_all("SHOW CONFIG").await.unwrap();
7+
let mut found = false;
8+
for row in rows {
9+
let db_name: String = row.get(0);
10+
let value: String = row.get(1);
11+
12+
if name == db_name {
13+
found = true;
14+
assert_eq!(value, expected);
15+
}
16+
}
17+
18+
assert!(found);
19+
}

integration/rust/tests/integration/auth.rs

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use rust::setup::admin_sqlx;
2+
use rust::utils::assert_setting_str;
23
use serial_test::serial;
3-
use sqlx::{Connection, Executor, PgConnection, Row};
4+
use sqlx::{Connection, Executor, PgConnection};
45

56
#[tokio::test]
67
#[serial]
@@ -25,23 +26,6 @@ async fn test_auth() {
2526
assert!(PgConnection::connect(bad_password).await.is_err());
2627
}
2728

28-
async fn assert_setting_str(name: &str, expected: &str) {
29-
let admin = admin_sqlx().await;
30-
let rows = admin.fetch_all("SHOW CONFIG").await.unwrap();
31-
let mut found = false;
32-
for row in rows {
33-
let db_name: String = row.get(0);
34-
let value: String = row.get(1);
35-
36-
if name == db_name {
37-
found = true;
38-
assert_eq!(value, expected);
39-
}
40-
}
41-
42-
assert!(found);
43-
}
44-
4529
#[tokio::test]
4630
async fn test_passthrough_auth() {
4731
let admin = admin_sqlx().await;

integration/rust/tests/integration/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ pub mod shard_consistency;
1717
pub mod stddev;
1818
pub mod syntax_error;
1919
pub mod timestamp_sorting;
20+
pub mod tls_enforced;
2021
pub mod tls_reload;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use rust::setup::admin_sqlx;
2+
use rust::utils::assert_setting_str;
3+
use sqlx::{ConnectOptions, Connection, Executor, postgres::PgSslMode};
4+
5+
#[tokio::test]
6+
async fn test_tls_enforced() {
7+
let admin = admin_sqlx().await;
8+
let conn_base: sqlx::postgres::PgConnectOptions =
9+
"postgres://pgdog:pgdog@127.0.0.1:6432/pgdog?application_name=sqlx"
10+
.parse()
11+
.unwrap();
12+
13+
let opts_notls = conn_base.clone().ssl_mode(PgSslMode::Disable);
14+
let opts_tls = conn_base.clone().ssl_mode(PgSslMode::Require);
15+
16+
{
17+
let tls = opts_tls.connect().await.unwrap();
18+
let notls = opts_notls.connect().await.unwrap();
19+
tls.close().await.unwrap();
20+
notls.close().await.unwrap();
21+
}
22+
23+
admin
24+
.execute("SET tls_client_required TO 'true'")
25+
.await
26+
.unwrap();
27+
assert_setting_str("tls_client_required", "true").await;
28+
29+
opts_tls.connect().await.unwrap();
30+
assert!(opts_notls.connect().await.is_err());
31+
}

pgdog/src/admin/set.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ impl Command for Set {
141141
config.config.general.ban_timeout = self.value.parse()?;
142142
}
143143

144+
"tls_client_required" => {
145+
config.config.general.tls_client_required = Self::from_json(&self.value)?;
146+
}
147+
144148
_ => return Err(Error::Syntax),
145149
}
146150

pgdog/src/config/core.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::fs::read_to_string;
44
use std::path::PathBuf;
55
use tracing::{info, warn};
66

7-
use crate::config::PreparedStatements;
7+
use crate::config::{PassthoughAuth, PreparedStatements};
88

99
use super::database::Database;
1010
use super::error::Error;
@@ -286,6 +286,17 @@ impl Config {
286286
self.general.idle_healthcheck_interval, self.general.ban_timeout
287287
);
288288
}
289+
290+
// Warn about plain auth and TLS
291+
match self.general.passthrough_auth {
292+
PassthoughAuth::Enabled if !self.general.tls_client_required => {
293+
warn!("consider setting tls_client_required while passthrough_auth is enabled to prevent clients from exposing plaintext passwords");
294+
}
295+
PassthoughAuth::EnabledPlain => {
296+
warn!("passthrough_auth plain is enabled - network traffic may expose plaintext passwords")
297+
}
298+
_ => (),
299+
}
289300
}
290301

291302
/// Multi-tenancy is enabled.

pgdog/src/config/general.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ pub struct General {
6363
pub tls_certificate: Option<PathBuf>,
6464
/// TLS private key.
6565
pub tls_private_key: Option<PathBuf>,
66+
#[serde(default)]
67+
pub tls_client_required: bool,
6668
/// TLS verification mode (for connecting to servers)
6769
#[serde(default = "General::default_tls_verify")]
6870
pub tls_verify: TlsVerifyMode,
@@ -182,6 +184,7 @@ impl Default for General {
182184
read_write_split: Self::read_write_split(),
183185
tls_certificate: Self::tls_certificate(),
184186
tls_private_key: Self::tls_private_key(),
187+
tls_client_required: bool::default(),
185188
tls_verify: Self::default_tls_verify(),
186189
tls_server_ca_certificate: Self::tls_server_ca_certificate(),
187190
shutdown_timeout: Self::default_shutdown_timeout(),

pgdog/src/frontend/client/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ use crate::net::messages::{
2020
Authentication, BackendKeyData, ErrorResponse, FromBytes, Message, Password, Protocol,
2121
ReadyForQuery, ToBytes,
2222
};
23-
use crate::net::{parameter::Parameters, Stream};
24-
use crate::net::{MessageBuffer, ProtocolMessage};
23+
use crate::net::{parameter::Parameters, MessageBuffer, ProtocolMessage, Stream};
2524
use crate::state::State;
2625
use crate::stats::memory::MemoryUsage;
2726
use crate::util::user_database_from_params;
@@ -135,9 +134,15 @@ impl Client {
135134
addr: SocketAddr,
136135
mut comms: Comms,
137136
) -> Result<Option<Client>, Error> {
138-
let (user, database) = user_database_from_params(&params);
139137
let config = config::config();
140138

139+
// Bail immediately if TLS is required but the connection isn't using it.
140+
if config.config.general.tls_client_required && !stream.is_tls() {
141+
stream.fatal(ErrorResponse::tls_required()).await?;
142+
return Ok(None);
143+
}
144+
145+
let (user, database) = user_database_from_params(&params);
141146
let admin = database == config.config.admin.name && config.config.admin.user == user;
142147
let admin_password = &config.config.admin.password;
143148
let auth_type = &config.config.general.auth_type;

0 commit comments

Comments
 (0)