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::Exclusive;
17
18use ::auth::{
19    Identity, Password, PgAuthInfo, PgScramSha256Verifier, UserInfoRef, UserProviderRef,
20    userinfo_by_name,
21};
22use async_trait::async_trait;
23use base64::Engine;
24use base64::engine::general_purpose::STANDARD as BASE64;
25use common_catalog::parse_catalog_and_schema_from_db_string;
26use common_error::ext::ErrorExt;
27use common_error::status_code::StatusCode;
28use common_time::Timezone;
29use futures::{Sink, SinkExt};
30use pgwire::api::auth::StartupHandler;
31use pgwire::api::auth::sasl::SCRAM_SHA_256_METHOD;
32use pgwire::api::{ClientInfo, PgWireConnectionState, auth};
33use pgwire::error::{ErrorInfo, PgWireError, PgWireResult};
34use pgwire::messages::response::ErrorResponse;
35use pgwire::messages::startup::{Authentication, PasswordMessageFamily, SecretKey};
36use pgwire::messages::{PgWireBackendMessage, PgWireFrontendMessage};
37use session::Session;
38use snafu::IntoError;
39use tokio::sync::Mutex;
40
41use crate::error::{AuthSnafu, Result};
42use crate::metrics::METRIC_AUTH_FAILURE;
43use crate::postgres::PostgresServerHandlerInner;
44use crate::postgres::types::PgErrorCode;
45use crate::postgres::utils::convert_err;
46use crate::query_handler::sql::ServerSqlQueryHandlerRef;
47
48pub(crate) struct PgLoginVerifier {
49    user_provider: Option<UserProviderRef>,
50    state: Mutex<PgAuthenticationState>,
51}
52
53impl PgLoginVerifier {
54    pub(crate) fn new(user_provider: Option<UserProviderRef>) -> Self {
55        Self {
56            user_provider,
57            state: Mutex::new(PgAuthenticationState::Initial),
58        }
59    }
60}
61
62enum PgAuthenticationState {
63    Initial,
64    Cleartext,
65    SaslInitial {
66        auth_info: PgAuthInfo,
67    },
68    SaslFinal {
69        verifier: PgScramSha256Verifier,
70        user_info: Option<UserInfoRef>,
71        channel_binding: String,
72        nonce: String,
73        client_first_bare: String,
74        server_first: String,
75    },
76}
77
78#[allow(dead_code)]
79struct LoginInfo {
80    user: Option<String>,
81    catalog: Option<String>,
82    schema: Option<String>,
83    host: String,
84}
85
86impl LoginInfo {
87    pub fn from_client_info<C>(client: &C) -> LoginInfo
88    where
89        C: ClientInfo,
90    {
91        LoginInfo {
92            user: client.metadata().get(super::METADATA_USER).map(Into::into),
93            catalog: client
94                .metadata()
95                .get(super::METADATA_CATALOG)
96                .map(Into::into),
97            schema: client
98                .metadata()
99                .get(super::METADATA_SCHEMA)
100                .map(Into::into),
101            host: client.socket_addr().ip().to_string(),
102        }
103    }
104}
105
106impl PgLoginVerifier {
107    async fn auth(&self, login: &LoginInfo, password: &str) -> Result<Option<UserInfoRef>> {
108        let user_provider = match &self.user_provider {
109            Some(provider) => provider,
110            None => return Ok(None),
111        };
112
113        let user_name = match &login.user {
114            Some(name) => name,
115            None => return Ok(None),
116        };
117        let catalog = match &login.catalog {
118            Some(name) => name,
119            None => return Ok(None),
120        };
121        let schema = match &login.schema {
122            Some(name) => name,
123            None => return Ok(None),
124        };
125
126        match user_provider
127            .auth(
128                Identity::UserId(user_name, None),
129                Password::PlainText(password.to_string().into()),
130                catalog,
131                schema,
132            )
133            .await
134        {
135            Err(e) => {
136                METRIC_AUTH_FAILURE
137                    .with_label_values(&[e.status_code().as_ref()])
138                    .inc();
139                Err(AuthSnafu.into_error(e))
140            }
141            Ok(user_info) => Ok(Some(user_info)),
142        }
143    }
144
145    async fn postgres_auth_info(&self, login: &LoginInfo) -> Result<PgAuthInfo> {
146        let user_provider = match &self.user_provider {
147            Some(provider) => provider,
148            None => return Ok(PgAuthInfo::Cleartext),
149        };
150
151        let user_name = match &login.user {
152            Some(name) => name,
153            None => return Ok(PgAuthInfo::Cleartext),
154        };
155
156        match user_provider
157            .postgres_auth_info(Identity::UserId(user_name, None))
158            .await
159        {
160            Err(e) => {
161                METRIC_AUTH_FAILURE
162                    .with_label_values(&[e.status_code().as_ref()])
163                    .inc();
164                Err(AuthSnafu.into_error(e))
165            }
166            Ok(auth_info) => Ok(auth_info),
167        }
168    }
169
170    async fn authorize(&self, login: &LoginInfo, user_info: &UserInfoRef) -> Result<()> {
171        let user_provider = match &self.user_provider {
172            Some(provider) => provider,
173            None => return Ok(()),
174        };
175
176        let catalog = match &login.catalog {
177            Some(name) => name,
178            None => return Ok(()),
179        };
180        let schema = match &login.schema {
181            Some(name) => name,
182            None => return Ok(()),
183        };
184
185        match user_provider.authorize(catalog, schema, user_info).await {
186            Err(e) => {
187                METRIC_AUTH_FAILURE
188                    .with_label_values(&[e.status_code().as_ref()])
189                    .inc();
190                Err(AuthSnafu.into_error(e))
191            }
192            Ok(()) => Ok(()),
193        }
194    }
195}
196
197fn set_client_info<C>(client: &mut C, session: &Session)
198where
199    C: ClientInfo,
200{
201    if let Some(current_catalog) = client.metadata().get(super::METADATA_CATALOG) {
202        session.set_catalog(current_catalog.clone());
203    }
204    if let Some(current_schema) = client.metadata().get(super::METADATA_SCHEMA) {
205        session.set_schema(current_schema.clone());
206    }
207
208    // pass generated process id and secret key to client, this information will
209    // be sent to postgres client for query cancellation.
210    // use all 0 before we actually supported query cancellation
211    client.set_pid_and_secret_key(0, SecretKey::I32(0));
212    // set userinfo outside
213}
214
215#[async_trait]
216impl StartupHandler for PostgresServerHandlerInner {
217    async fn on_startup<C>(
218        &self,
219        client: &mut C,
220        message: PgWireFrontendMessage,
221    ) -> PgWireResult<()>
222    where
223        C: ClientInfo + Sink<PgWireBackendMessage> + Unpin + Send,
224        C::Error: Debug,
225        PgWireError: From<<C as Sink<PgWireBackendMessage>>::Error>,
226    {
227        match message {
228            PgWireFrontendMessage::Startup(ref startup) => {
229                // check ssl requirement
230                if !client.is_secure() && self.force_tls {
231                    send_error(
232                        client,
233                        PgErrorCode::Ec28000.to_err_info("No encryption".to_string()),
234                    )
235                    .await?;
236                    return Ok(());
237                }
238
239                auth::save_startup_parameters_to_metadata(client, startup);
240
241                // check if db is valid
242                match resolve_db_info(Exclusive::new(client), self.query_handler.clone()).await? {
243                    DbResolution::Resolved(catalog, schema) => {
244                        let metadata = client.metadata_mut();
245                        let _ = metadata.insert(super::METADATA_CATALOG.to_owned(), catalog);
246                        let _ = metadata.insert(super::METADATA_SCHEMA.to_owned(), schema);
247                    }
248                    DbResolution::NotFound(msg) => {
249                        send_error(client, PgErrorCode::Ec3D000.to_err_info(msg)).await?;
250                        return Ok(());
251                    }
252                }
253
254                // try to set TimeZone
255                if let Some(tz) = client.metadata().get("TimeZone") {
256                    match Timezone::from_tz_string(tz) {
257                        Ok(tz) => self.session.set_timezone(tz),
258                        Err(_) => {
259                            send_error(
260                                client,
261                                PgErrorCode::Ec22023
262                                    .to_err_info(format!("Invalid TimeZone: {}", tz)),
263                            )
264                            .await?;
265
266                            return Ok(());
267                        }
268                    }
269                }
270
271                if self.login_verifier.user_provider.is_some() {
272                    let login_info = LoginInfo::from_client_info(client);
273                    let auth_info = match self.login_verifier.postgres_auth_info(&login_info).await
274                    {
275                        Ok(auth_info) => auth_info,
276                        Err(_) => {
277                            return send_password_authentication_failed(client).await;
278                        }
279                    };
280
281                    client.set_state(PgWireConnectionState::AuthenticationInProgress);
282                    match auth_info {
283                        PgAuthInfo::ScramSha256 { .. } => {
284                            *self.login_verifier.state.lock().await =
285                                PgAuthenticationState::SaslInitial { auth_info };
286                            client
287                                .send(PgWireBackendMessage::Authentication(Authentication::SASL(
288                                    vec![SCRAM_SHA_256_METHOD.to_string()],
289                                )))
290                                .await?;
291                        }
292                        PgAuthInfo::Cleartext => {
293                            *self.login_verifier.state.lock().await =
294                                PgAuthenticationState::Cleartext;
295                            client
296                                .send(PgWireBackendMessage::Authentication(
297                                    Authentication::CleartextPassword,
298                                ))
299                                .await?;
300                        }
301                    }
302                } else {
303                    self.session.set_user_info(userinfo_by_name(
304                        client.metadata().get(super::METADATA_USER).cloned(),
305                    ));
306                    set_client_info(client, &self.session);
307                    auth::finish_authentication(client, self.param_provider.as_ref()).await?;
308                }
309            }
310            PgWireFrontendMessage::PasswordMessageFamily(pwd) => {
311                let login_info = LoginInfo::from_client_info(client);
312                match self
313                    .authenticate_password_message(client, &login_info, pwd)
314                    .await?
315                {
316                    PgAuthenticationResult::Continue => {}
317                    PgAuthenticationResult::Success(user_info) => {
318                        self.session.set_user_info(user_info);
319                        set_client_info(client, &self.session);
320                        auth::finish_authentication(client, self.param_provider.as_ref()).await?;
321                    }
322                    PgAuthenticationResult::Failed => {
323                        return send_password_authentication_failed(client).await;
324                    }
325                }
326            }
327            _ => {}
328        }
329        Ok(())
330    }
331}
332
333enum PgAuthenticationResult {
334    Continue,
335    Success(UserInfoRef),
336    Failed,
337}
338
339impl PostgresServerHandlerInner {
340    /// Records a rejected SCRAM attempt in [`METRIC_AUTH_FAILURE`]. The label is
341    /// intentionally uniform (never `UserNotFound`), so the counter cannot be
342    /// used to distinguish a wrong password from an unknown user.
343    fn record_scram_failure(result: PgAuthenticationResult) -> PgAuthenticationResult {
344        if matches!(result, PgAuthenticationResult::Failed) {
345            METRIC_AUTH_FAILURE
346                .with_label_values(&[StatusCode::UserPasswordMismatch.as_ref()])
347                .inc();
348        }
349        result
350    }
351
352    async fn authenticate_password_message<C>(
353        &self,
354        client: &mut C,
355        login_info: &LoginInfo,
356        pwd: PasswordMessageFamily,
357    ) -> PgWireResult<PgAuthenticationResult>
358    where
359        C: ClientInfo + Sink<PgWireBackendMessage> + Unpin + Send,
360        C::Error: Debug,
361        PgWireError: From<<C as Sink<PgWireBackendMessage>>::Error>,
362    {
363        let mut state = self.login_verifier.state.lock().await;
364        let current_state = std::mem::replace(&mut *state, PgAuthenticationState::Initial);
365
366        match current_state {
367            PgAuthenticationState::Cleartext => {
368                let pwd = pwd.into_password()?;
369                drop(state);
370                match self.login_verifier.auth(login_info, &pwd.password).await {
371                    Ok(Some(user_info)) => Ok(PgAuthenticationResult::Success(user_info)),
372                    Ok(None) | Err(_) => Ok(PgAuthenticationResult::Failed),
373                }
374            }
375            PgAuthenticationState::SaslInitial { auth_info } => {
376                // A labeled block funnels every rejection through a single
377                // `record_scram_failure`, so failed SCRAM attempts still show up
378                // in `METRIC_AUTH_FAILURE` without sprinkling the metric call
379                // across each early return.
380                let result = 'sasl: {
381                    let sasl_initial = pwd.into_sasl_initial_response()?;
382                    if sasl_initial.auth_method != SCRAM_SHA_256_METHOD {
383                        break 'sasl PgAuthenticationResult::Failed;
384                    }
385                    let Some(data) = sasl_initial.data else {
386                        break 'sasl PgAuthenticationResult::Failed;
387                    };
388                    let Some(client_first) = ScramClientFirst::parse(&data) else {
389                        break 'sasl PgAuthenticationResult::Failed;
390                    };
391                    let PgAuthInfo::ScramSha256 {
392                        verifier,
393                        user_info,
394                    } = auth_info
395                    else {
396                        break 'sasl PgAuthenticationResult::Failed;
397                    };
398
399                    let server_nonce = BASE64.encode(rand::random::<[u8; 18]>());
400                    let nonce = format!("{}{}", client_first.nonce, server_nonce);
401                    let server_first = format!(
402                        "r={},s={},i={}",
403                        nonce,
404                        BASE64.encode(verifier.salt()),
405                        verifier.iterations()
406                    );
407                    client
408                        .send(PgWireBackendMessage::Authentication(
409                            Authentication::SASLContinue(server_first.clone().into()),
410                        ))
411                        .await?;
412                    *state = PgAuthenticationState::SaslFinal {
413                        verifier,
414                        user_info,
415                        channel_binding: client_first.channel_binding,
416                        nonce,
417                        client_first_bare: client_first.bare,
418                        server_first,
419                    };
420                    PgAuthenticationResult::Continue
421                };
422                Ok(Self::record_scram_failure(result))
423            }
424            PgAuthenticationState::SaslFinal {
425                verifier,
426                user_info,
427                channel_binding,
428                nonce,
429                client_first_bare,
430                server_first,
431            } => {
432                let result = 'sasl: {
433                    let sasl_response = pwd.into_sasl_response()?;
434                    let Some(client_final) = ScramClientFinal::parse(&sasl_response.data) else {
435                        break 'sasl PgAuthenticationResult::Failed;
436                    };
437                    if client_final.channel_binding != channel_binding {
438                        break 'sasl PgAuthenticationResult::Failed;
439                    }
440                    if client_final.nonce != nonce {
441                        break 'sasl PgAuthenticationResult::Failed;
442                    }
443
444                    let auth_message = format!(
445                        "{},{},{}",
446                        client_first_bare, server_first, client_final.without_proof
447                    );
448                    let Ok(client_proof) = BASE64.decode(client_final.proof.as_bytes()) else {
449                        break 'sasl PgAuthenticationResult::Failed;
450                    };
451                    let Ok(Some(server_signature)) =
452                        verifier.verify_client_proof(auth_message.as_bytes(), &client_proof)
453                    else {
454                        break 'sasl PgAuthenticationResult::Failed;
455                    };
456                    let Some(user_info) = user_info else {
457                        break 'sasl PgAuthenticationResult::Failed;
458                    };
459
460                    drop(state);
461                    if self
462                        .login_verifier
463                        .authorize(login_info, &user_info)
464                        .await
465                        .is_err()
466                    {
467                        // `authorize` already recorded this failure; return early
468                        // to bypass `record_scram_failure` and avoid double-counting.
469                        return Ok(PgAuthenticationResult::Failed);
470                    }
471
472                    client
473                        .send(PgWireBackendMessage::Authentication(
474                            Authentication::SASLFinal(
475                                format!("v={}", BASE64.encode(server_signature)).into(),
476                            ),
477                        ))
478                        .await?;
479                    PgAuthenticationResult::Success(user_info)
480                };
481                Ok(Self::record_scram_failure(result))
482            }
483            PgAuthenticationState::Initial => Ok(PgAuthenticationResult::Failed),
484        }
485    }
486}
487
488struct ScramClientFirst {
489    channel_binding: String,
490    bare: String,
491    nonce: String,
492}
493
494impl ScramClientFirst {
495    fn parse(data: &[u8]) -> Option<Self> {
496        let message = std::str::from_utf8(data).ok()?;
497        let mut parts = message.splitn(3, ',');
498        let cbind = parts.next()?;
499        if !matches!(cbind, "n" | "y") {
500            return None;
501        }
502        let authzid = parts.next()?;
503        if !authzid.is_empty() {
504            return None;
505        }
506        let channel_binding = BASE64.encode(format!("{cbind},,"));
507        let bare = parts.next()?.to_string();
508        let nonce = bare
509            .split(',')
510            .find_map(|chunk| chunk.strip_prefix("r="))?
511            .to_string();
512        Some(Self {
513            channel_binding,
514            bare,
515            nonce,
516        })
517    }
518}
519
520struct ScramClientFinal {
521    channel_binding: String,
522    nonce: String,
523    without_proof: String,
524    proof: String,
525}
526
527impl ScramClientFinal {
528    fn parse(data: &[u8]) -> Option<Self> {
529        let message = std::str::from_utf8(data).ok()?;
530        let proof_pos = message.rfind(",p=")?;
531        let without_proof = message[..proof_pos].to_string();
532        let proof = message[proof_pos + 3..].to_string();
533        let channel_binding = without_proof
534            .split(',')
535            .find_map(|chunk| chunk.strip_prefix("c="))?;
536        let nonce = without_proof
537            .split(',')
538            .find_map(|chunk| chunk.strip_prefix("r="))?;
539        if nonce.is_empty() || proof.is_empty() {
540            return None;
541        }
542
543        Some(Self {
544            channel_binding: channel_binding.to_string(),
545            nonce: nonce.to_string(),
546            without_proof,
547            proof,
548        })
549    }
550}
551
552async fn send_error<C>(client: &mut C, err_info: ErrorInfo) -> PgWireResult<()>
553where
554    C: ClientInfo + Sink<PgWireBackendMessage> + Unpin + Send,
555    C::Error: Debug,
556    PgWireError: From<<C as Sink<PgWireBackendMessage>>::Error>,
557{
558    let error = ErrorResponse::from(err_info);
559    client
560        .feed(PgWireBackendMessage::ErrorResponse(error))
561        .await?;
562    client.close().await?;
563    Ok(())
564}
565
566async fn send_password_authentication_failed<C>(client: &mut C) -> PgWireResult<()>
567where
568    C: ClientInfo + Sink<PgWireBackendMessage> + Unpin + Send,
569    C::Error: Debug,
570    PgWireError: From<<C as Sink<PgWireBackendMessage>>::Error>,
571{
572    send_error(
573        client,
574        PgErrorCode::Ec28P01.to_err_info("password authentication failed".to_string()),
575    )
576    .await
577}
578
579enum DbResolution {
580    Resolved(String, String),
581    NotFound(String),
582}
583
584/// A function extracted to resolve lifetime and readability issues:
585async fn resolve_db_info<C>(
586    client: Exclusive<&mut C>,
587    query_handler: ServerSqlQueryHandlerRef,
588) -> PgWireResult<DbResolution>
589where
590    C: ClientInfo + Unpin + Send,
591{
592    let db_ref = client.into_inner().metadata().get(super::METADATA_DATABASE);
593    if let Some(db) = db_ref {
594        let (catalog, schema) = parse_catalog_and_schema_from_db_string(db);
595        if query_handler
596            .is_valid_schema(&catalog, &schema)
597            .await
598            .map_err(convert_err)?
599        {
600            Ok(DbResolution::Resolved(catalog, schema))
601        } else {
602            Ok(DbResolution::NotFound(format!("Database not found: {db}")))
603        }
604    } else {
605        Ok(DbResolution::NotFound("Database not specified".to_owned()))
606    }
607}
608
609#[cfg(test)]
610mod tests {
611    use super::*;
612
613    #[test]
614    fn test_scram_client_first_parse() {
615        let message = b"n,,n=greptime,r=clientnonce";
616        let client_first = ScramClientFirst::parse(message).unwrap();
617        assert_eq!("biws", client_first.channel_binding);
618        assert_eq!("n=greptime,r=clientnonce", client_first.bare);
619        assert_eq!("clientnonce", client_first.nonce);
620
621        let message = b"y,,n=greptime,r=clientnonce";
622        let client_first = ScramClientFirst::parse(message).unwrap();
623        assert_eq!("eSws", client_first.channel_binding);
624        assert_eq!("n=greptime,r=clientnonce", client_first.bare);
625        assert_eq!("clientnonce", client_first.nonce);
626
627        assert!(ScramClientFirst::parse(b"p=tls-server-end-point,,n=greptime,r=nonce").is_none());
628        assert!(ScramClientFirst::parse(b"n,a=authzid,n=greptime,r=nonce").is_none());
629    }
630
631    #[test]
632    fn test_scram_client_final_parse() {
633        let message = b"c=biws,r=clientnonceservernonce,p=dGVzdA==";
634        let client_final = ScramClientFinal::parse(message).unwrap();
635        assert_eq!("biws", client_final.channel_binding);
636        assert_eq!("clientnonceservernonce", client_final.nonce);
637        assert_eq!(
638            "c=biws,r=clientnonceservernonce",
639            client_final.without_proof
640        );
641        assert_eq!("dGVzdA==", client_final.proof);
642
643        assert!(ScramClientFinal::parse(b"r=nonce,p=dGVzdA==").is_none());
644        assert!(ScramClientFinal::parse(b"c=biws,r=nonce").is_none());
645        assert!(ScramClientFinal::parse(b"c=biws,r=,p=dGVzdA==").is_none());
646    }
647}