diff --git a/proxy/src/auth/backend.rs b/proxy/src/auth/backend.rs index 25e288ad2e..42b2304bb8 100644 --- a/proxy/src/auth/backend.rs +++ b/proxy/src/auth/backend.rs @@ -1,10 +1,11 @@ mod classic; +mod hacks; mod link; pub use link::LinkAuthError; use crate::{ - auth::{self, AuthFlow, ClientCredentials}, + auth::{self, ClientCredentials}, console::{ self, provider::{CachedNodeInfo, ConsoleReqExtra}, @@ -15,7 +16,7 @@ use crate::{ use futures::TryFutureExt; use std::borrow::Cow; use tokio::io::{AsyncRead, AsyncWrite}; -use tracing::{info, warn}; +use tracing::info; /// A product of successful authentication. pub struct AuthSuccess { @@ -105,59 +106,6 @@ impl<'a, T, E> BackendType<'a, Result> { } } -/// Compared to [SCRAM](crate::scram), cleartext password auth saves -/// one round trip and *expensive* computations (>= 4096 HMAC iterations). -/// These properties are benefical for serverless JS workers, so we -/// use this mechanism for websocket connections. -async fn do_cleartext_hack( - api: &impl console::Api, - extra: &ConsoleReqExtra<'_>, - creds: &mut ClientCredentials<'_>, - client: &mut stream::PqStream, -) -> auth::Result> { - warn!("cleartext auth flow override is enabled, proceeding"); - let password = AuthFlow::new(client) - .begin(auth::CleartextPassword) - .await? - .authenticate() - .await?; - - let mut node = api.wake_compute(extra, creds).await?; - node.config.password(password); - - Ok(AuthSuccess { - reported_auth_ok: false, - value: node, - }) -} - -/// Workaround for clients which don't provide an endpoint (project) name. -/// Very similar to [`do_cleartext`], but there's a specific password format. -async fn do_password_hack( - api: &impl console::Api, - extra: &ConsoleReqExtra<'_>, - creds: &mut ClientCredentials<'_>, - client: &mut stream::PqStream, -) -> auth::Result> { - warn!("project not specified, resorting to the password hack auth flow"); - let payload = AuthFlow::new(client) - .begin(auth::PasswordHack) - .await? - .authenticate() - .await?; - - info!(project = &payload.project, "received missing parameter"); - creds.project = Some(payload.project.into()); - - let mut node = api.wake_compute(extra, creds).await?; - node.config.password(payload.password); - - Ok(AuthSuccess { - reported_auth_ok: false, - value: node, - }) -} - /// True to its name, this function encapsulates our current auth trade-offs. /// Here, we choose the appropriate auth flow based on circumstances. async fn auth_quirks( @@ -171,7 +119,8 @@ async fn auth_quirks( // support SNI or other means of passing the endpoint (project) name. // We now expect to see a very specific payload in the place of password. if creds.project.is_none() { - return do_password_hack(api, extra, creds, client).await; + // Password will be checked by the compute node later. + return hacks::password_hack(api, extra, creds, client).await; } // Password hack should set the project name. @@ -181,7 +130,8 @@ async fn auth_quirks( // Perform cleartext auth if we're allowed to do that. // Currently, we use it for websocket connections (latency). if allow_cleartext { - return do_cleartext_hack(api, extra, creds, client).await; + // Password will be checked by the compute node later. + return hacks::cleartext_hack(api, extra, creds, client).await; } // Finally, proceed with the main auth flow (SCRAM-based). diff --git a/proxy/src/auth/backend/hacks.rs b/proxy/src/auth/backend/hacks.rs new file mode 100644 index 0000000000..f710581cb2 --- /dev/null +++ b/proxy/src/auth/backend/hacks.rs @@ -0,0 +1,66 @@ +use super::AuthSuccess; +use crate::{ + auth::{self, AuthFlow, ClientCredentials}, + console::{ + self, + provider::{CachedNodeInfo, ConsoleReqExtra}, + }, + stream, +}; +use tokio::io::{AsyncRead, AsyncWrite}; +use tracing::{info, warn}; + +/// Compared to [SCRAM](crate::scram), cleartext password auth saves +/// one round trip and *expensive* computations (>= 4096 HMAC iterations). +/// These properties are benefical for serverless JS workers, so we +/// use this mechanism for websocket connections. +pub async fn cleartext_hack( + api: &impl console::Api, + extra: &ConsoleReqExtra<'_>, + creds: &mut ClientCredentials<'_>, + client: &mut stream::PqStream, +) -> auth::Result> { + warn!("cleartext auth flow override is enabled, proceeding"); + let password = AuthFlow::new(client) + .begin(auth::CleartextPassword) + .await? + .authenticate() + .await?; + + let mut node = api.wake_compute(extra, creds).await?; + node.config.password(password); + + // Report tentative success; compute node will check the password anyway. + Ok(AuthSuccess { + reported_auth_ok: false, + value: node, + }) +} + +/// Workaround for clients which don't provide an endpoint (project) name. +/// Very similar to [`cleartext_hack`], but there's a specific password format. +pub async fn password_hack( + api: &impl console::Api, + extra: &ConsoleReqExtra<'_>, + creds: &mut ClientCredentials<'_>, + client: &mut stream::PqStream, +) -> auth::Result> { + warn!("project not specified, resorting to the password hack auth flow"); + let payload = AuthFlow::new(client) + .begin(auth::PasswordHack) + .await? + .authenticate() + .await?; + + info!(project = &payload.project, "received missing parameter"); + creds.project = Some(payload.project.into()); + + let mut node = api.wake_compute(extra, creds).await?; + node.config.password(payload.password); + + // Report tentative success; compute node will check the password anyway. + Ok(AuthSuccess { + reported_auth_ok: false, + value: node, + }) +} diff --git a/proxy/src/proxy.rs b/proxy/src/proxy.rs index c1ed79ecb6..0dc48f1212 100644 --- a/proxy/src/proxy.rs +++ b/proxy/src/proxy.rs @@ -404,7 +404,7 @@ impl Client<'_, S> { async fn connect_to_db( self, session: cancellation::Session<'_>, - use_cleartext_password_flow: bool, + allow_cleartext: bool, ) -> anyhow::Result<()> { let Self { mut stream, @@ -421,7 +421,7 @@ impl Client<'_, S> { let auth_result = async { // `&mut stream` doesn't let us merge those 2 lines. let res = creds - .authenticate(&extra, &mut stream, use_cleartext_password_flow) + .authenticate(&extra, &mut stream, allow_cleartext) .await; async { res }.or_else(|e| stream.throw_error(e)).await }