From fe1513ca5774473214cadf7f82b36ed23d79c734 Mon Sep 17 00:00:00 2001 From: Tristan Partin Date: Mon, 26 May 2025 21:21:24 -0500 Subject: [PATCH] Add neon.safekeeper_conninfo_options GUC (#11901) In order to enable TLS connections between computes and safekeepers, we need to provide the control plane with a way to configure the various libpq keyword parameters, sslmode and sslrootcert. neon.safekeepers is a comma separated list of safekeepers formatted as host:port, so isn't available for extension in the same way that neon.pageserver_connstring is. This could be remedied in a future PR. Part-of: https://github.com/neondatabase/cloud/issues/25823 Link: https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS Signed-off-by: Tristan Partin --- libs/walproposer/src/walproposer.rs | 8 ++++++++ pgxn/neon/walproposer.c | 5 +++-- pgxn/neon/walproposer.h | 3 +++ pgxn/neon/walproposer_pg.c | 12 ++++++++++++ safekeeper/tests/walproposer_sim/simulation.rs | 1 + 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/libs/walproposer/src/walproposer.rs b/libs/walproposer/src/walproposer.rs index 4e50c21fca..e95494297c 100644 --- a/libs/walproposer/src/walproposer.rs +++ b/libs/walproposer/src/walproposer.rs @@ -1,6 +1,7 @@ #![allow(clippy::todo)] use std::ffi::CString; +use std::str::FromStr; use postgres_ffi::WAL_SEGMENT_SIZE; use utils::id::TenantTimelineId; @@ -173,6 +174,8 @@ pub struct Config { pub ttid: TenantTimelineId, /// List of safekeepers in format `host:port` pub safekeepers_list: Vec, + /// libpq connection info options + pub safekeeper_conninfo_options: String, /// Safekeeper reconnect timeout in milliseconds pub safekeeper_reconnect_timeout: i32, /// Safekeeper connection timeout in milliseconds @@ -202,6 +205,9 @@ impl Wrapper { .into_bytes_with_nul(); assert!(safekeepers_list_vec.len() == safekeepers_list_vec.capacity()); let safekeepers_list = safekeepers_list_vec.as_mut_ptr() as *mut std::ffi::c_char; + let safekeeper_conninfo_options = CString::from_str(&config.safekeeper_conninfo_options) + .unwrap() + .into_raw(); let callback_data = Box::into_raw(Box::new(api)) as *mut ::std::os::raw::c_void; @@ -209,6 +215,7 @@ impl Wrapper { neon_tenant, neon_timeline, safekeepers_list, + safekeeper_conninfo_options, safekeeper_reconnect_timeout: config.safekeeper_reconnect_timeout, safekeeper_connection_timeout: config.safekeeper_connection_timeout, wal_segment_size: WAL_SEGMENT_SIZE as i32, // default 16MB @@ -576,6 +583,7 @@ mod tests { let config = crate::walproposer::Config { ttid, safekeepers_list: vec!["localhost:5000".to_string()], + safekeeper_conninfo_options: String::new(), safekeeper_reconnect_timeout: 1000, safekeeper_connection_timeout: 10000, sync_safekeepers: true, diff --git a/pgxn/neon/walproposer.c b/pgxn/neon/walproposer.c index 3befb42030..f42103c7cd 100644 --- a/pgxn/neon/walproposer.c +++ b/pgxn/neon/walproposer.c @@ -155,8 +155,9 @@ WalProposerCreate(WalProposerConfig *config, walproposer_api api) int written = 0; written = snprintf((char *) &sk->conninfo, MAXCONNINFO, - "host=%s port=%s dbname=replication options='-c timeline_id=%s tenant_id=%s'", - sk->host, sk->port, wp->config->neon_timeline, wp->config->neon_tenant); + "%s host=%s port=%s dbname=replication options='-c timeline_id=%s tenant_id=%s'", + wp->config->safekeeper_conninfo_options, sk->host, sk->port, + wp->config->neon_timeline, wp->config->neon_tenant); if (written > MAXCONNINFO || written < 0) wp_log(FATAL, "could not create connection string for safekeeper %s:%s", sk->host, sk->port); } diff --git a/pgxn/neon/walproposer.h b/pgxn/neon/walproposer.h index 83ef72d3d7..cca20e746b 100644 --- a/pgxn/neon/walproposer.h +++ b/pgxn/neon/walproposer.h @@ -714,6 +714,9 @@ typedef struct WalProposerConfig */ char *safekeepers_list; + /* libpq connection info options. */ + char *safekeeper_conninfo_options; + /* * WalProposer reconnects to offline safekeepers once in this interval. * Time is in milliseconds. diff --git a/pgxn/neon/walproposer_pg.c b/pgxn/neon/walproposer_pg.c index 17582405db..d15bf91d24 100644 --- a/pgxn/neon/walproposer_pg.c +++ b/pgxn/neon/walproposer_pg.c @@ -64,6 +64,7 @@ char *wal_acceptors_list = ""; int wal_acceptor_reconnect_timeout = 1000; int wal_acceptor_connection_timeout = 10000; int safekeeper_proto_version = 3; +char *safekeeper_conninfo_options = ""; /* Set to true in the walproposer bgw. */ static bool am_walproposer; @@ -119,6 +120,7 @@ init_walprop_config(bool syncSafekeepers) walprop_config.neon_timeline = neon_timeline; /* WalProposerCreate scribbles directly on it, so pstrdup */ walprop_config.safekeepers_list = pstrdup(wal_acceptors_list); + walprop_config.safekeeper_conninfo_options = pstrdup(safekeeper_conninfo_options); walprop_config.safekeeper_reconnect_timeout = wal_acceptor_reconnect_timeout; walprop_config.safekeeper_connection_timeout = wal_acceptor_connection_timeout; walprop_config.wal_segment_size = wal_segment_size; @@ -203,6 +205,16 @@ nwp_register_gucs(void) * GUC_LIST_QUOTE */ NULL, assign_neon_safekeepers, NULL); + DefineCustomStringVariable( + "neon.safekeeper_conninfo_options", + "libpq keyword parameters and values to apply to safekeeper connections", + NULL, + &safekeeper_conninfo_options, + "", + PGC_POSTMASTER, + 0, + NULL, NULL, NULL); + DefineCustomIntVariable( "neon.safekeeper_reconnect_timeout", "Walproposer reconnects to offline safekeepers once in this interval.", diff --git a/safekeeper/tests/walproposer_sim/simulation.rs b/safekeeper/tests/walproposer_sim/simulation.rs index f314143952..70fecfbe22 100644 --- a/safekeeper/tests/walproposer_sim/simulation.rs +++ b/safekeeper/tests/walproposer_sim/simulation.rs @@ -87,6 +87,7 @@ impl WalProposer { let config = Config { ttid, safekeepers_list: addrs, + safekeeper_conninfo_options: String::new(), safekeeper_reconnect_timeout: 1000, safekeeper_connection_timeout: 5000, sync_safekeepers,