Skip to main content

tokio_postgres/proxy/
mod.rs

1#![allow(missing_docs)]
2
3mod auth;
4mod startup;
5
6use bytes::Bytes;
7use std::collections::HashMap;
8use std::future::Future;
9use std::sync::Arc;
10
11use futures_util::{try_join, SinkExt};
12use tokio::io::{copy_bidirectional, AsyncRead, AsyncWrite, AsyncWriteExt};
13use tokio::net::TcpStream;
14use tokio_util::codec::{Framed, FramedParts};
15
16use postgres_protocol::message::startup::{CancelData, StartupData, StartupResponse};
17
18use crate::config::SslNegotiation;
19use crate::connect_proxy::connect_proxy;
20use crate::proxy::startup::{read_frontend_startup, StartupCodec, StartupInfo};
21use crate::tls::{MakeTlsConnect, TlsConnect};
22use crate::{CancelToken, Config, Error, Socket};
23
24/// A trait for determining if, and where, to route an incoming client connection.
25pub trait ClientBouncer: Clone + Sync + Send + 'static {
26    type Tls: MakeTlsConnect<Socket> + Send + Clone + 'static;
27    type Future: Future<Output = Result<AcceptConn<Self::Tls>, RejectConn>> + Send + 'static;
28
29    /// Handles a startup message from a client.
30    /// Returns a `BackendConfig` if the connection should be proxied to a backend,
31    /// or an error if the connection should be rejected.
32    fn handle_startup(&self, info: &StartupData) -> Self::Future;
33}
34
35/// RejectConn contains reasons for rejecting an incoming connection.
36pub enum RejectConn {
37    UnknownDatabase,
38    UnknownUser,
39    InternalError,
40}
41
42impl RejectConn {
43    /// The SQLSTATE reported to the client for this rejection.
44    pub fn sqlstate(&self) -> &'static str {
45        match self {
46            RejectConn::UnknownDatabase => "3D000", // invalid_catalog_name
47            RejectConn::UnknownUser => "28000",     // invalid_authorization_specification
48            RejectConn::InternalError => "XX000",   // internal_error
49        }
50    }
51}
52
53impl std::fmt::Display for RejectConn {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        match self {
56            RejectConn::UnknownDatabase => write!(f, "unknown database"),
57            RejectConn::UnknownUser => write!(f, "unknown user"),
58            RejectConn::InternalError => write!(f, "internal error"),
59        }
60    }
61}
62
63/// AcceptConn specifies how a connection should be proxied to a backend.
64pub struct AcceptConn<T> {
65    /// How to authenticate the client.
66    pub auth_method: AuthMethod,
67
68    /// TLS configuration to use.
69    pub tls: T,
70
71    /// Backend configuration to use.
72    pub backend_config: Arc<Config>,
73}
74
75/// How to authenticate an incoming connection.
76pub enum AuthMethod {
77    // Trust the user implicitly without authentication.
78    Trust,
79
80    /// Authenticate the user with a password.
81    Password(String),
82}
83
84#[derive(Clone)]
85pub struct ProxyManager<B>
86where
87    B: ClientBouncer,
88{
89    bouncer: B,
90
91    /// The cancel handles for active connections, keyed by the process ID and secret key.
92    cancel_handles: Arc<tokio::sync::RwLock<HashMap<CancelKey, CancelHandle<B::Tls>>>>,
93}
94
95/// Handles proxying connections from clients to backends.
96impl<B> ProxyManager<B>
97where
98    B: ClientBouncer,
99    <B::Tls as MakeTlsConnect<Socket>>::TlsConnect: Send,
100    <B::Tls as MakeTlsConnect<Socket>>::Stream: Send,
101    <<B::Tls as MakeTlsConnect<Socket>>::TlsConnect as TlsConnect<Socket>>::Future: Send,
102{
103    pub fn new(bouncer: B) -> Self {
104        Self {
105            bouncer,
106            cancel_handles: Arc::default(),
107        }
108    }
109
110    /// Handles a TCP connection from a client.
111    pub async fn handle_conn(self, client_stream: TcpStream) {
112        let mut startup_stream = Framed::new(client_stream, StartupCodec::new());
113
114        // Phase 1: client startup
115        let Some((mut accept, startup_data)) = self.client_startup(&mut startup_stream).await
116        else {
117            return;
118        };
119
120        // Phase 2: backend startup
121        let client_params = client_params(&startup_data, &accept.backend_config);
122        let backend_info =
123            match connect_proxy(&mut accept.tls, &accept.backend_config, &client_params).await {
124                Ok(backend_info) => backend_info,
125                Err(err) => {
126                    _ = startup_stream
127                        .send(StartupResponse::ErrorResponse {
128                            code: "08006", // connection_failure
129                            message: format!("backend connection failed: {:?}", err),
130                        })
131                        .await;
132                    return;
133                }
134            };
135
136        // Register the cancel handle so cancellation requests can be handled,
137        // before the client is given the backend's key to send them with.
138        let cancel_registration = match backend_info.backend_key {
139            Some((process_id, secret_key)) => {
140                let reg = CancelHandleRegistration {
141                    key: CancelKey {
142                        process_id,
143                        secret_key,
144                    },
145                    lock: self.cancel_handles.clone(),
146                };
147                reg.register(CancelHandle {
148                    token: CancelToken {
149                        socket_config: Some(backend_info.socket_config),
150                        ssl_mode: accept.backend_config.ssl_mode,
151                        process_id,
152                        secret_key,
153                        ssl_negotiation: SslNegotiation::Postgres,
154                    },
155                    tls: accept.tls,
156                })
157                .await;
158                Some(reg)
159            }
160            None => None,
161        };
162
163        // Notify the client that authentication is successful.
164        if self
165            .complete_client_init(
166                &mut startup_stream,
167                &backend_info.parameters,
168                backend_info.backend_key,
169            )
170            .await
171            .is_err()
172        {
173            // Client is gone.
174            if let Some(reg) = cancel_registration {
175                reg.deregister().await;
176            }
177            return;
178        }
179
180        // Proxy data in both directions.
181        let proxy_result = {
182            let mut backend_parts = backend_info.backend.into_parts();
183            let mut client_parts = startup_stream.into_parts();
184            proxy_data(&mut client_parts, &mut backend_parts).await
185        };
186
187        // Remove the cancel registration.
188        if let Some(reg) = cancel_registration {
189            reg.deregister().await;
190        }
191
192        match proxy_result {
193            Ok(()) => log::debug!("proxy connection closed"),
194            Err(err) => log::error!("proxy connection error: {}", err),
195        }
196    }
197
198    /// Handles starting up a client connection, returning how to proxy it along
199    /// with the client's startup data.
200    /// It returns None if the connection should be closed, whether for authentication issues
201    /// or because the client requested cancellation.
202    async fn client_startup<S>(
203        &self,
204        startup_stream: &mut Framed<S, StartupCodec>,
205    ) -> Option<(AcceptConn<B::Tls>, StartupData)>
206    where
207        S: AsyncRead + AsyncWrite + Unpin,
208    {
209        // Read the startup message.
210        match read_frontend_startup(startup_stream).await.ok()? {
211            StartupInfo::Cancel(cancel) => {
212                self.handle_cancel(cancel).await;
213                // From https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-CANCELING-REQUESTS:
214                // "For security reasons, no direct reply is made to the cancel request message"
215                None
216            }
217
218            StartupInfo::Startup(raw) => {
219                // Determine where to route the connection.
220                let startup_data = StartupData::parse(raw).ok()?;
221                match self.bouncer.handle_startup(&startup_data).await {
222                    Ok(accept) => {
223                        // Authenticate the user.
224                        match accept
225                            .auth_method
226                            .authenticate(startup_stream, &startup_data)
227                            .await
228                        {
229                            Ok(()) => {
230                                // Successfully authenticated.
231                                Some((accept, startup_data))
232                            }
233                            Err(err) => {
234                                // Failed to authenticate.
235                                log::error!("authentication failed: {}", err);
236
237                                // Ignore error from sending to client; we already have an error to return.
238                                _ = startup_stream
239                                    .send(StartupResponse::ErrorResponse {
240                                        code: "28P01", // invalid_password
241                                        message: "authentication failed".to_string(),
242                                    })
243                                    .await;
244                                None
245                            }
246                        }
247                    }
248                    Err(reject) => {
249                        log::error!("connection rejected: {}", reject);
250                        // Ignore error from sending to client; we already have an error to return.
251                        _ = startup_stream
252                            .send(StartupResponse::ErrorResponse {
253                                code: reject.sqlstate(),
254                                message: format!("connection rejected: {}", reject),
255                            })
256                            .await;
257                        None
258                    }
259                }
260            }
261        }
262    }
263
264    async fn complete_client_init<S>(
265        &self,
266        startup_stream: &mut Framed<S, StartupCodec>,
267        parameters: &HashMap<String, String>,
268        backend_key: Option<(i32, i32)>,
269    ) -> Result<(), Error>
270    where
271        S: AsyncRead + AsyncWrite + Unpin,
272    {
273        // Notify the client the authentication is successful.
274        startup_stream
275            .feed(StartupResponse::AuthenticationOk)
276            .await
277            .map_err(Error::io)?;
278
279        // Send backend parameters, sorted by key.
280        let mut parameters = parameters
281            .iter()
282            .map(|(k, v)| (k.clone(), v.clone()))
283            .collect::<Vec<_>>();
284
285        parameters.sort_by(|a, b| a.0.cmp(&b.0));
286        for (key, value) in parameters {
287            let msg = StartupResponse::ParameterStatus {
288                key: key.clone(),
289                value: Bytes::from(value),
290            };
291            startup_stream.feed(msg).await.map_err(Error::io)?;
292        }
293
294        // Send the backend's cancellation key, which the client's cancellation
295        // requests to this proxy are looked up by.
296        if let Some((process_id, secret_key)) = backend_key {
297            startup_stream
298                .feed(StartupResponse::BackendKeyData {
299                    process_id,
300                    secret_key,
301                })
302                .await
303                .map_err(Error::io)?;
304        }
305
306        // Send ReadyForQuery
307        startup_stream
308            .feed(StartupResponse::ReadyForQuery)
309            .await
310            .map_err(Error::io)?;
311
312        // Flush the stream.
313        startup_stream.flush().await.map_err(Error::io)?;
314
315        Ok(())
316    }
317
318    /// Handles a cancellation request from a client.
319    async fn handle_cancel(&self, cancel: CancelData) {
320        let key = CancelKey {
321            process_id: cancel.process_id,
322            secret_key: cancel.secret_key,
323        };
324
325        // Copied out, so the lock is not held while the backend is dialed: a
326        // slow backend would otherwise hold up every connection registering.
327        let handle = self
328            .cancel_handles
329            .read()
330            .await
331            .get(&key)
332            .map(|handle| (handle.token.clone(), handle.tls.clone()));
333        if let Some((token, tls)) = handle {
334            _ = token.cancel_query(tls).await;
335        }
336    }
337}
338
339/// The startup parameters a client passes on to its backend: all it sent but
340/// `user` and `database`, which the backend config decides, and protocol
341/// options (`_pq_.*`), which the proxy does not negotiate. The client's
342/// `options` follow the config's, so they add to them, and take precedence
343/// over them where both set the same thing.
344fn client_params(startup: &StartupData, config: &Config) -> Vec<(String, String)> {
345    let mut params = Vec::new();
346    for (key, value) in &startup.parameters {
347        if matches!(key.as_str(), "user" | "database") || key.starts_with("_pq_.") {
348            continue;
349        }
350        let Ok(value) = std::str::from_utf8(value) else {
351            log::warn!("dropping startup parameter {key}: its value is not UTF-8");
352            continue;
353        };
354        let value = match (key.as_str(), config.get_options()) {
355            ("options", Some(options)) => format!("{options} {value}"),
356            _ => value.to_string(),
357        };
358        params.push((key.clone(), value));
359    }
360    params.sort();
361    params
362}
363
364async fn proxy_data<C, CC, S, SC>(
365    client: &mut FramedParts<C, CC>,
366    server: &mut FramedParts<S, SC>,
367) -> Result<(), Error>
368where
369    C: AsyncRead + AsyncWrite + Unpin,
370    S: AsyncRead + AsyncWrite + Unpin,
371{
372    // Write all pending data
373    write_pending(client, server).await?;
374
375    // Copy data in both directions until EOF is reached.
376    copy_bidirectional(&mut client.io, &mut server.io)
377        .await
378        .map_err(Error::io)?;
379
380    Ok(())
381}
382
383async fn write_pending<C, CC, S, SC>(
384    client: &mut FramedParts<C, CC>,
385    server: &mut FramedParts<S, SC>,
386) -> Result<(), Error>
387where
388    C: AsyncRead + AsyncWrite + Unpin,
389    S: AsyncRead + AsyncWrite + Unpin,
390{
391    // Write unwritten data.
392    let a = server.io.write_all(&server.write_buf);
393    let b = client.io.write_all(&client.write_buf);
394    try_join!(a, b).map_err(Error::io)?;
395
396    let c = client.io.write_all(&server.read_buf);
397    let d = server.io.write_all(&client.read_buf);
398    try_join!(c, d).map_err(Error::io)?;
399    Ok(())
400}
401
402struct CancelHandle<T> {
403    token: CancelToken,
404    tls: T,
405}
406
407/// The key used to identify a cancellation token.
408#[derive(Clone, Hash, PartialEq, Eq)]
409struct CancelKey {
410    process_id: i32,
411    secret_key: i32,
412}
413
414struct CancelHandleRegistration<T> {
415    key: CancelKey,
416    lock: Arc<tokio::sync::RwLock<HashMap<CancelKey, CancelHandle<T>>>>,
417}
418
419impl<T> CancelHandleRegistration<T> {
420    pub async fn register(&self, handle: CancelHandle<T>) {
421        self.lock.write().await.insert(self.key.clone(), handle);
422    }
423
424    pub async fn deregister(&self) {
425        self.lock.write().await.remove(&self.key);
426    }
427}