Line data Source code
1 : #[cfg(test)]
2 : mod tests;
3 :
4 : pub mod connect_compute;
5 : mod copy_bidirectional;
6 : pub mod handshake;
7 : pub mod passthrough;
8 : pub mod retry;
9 : pub mod wake_compute;
10 :
11 : use crate::{
12 : auth,
13 : cancellation::{self, CancellationHandlerMain, CancellationHandlerMainInternal},
14 : compute,
15 : config::{ProxyConfig, TlsConfig},
16 : context::RequestMonitoring,
17 : error::ReportableError,
18 : metrics::{NUM_CLIENT_CONNECTION_GAUGE, NUM_CONNECTION_REQUESTS_GAUGE},
19 : protocol2::WithClientIp,
20 : proxy::handshake::{handshake, HandshakeData},
21 : rate_limiter::EndpointRateLimiter,
22 : stream::{PqStream, Stream},
23 : EndpointCacheKey,
24 : };
25 : use futures::TryFutureExt;
26 : use itertools::Itertools;
27 : use metrics::IntCounterPairGuard;
28 : use once_cell::sync::OnceCell;
29 : use pq_proto::{BeMessage as Be, StartupMessageParams};
30 : use regex::Regex;
31 : use smol_str::{format_smolstr, SmolStr};
32 : use std::sync::Arc;
33 : use thiserror::Error;
34 : use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
35 : use tokio_util::sync::CancellationToken;
36 : use tracing::{error, info, Instrument};
37 :
38 : use self::{
39 : connect_compute::{connect_to_compute, TcpMechanism},
40 : passthrough::ProxyPassthrough,
41 : };
42 :
43 : const ERR_INSECURE_CONNECTION: &str = "connection is insecure (try using `sslmode=require`)";
44 :
45 0 : pub async fn run_until_cancelled<F: std::future::Future>(
46 0 : f: F,
47 0 : cancellation_token: &CancellationToken,
48 0 : ) -> Option<F::Output> {
49 0 : match futures::future::select(
50 0 : std::pin::pin!(f),
51 0 : std::pin::pin!(cancellation_token.cancelled()),
52 0 : )
53 0 : .await
54 : {
55 0 : futures::future::Either::Left((f, _)) => Some(f),
56 0 : futures::future::Either::Right(((), _)) => None,
57 : }
58 0 : }
59 :
60 0 : pub async fn task_main(
61 0 : config: &'static ProxyConfig,
62 0 : listener: tokio::net::TcpListener,
63 0 : cancellation_token: CancellationToken,
64 0 : endpoint_rate_limiter: Arc<EndpointRateLimiter>,
65 0 : cancellation_handler: Arc<CancellationHandlerMain>,
66 0 : ) -> anyhow::Result<()> {
67 0 : scopeguard::defer! {
68 0 : info!("proxy has shut down");
69 : }
70 :
71 : // When set for the server socket, the keepalive setting
72 : // will be inherited by all accepted client sockets.
73 0 : socket2::SockRef::from(&listener).set_keepalive(true)?;
74 :
75 0 : let connections = tokio_util::task::task_tracker::TaskTracker::new();
76 :
77 0 : while let Some(accept_result) =
78 0 : run_until_cancelled(listener.accept(), &cancellation_token).await
79 : {
80 0 : let (socket, peer_addr) = accept_result?;
81 :
82 0 : let conn_gauge = NUM_CLIENT_CONNECTION_GAUGE
83 0 : .with_label_values(&["tcp"])
84 0 : .guard();
85 0 :
86 0 : let session_id = uuid::Uuid::new_v4();
87 0 : let cancellation_handler = Arc::clone(&cancellation_handler);
88 0 : let endpoint_rate_limiter = endpoint_rate_limiter.clone();
89 0 :
90 0 : tracing::info!(protocol = "tcp", %session_id, "accepted new TCP connection");
91 :
92 0 : connections.spawn(async move {
93 0 : let mut socket = WithClientIp::new(socket);
94 0 : let mut peer_addr = peer_addr.ip();
95 0 : match socket.wait_for_addr().await {
96 0 : Ok(Some(addr)) => peer_addr = addr.ip(),
97 0 : Err(e) => {
98 0 : error!("per-client task finished with an error: {e:#}");
99 0 : return;
100 : }
101 0 : Ok(None) if config.require_client_ip => {
102 0 : error!("missing required client IP");
103 0 : return;
104 : }
105 0 : Ok(None) => {}
106 : }
107 :
108 0 : match socket.inner.set_nodelay(true) {
109 0 : Ok(()) => {},
110 0 : Err(e) => {
111 0 : error!("per-client task finished with an error: failed to set socket option: {e:#}");
112 0 : return;
113 : },
114 : };
115 :
116 0 : let mut ctx = RequestMonitoring::new(session_id, peer_addr, "tcp", &config.region);
117 0 : let span = ctx.span.clone();
118 :
119 0 : let res = handle_client(
120 0 : config,
121 0 : &mut ctx,
122 0 : cancellation_handler,
123 0 : socket,
124 0 : ClientMode::Tcp,
125 0 : endpoint_rate_limiter,
126 0 : conn_gauge,
127 0 : )
128 0 : .instrument(span.clone())
129 0 : .await;
130 :
131 0 : match res {
132 0 : Err(e) => {
133 0 : // todo: log and push to ctx the error kind
134 0 : ctx.set_error_kind(e.get_error_kind());
135 0 : ctx.log();
136 0 : error!(parent: &span, "per-client task finished with an error: {e:#}");
137 : }
138 0 : Ok(None) => {
139 0 : ctx.set_success();
140 0 : ctx.log();
141 0 : }
142 0 : Ok(Some(p)) => {
143 0 : ctx.set_success();
144 0 : ctx.log();
145 0 : match p.proxy_pass().instrument(span.clone()).await {
146 0 : Ok(()) => {}
147 0 : Err(e) => {
148 0 : error!(parent: &span, "per-client task finished with an error: {e:#}");
149 0 : }
150 : }
151 : }
152 : }
153 0 : });
154 : }
155 :
156 0 : connections.close();
157 0 : drop(listener);
158 0 :
159 0 : // Drain connections
160 0 : connections.wait().await;
161 :
162 0 : Ok(())
163 0 : }
164 :
165 : pub enum ClientMode {
166 : Tcp,
167 : Websockets { hostname: Option<String> },
168 : }
169 :
170 : /// Abstracts the logic of handling TCP vs WS clients
171 : impl ClientMode {
172 0 : pub fn allow_cleartext(&self) -> bool {
173 0 : match self {
174 0 : ClientMode::Tcp => false,
175 0 : ClientMode::Websockets { .. } => true,
176 : }
177 0 : }
178 :
179 0 : pub fn allow_self_signed_compute(&self, config: &ProxyConfig) -> bool {
180 0 : match self {
181 0 : ClientMode::Tcp => config.allow_self_signed_compute,
182 0 : ClientMode::Websockets { .. } => false,
183 : }
184 0 : }
185 :
186 0 : fn hostname<'a, S>(&'a self, s: &'a Stream<S>) -> Option<&'a str> {
187 0 : match self {
188 0 : ClientMode::Tcp => s.sni_hostname(),
189 0 : ClientMode::Websockets { hostname } => hostname.as_deref(),
190 : }
191 0 : }
192 :
193 0 : fn handshake_tls<'a>(&self, tls: Option<&'a TlsConfig>) -> Option<&'a TlsConfig> {
194 0 : match self {
195 0 : ClientMode::Tcp => tls,
196 : // TLS is None here if using websockets, because the connection is already encrypted.
197 0 : ClientMode::Websockets { .. } => None,
198 : }
199 0 : }
200 : }
201 :
202 0 : #[derive(Debug, Error)]
203 : // almost all errors should be reported to the user, but there's a few cases where we cannot
204 : // 1. Cancellation: we are not allowed to tell the client any cancellation statuses for security reasons
205 : // 2. Handshake: handshake reports errors if it can, otherwise if the handshake fails due to protocol violation,
206 : // we cannot be sure the client even understands our error message
207 : // 3. PrepareClient: The client disconnected, so we can't tell them anyway...
208 : pub enum ClientRequestError {
209 : #[error("{0}")]
210 : Cancellation(#[from] cancellation::CancelError),
211 : #[error("{0}")]
212 : Handshake(#[from] handshake::HandshakeError),
213 : #[error("{0}")]
214 : HandshakeTimeout(#[from] tokio::time::error::Elapsed),
215 : #[error("{0}")]
216 : PrepareClient(#[from] std::io::Error),
217 : #[error("{0}")]
218 : ReportedError(#[from] crate::stream::ReportedError),
219 : }
220 :
221 : impl ReportableError for ClientRequestError {
222 0 : fn get_error_kind(&self) -> crate::error::ErrorKind {
223 0 : match self {
224 0 : ClientRequestError::Cancellation(e) => e.get_error_kind(),
225 0 : ClientRequestError::Handshake(e) => e.get_error_kind(),
226 0 : ClientRequestError::HandshakeTimeout(_) => crate::error::ErrorKind::RateLimit,
227 0 : ClientRequestError::ReportedError(e) => e.get_error_kind(),
228 0 : ClientRequestError::PrepareClient(_) => crate::error::ErrorKind::ClientDisconnect,
229 : }
230 0 : }
231 : }
232 :
233 0 : pub async fn handle_client<S: AsyncRead + AsyncWrite + Unpin>(
234 0 : config: &'static ProxyConfig,
235 0 : ctx: &mut RequestMonitoring,
236 0 : cancellation_handler: Arc<CancellationHandlerMain>,
237 0 : stream: S,
238 0 : mode: ClientMode,
239 0 : endpoint_rate_limiter: Arc<EndpointRateLimiter>,
240 0 : conn_gauge: IntCounterPairGuard,
241 0 : ) -> Result<Option<ProxyPassthrough<CancellationHandlerMainInternal, S>>, ClientRequestError> {
242 0 : info!("handling interactive connection from client");
243 :
244 0 : let proto = ctx.protocol;
245 0 : let _request_gauge = NUM_CONNECTION_REQUESTS_GAUGE
246 0 : .with_label_values(&[proto])
247 0 : .guard();
248 0 :
249 0 : let tls = config.tls_config.as_ref();
250 0 :
251 0 : let pause = ctx.latency_timer.pause(crate::metrics::Waiting::Client);
252 0 : let do_handshake = handshake(stream, mode.handshake_tls(tls));
253 0 : let (mut stream, params) =
254 0 : match tokio::time::timeout(config.handshake_timeout, do_handshake).await?? {
255 0 : HandshakeData::Startup(stream, params) => (stream, params),
256 0 : HandshakeData::Cancel(cancel_key_data) => {
257 0 : return Ok(cancellation_handler
258 0 : .cancel_session(cancel_key_data, ctx.session_id)
259 0 : .await
260 0 : .map(|()| None)?)
261 : }
262 : };
263 0 : drop(pause);
264 0 :
265 0 : let hostname = mode.hostname(stream.get_ref());
266 0 :
267 0 : let common_names = tls.map(|tls| &tls.common_names);
268 0 :
269 0 : // Extract credentials which we're going to use for auth.
270 0 : let result = config
271 0 : .auth_backend
272 0 : .as_ref()
273 0 : .map(|_| auth::ComputeUserInfoMaybeEndpoint::parse(ctx, ¶ms, hostname, common_names))
274 0 : .transpose();
275 :
276 0 : let user_info = match result {
277 0 : Ok(user_info) => user_info,
278 0 : Err(e) => stream.throw_error(e).await?,
279 : };
280 :
281 : // check rate limit
282 0 : if let Some(ep) = user_info.get_endpoint() {
283 0 : if !endpoint_rate_limiter.check(ep, 1) {
284 0 : return stream
285 0 : .throw_error(auth::AuthError::too_many_connections())
286 0 : .await?;
287 0 : }
288 0 : }
289 :
290 0 : let user = user_info.get_user().to_owned();
291 0 : let user_info = match user_info
292 0 : .authenticate(
293 0 : ctx,
294 0 : &mut stream,
295 0 : mode.allow_cleartext(),
296 0 : &config.authentication_config,
297 0 : )
298 0 : .await
299 : {
300 0 : Ok(auth_result) => auth_result,
301 0 : Err(e) => {
302 0 : let db = params.get("database");
303 0 : let app = params.get("application_name");
304 0 : let params_span = tracing::info_span!("", ?user, ?db, ?app);
305 :
306 0 : return stream.throw_error(e).instrument(params_span).await?;
307 : }
308 : };
309 :
310 0 : let mut node = connect_to_compute(
311 0 : ctx,
312 0 : &TcpMechanism { params: ¶ms },
313 0 : &user_info,
314 0 : mode.allow_self_signed_compute(config),
315 0 : )
316 0 : .or_else(|e| stream.throw_error(e))
317 0 : .await?;
318 :
319 0 : let session = cancellation_handler.get_session();
320 0 : prepare_client_connection(&node, &session, &mut stream).await?;
321 :
322 : // Before proxy passing, forward to compute whatever data is left in the
323 : // PqStream input buffer. Normally there is none, but our serverless npm
324 : // driver in pipeline mode sends startup, password and first query
325 : // immediately after opening the connection.
326 0 : let (stream, read_buf) = stream.into_inner();
327 0 : node.stream.write_all(&read_buf).await?;
328 :
329 0 : Ok(Some(ProxyPassthrough {
330 0 : client: stream,
331 0 : aux: node.aux.clone(),
332 0 : compute: node,
333 0 : req: _request_gauge,
334 0 : conn: conn_gauge,
335 0 : cancel: session,
336 0 : }))
337 0 : }
338 :
339 : /// Finish client connection initialization: confirm auth success, send params, etc.
340 0 : #[tracing::instrument(skip_all)]
341 : async fn prepare_client_connection<P>(
342 : node: &compute::PostgresConnection,
343 : session: &cancellation::Session<P>,
344 : stream: &mut PqStream<impl AsyncRead + AsyncWrite + Unpin>,
345 : ) -> Result<(), std::io::Error> {
346 : // Register compute's query cancellation token and produce a new, unique one.
347 : // The new token (cancel_key_data) will be sent to the client.
348 : let cancel_key_data = session.enable_query_cancellation(node.cancel_closure.clone());
349 :
350 : // Forward all postgres connection params to the client.
351 : // Right now the implementation is very hacky and inefficent (ideally,
352 : // we don't need an intermediate hashmap), but at least it should be correct.
353 : for (name, value) in &node.params {
354 : // TODO: Theoretically, this could result in a big pile of params...
355 : stream.write_message_noflush(&Be::ParameterStatus {
356 : name: name.as_bytes(),
357 : value: value.as_bytes(),
358 : })?;
359 : }
360 :
361 : stream
362 : .write_message_noflush(&Be::BackendKeyData(cancel_key_data))?
363 : .write_message(&Be::ReadyForQuery)
364 : .await?;
365 :
366 : Ok(())
367 : }
368 :
369 : #[derive(Debug, Clone, PartialEq, Eq, Default)]
370 : pub struct NeonOptions(Vec<(SmolStr, SmolStr)>);
371 :
372 : impl NeonOptions {
373 22 : pub fn parse_params(params: &StartupMessageParams) -> Self {
374 22 : params
375 22 : .options_raw()
376 22 : .map(Self::parse_from_iter)
377 22 : .unwrap_or_default()
378 22 : }
379 14 : pub fn parse_options_raw(options: &str) -> Self {
380 14 : Self::parse_from_iter(StartupMessageParams::parse_options_raw(options))
381 14 : }
382 :
383 4 : pub fn is_ephemeral(&self) -> bool {
384 4 : // Currently, neon endpoint options are all reserved for ephemeral endpoints.
385 4 : !self.0.is_empty()
386 4 : }
387 :
388 26 : fn parse_from_iter<'a>(options: impl Iterator<Item = &'a str>) -> Self {
389 26 : let mut options = options
390 26 : .filter_map(neon_option)
391 26 : .map(|(k, v)| (k.into(), v.into()))
392 26 : .collect_vec();
393 26 : options.sort();
394 26 : Self(options)
395 26 : }
396 :
397 8 : pub fn get_cache_key(&self, prefix: &str) -> EndpointCacheKey {
398 8 : // prefix + format!(" {k}:{v}")
399 8 : // kinda jank because SmolStr is immutable
400 8 : std::iter::once(prefix)
401 8 : .chain(self.0.iter().flat_map(|(k, v)| [" ", &**k, ":", &**v]))
402 8 : .collect::<SmolStr>()
403 8 : .into()
404 8 : }
405 :
406 : /// <https://swagger.io/docs/specification/serialization/> DeepObject format
407 : /// `paramName[prop1]=value1¶mName[prop2]=value2&...`
408 0 : pub fn to_deep_object(&self) -> Vec<(SmolStr, SmolStr)> {
409 0 : self.0
410 0 : .iter()
411 0 : .map(|(k, v)| (format_smolstr!("options[{}]", k), v.clone()))
412 0 : .collect()
413 0 : }
414 : }
415 :
416 64 : pub fn neon_option(bytes: &str) -> Option<(&str, &str)> {
417 64 : static RE: OnceCell<Regex> = OnceCell::new();
418 64 : let re = RE.get_or_init(|| Regex::new(r"^neon_(\w+):(.+)").unwrap());
419 :
420 64 : let cap = re.captures(bytes)?;
421 8 : let (_, [k, v]) = cap.extract();
422 8 : Some((k, v))
423 64 : }
|