Skip to main content

servers/postgres/
auth_handler.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::fmt::Debug;
16use std::sync::Arc;
17use std::sync::atomic::Ordering;
18
19use ::auth::{
20    BEARER_TOKEN_USER, Identity, Password, PgAuthInfo, PgScramSha256Verifier, UserInfoRef,
21    UserProviderRef, userinfo_by_name,
22};
23use async_trait::async_trait;
24use common_catalog::parse_catalog_and_schema_from_db_string;
25use common_error::ext::ErrorExt;
26use common_error::status_code::StatusCode;
27use common_time::Timezone;
28use futures::{Sink, SinkExt};
29use pgwire::api::auth::StartupHandler;
30use pgwire::api::auth::cleartext::CleartextPasswordAuth;
31use pgwire::api::auth::mechanism::AuthMechanismFactory;
32use pgwire::api::auth::sasl::scram::{ScramAuth, ScramSecret};
33use pgwire::api::auth::{AuthOutcome, AuthSource, LoginInfo as WireLoginInfo};
34use pgwire::api::{ClientInfo, PidSecretKeyGenerator, auth};
35use pgwire::error::{ErrorInfo, PgWireError, PgWireResult};
36use pgwire::messages::response::ErrorResponse;
37use pgwire::messages::startup::SecretKey;
38use pgwire::messages::{PgWireBackendMessage, PgWireFrontendMessage};
39use session::Session;
40use snafu::IntoError;
41
42use crate::error::{AuthSnafu, Result};
43use crate::metrics::METRIC_AUTH_FAILURE;
44use crate::postgres::PostgresServerHandlerInner;
45use crate::postgres::types::PgErrorCode;
46use crate::postgres::utils::convert_err;
47use crate::query_handler::sql::ServerSqlQueryHandlerRef;
48
49/// Query cancellation is not supported yet, so announce pid/secret key 0 as
50/// before the `AuthDriver` adoption.
51pub(crate) struct ZeroPidSecretKeyGenerator;
52
53impl PidSecretKeyGenerator for ZeroPidSecretKeyGenerator {
54    fn generate(&self, _client: &dyn ClientInfo) -> (i32, SecretKey) {
55        (0, SecretKey::I32(0))
56    }
57}
58
59fn record_auth_failure(status_code: StatusCode) {
60    METRIC_AUTH_FAILURE
61        .with_label_values(&[status_code.as_ref()])
62        .inc();
63}
64
65/// The [`AuthSource`] shared by pgwire's cleartext and SCRAM mechanisms.
66/// Stateless by design: it only holds the user provider, everything about
67/// the current login arrives with each call, and the verified
68/// [`UserInfoRef`] travels back to the startup handler as the source
69/// identity, reported by [`AuthOutcome::Authenticated`].
70///
71/// Read-only: it resolves credentials and checks authorization with the
72/// provider, but holds no policy — authorization runs in the startup
73/// handler after verification, before the startup completes.
74pub(crate) struct PgUserProviderSource {
75    user_provider: UserProviderRef,
76}
77
78impl Debug for PgUserProviderSource {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        f.debug_struct("PgUserProviderSource")
81            .finish_non_exhaustive()
82    }
83}
84
85impl PgUserProviderSource {
86    pub(crate) fn new(user_provider: UserProviderRef) -> Self {
87        Self { user_provider }
88    }
89
90    /// (user, catalog, schema) of this login. Catalog/schema are parsed from
91    /// the `database` startup parameter, which was validated at connection
92    /// start.
93    fn resolve(login: &WireLoginInfo<'_>) -> Option<(String, String, String)> {
94        let user = login.user()?.to_string();
95        let (catalog, schema) = parse_catalog_and_schema_from_db_string(login.database()?);
96        Some((user, catalog, schema))
97    }
98
99    /// Which mechanism to use for this login. Called once at connection
100    /// start; the SCRAM machinery resolves the verifier itself at
101    /// client-first time.
102    async fn postgres_auth_info(&self, login: &WireLoginInfo<'_>) -> Result<PgAuthInfo> {
103        let Some((user, catalog, _)) = Self::resolve(login) else {
104            return Ok(PgAuthInfo::Cleartext);
105        };
106
107        match self
108            .user_provider
109            .postgres_auth_info(Identity::UserId(&user, None), &catalog)
110            .await
111        {
112            Err(e) => {
113                record_auth_failure(e.status_code());
114                Err(AuthSnafu.into_error(e))
115            }
116            Ok(auth_info) => Ok(auth_info),
117        }
118    }
119
120    /// Checks with the provider that the user may access this
121    /// catalog/schema. Called by the startup handler after verification,
122    /// while nothing terminal has been sent yet.
123    async fn authorize(
124        &self,
125        login: &WireLoginInfo<'_>,
126        user_info: &UserInfoRef,
127    ) -> PgWireResult<()> {
128        let Some((_, catalog, schema)) = Self::resolve(login) else {
129            return Err(PgWireError::UserNameRequired);
130        };
131
132        match self
133            .user_provider
134            .authorize(&catalog, &schema, user_info)
135            .await
136        {
137            Err(e) => {
138                record_auth_failure(e.status_code());
139                Err(PgWireError::ApiError(Box::new(AuthSnafu.into_error(e))))
140            }
141            Ok(()) => Ok(()),
142        }
143    }
144}
145
146#[async_trait]
147impl AuthSource<UserInfoRef> for PgUserProviderSource {
148    async fn get_password(
149        &self,
150        _login: &WireLoginInfo,
151    ) -> PgWireResult<pgwire::api::auth::Password> {
152        Err(PgWireError::ApiError(
153            "no cleartext password available from the user provider".into(),
154        ))
155    }
156
157    /// Cleartext and bearer-token verification; the provider verifies the
158    /// password itself. Authorization is left to the startup handler.
159    async fn verify_password(
160        &self,
161        login: &WireLoginInfo,
162        presented_password: &[u8],
163    ) -> PgWireResult<Option<UserInfoRef>> {
164        let Some((user, catalog, _schema)) = Self::resolve(login) else {
165            return Err(PgWireError::UserNameRequired);
166        };
167        let password = String::from_utf8_lossy(presented_password).into_owned();
168
169        let result = if user == BEARER_TOKEN_USER {
170            self.user_provider
171                .authenticate_bearer_token(&password, &catalog)
172                .await
173        } else {
174            self.user_provider
175                .authenticate(
176                    Identity::UserId(&user, None),
177                    Password::PlainText(password.into()),
178                )
179                .await
180        };
181        match result {
182            Err(e) => {
183                record_auth_failure(e.status_code());
184                Err(PgWireError::ApiError(Box::new(AuthSnafu.into_error(e))))
185            }
186            Ok(user_info) => Ok(Some(user_info)),
187        }
188    }
189
190    async fn get_scram_secret(
191        &self,
192        login: &WireLoginInfo,
193    ) -> PgWireResult<(ScramSecret, Option<UserInfoRef>)> {
194        match self.postgres_auth_info(login).await {
195            // For unknown users the provider hands out a mock verifier with
196            // no identity, so their handshake fails at proof verification
197            // exactly like a wrong password.
198            Ok(PgAuthInfo::ScramSha256 {
199                verifier,
200                user_info,
201            }) => Ok((to_scram_secret(&verifier), user_info)),
202            // SCRAM was selected but the provider no longer backs it: fail
203            // closed with a mock verifier.
204            _ => {
205                let user = login.user().unwrap_or_default();
206                Ok((
207                    to_scram_secret(&PgScramSha256Verifier::mock_for_unknown_user(
208                        user.as_bytes(),
209                    )),
210                    None,
211                ))
212            }
213        }
214    }
215}
216
217fn to_scram_secret(verifier: &PgScramSha256Verifier) -> ScramSecret {
218    ScramSecret::Verifier {
219        salt: verifier.salt().to_vec(),
220        iterations: verifier.iterations() as usize,
221        stored_key: verifier.stored_key().to_vec(),
222        server_key: verifier.server_key().to_vec(),
223    }
224}
225
226fn set_client_info<C>(client: &mut C, session: &Session)
227where
228    C: ClientInfo,
229{
230    if let Some(current_catalog) = client.metadata().get(super::METADATA_CATALOG) {
231        session.set_catalog(current_catalog.clone());
232    }
233    if let Some(current_schema) = client.metadata().get(super::METADATA_SCHEMA) {
234        session.set_schema(current_schema.clone());
235    }
236}
237
238enum MechanismSelection {
239    /// No user provider configured: skip authentication.
240    NoAuth,
241    Cleartext(Arc<PgUserProviderSource>),
242    Scram(Arc<PgUserProviderSource>),
243    /// Resolving the mechanism failed.
244    Failed,
245}
246
247impl PostgresServerHandlerInner {
248    async fn select_mechanism<C>(&self, client: &C) -> MechanismSelection
249    where
250        C: ClientInfo + Sync,
251    {
252        let Some(source) = self.auth_source.clone() else {
253            return MechanismSelection::NoAuth;
254        };
255        let login = WireLoginInfo::from_client_info(client);
256        if login.user() == Some(BEARER_TOKEN_USER) {
257            return MechanismSelection::Cleartext(source);
258        }
259        match source.postgres_auth_info(&login).await {
260            Err(_) => MechanismSelection::Failed,
261            Ok(PgAuthInfo::Cleartext) => MechanismSelection::Cleartext(source),
262            Ok(PgAuthInfo::ScramSha256 { .. }) => MechanismSelection::Scram(source),
263        }
264    }
265}
266
267#[async_trait]
268impl StartupHandler for PostgresServerHandlerInner {
269    async fn on_startup<C>(
270        &self,
271        client: &mut C,
272        message: PgWireFrontendMessage,
273    ) -> PgWireResult<()>
274    where
275        C: ClientInfo + Sink<PgWireBackendMessage> + Unpin + Send + Sync,
276        C::Error: Debug,
277        PgWireError: From<<C as Sink<PgWireBackendMessage>>::Error>,
278    {
279        match message {
280            PgWireFrontendMessage::Startup(ref startup) => {
281                // check ssl requirement
282                if !client.is_secure() && self.force_tls {
283                    send_error(
284                        client,
285                        PgErrorCode::Ec28000.to_err_info("No encryption".to_string()),
286                    )
287                    .await?;
288                    return Ok(());
289                }
290
291                auth::protocol_negotiation(client, startup).await?;
292                auth::save_startup_parameters_to_metadata(client, startup);
293
294                // check if db is valid
295                match resolve_db_info(client, self.query_handler.clone()).await? {
296                    DbResolution::Resolved(catalog, schema) => {
297                        let metadata = client.metadata_mut();
298                        let _ = metadata.insert(super::METADATA_CATALOG.to_owned(), catalog);
299                        let _ = metadata.insert(super::METADATA_SCHEMA.to_owned(), schema);
300                    }
301                    DbResolution::NotFound(msg) => {
302                        send_error(client, PgErrorCode::Ec3D000.to_err_info(msg)).await?;
303                        return Ok(());
304                    }
305                }
306
307                // try to set TimeZone
308                if let Some(tz) = client.metadata().get("TimeZone") {
309                    match Timezone::from_tz_string(tz) {
310                        Ok(tz) => self.session.set_timezone(tz),
311                        Err(_) => {
312                            send_error(
313                                client,
314                                PgErrorCode::Ec22023
315                                    .to_err_info(format!("Invalid TimeZone: {}", tz)),
316                            )
317                            .await?;
318
319                            return Ok(());
320                        }
321                    }
322                }
323
324                match self.select_mechanism(client).await {
325                    MechanismSelection::NoAuth => {
326                        self.session.set_user_info(userinfo_by_name(
327                            client.metadata().get(super::METADATA_USER).cloned(),
328                        ));
329                        set_client_info(client, &self.session);
330                        self.auth_driver
331                            .complete_authentication(client, None)
332                            .await?;
333                    }
334                    MechanismSelection::Cleartext(source) => {
335                        if self
336                            .auth_driver
337                            .begin_authentication(
338                                client,
339                                CleartextPasswordAuth::new(source).create(),
340                            )
341                            .await
342                            .is_err()
343                        {
344                            return send_password_authentication_failed(client).await;
345                        }
346                    }
347                    MechanismSelection::Scram(source) => {
348                        self.is_scram_auth.store(true, Ordering::Relaxed);
349                        if self
350                            .auth_driver
351                            .begin_authentication(client, ScramAuth::new(source).create())
352                            .await
353                            .is_err()
354                        {
355                            return send_password_authentication_failed(client).await;
356                        }
357                    }
358                    MechanismSelection::Failed => {
359                        return send_password_authentication_failed(client).await;
360                    }
361                }
362            }
363            PgWireFrontendMessage::PasswordMessageFamily(pwd) => {
364                match self.auth_driver.advance_authentication(client, pwd).await {
365                    Ok(AuthOutcome::InProgress) => {}
366                    Ok(AuthOutcome::Authenticated {
367                        identity,
368                        final_message,
369                    }) => {
370                        self.is_scram_auth.store(false, Ordering::Relaxed);
371                        let Some(user_info) = identity else {
372                            return send_password_authentication_failed(client).await;
373                        };
374
375                        // Nothing terminal has been sent yet: authorize
376                        // before completing, so a rejection reaches the
377                        // client as an authentication failure. Failures are
378                        // recorded by `authorize` itself.
379                        let authorized = {
380                            let login = WireLoginInfo::from_client_info(client);
381                            match &self.auth_source {
382                                Some(source) => source.authorize(&login, &user_info).await.is_ok(),
383                                None => false,
384                            }
385                        };
386                        if !authorized {
387                            return send_password_authentication_failed(client).await;
388                        }
389
390                        self.session.set_user_info(user_info);
391                        set_client_info(client, &self.session);
392                        self.auth_driver
393                            .complete_authentication(client, final_message)
394                            .await?;
395                    }
396                    Err(_e) => {
397                        // SCRAM failures carry a uniform label so the metric
398                        // cannot distinguish wrong password, unknown and
399                        // unauthorized users.
400                        if self.is_scram_auth.swap(false, Ordering::Relaxed) {
401                            record_auth_failure(StatusCode::UserPasswordMismatch);
402                        }
403                        return send_password_authentication_failed(client).await;
404                    }
405                }
406            }
407            _ => {}
408        }
409        Ok(())
410    }
411}
412
413async fn send_error<C>(client: &mut C, err_info: ErrorInfo) -> PgWireResult<()>
414where
415    C: ClientInfo + Sink<PgWireBackendMessage> + Unpin + Send,
416    C::Error: Debug,
417    PgWireError: From<<C as Sink<PgWireBackendMessage>>::Error>,
418{
419    let error = ErrorResponse::from(err_info);
420    client
421        .feed(PgWireBackendMessage::ErrorResponse(error))
422        .await?;
423    client.close().await?;
424    Ok(())
425}
426
427async fn send_password_authentication_failed<C>(client: &mut C) -> PgWireResult<()>
428where
429    C: ClientInfo + Sink<PgWireBackendMessage> + Unpin + Send,
430    C::Error: Debug,
431    PgWireError: From<<C as Sink<PgWireBackendMessage>>::Error>,
432{
433    send_error(
434        client,
435        PgErrorCode::Ec28P01.to_err_info("password authentication failed".to_string()),
436    )
437    .await
438}
439
440enum DbResolution {
441    Resolved(String, String),
442    NotFound(String),
443}
444
445/// A function extracted to resolve lifetime and readability issues:
446async fn resolve_db_info<C>(
447    client: &mut C,
448    query_handler: ServerSqlQueryHandlerRef,
449) -> PgWireResult<DbResolution>
450where
451    C: ClientInfo + Unpin + Send,
452{
453    let db_ref = client.metadata().get(super::METADATA_DATABASE);
454    if let Some(db) = db_ref {
455        let (catalog, schema) = parse_catalog_and_schema_from_db_string(db);
456        if query_handler
457            .is_valid_schema(&catalog, &schema)
458            .await
459            .map_err(convert_err)?
460        {
461            Ok(DbResolution::Resolved(catalog, schema))
462        } else {
463            Ok(DbResolution::NotFound(format!("Database not found: {db}")))
464        }
465    } else {
466        Ok(DbResolution::NotFound("Database not specified".to_owned()))
467    }
468}