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