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