1use std::collections::HashMap;
16use std::future::Future;
17use std::str::FromStr;
18use std::sync::Arc;
19use std::time::Instant;
20
21use bytes::Bytes;
22use jsonwebtoken::{Algorithm, DecodingKey, Validation, decode, decode_header};
23use parking_lot::Mutex;
24use risingwave_common::config::HbaEntry;
25use risingwave_common::id::DatabaseId;
26use risingwave_common::types::DataType;
27use risingwave_common::util::runtime::BackgroundShutdownRuntime;
28use risingwave_common::util::tokio_util::sync::CancellationToken;
29use risingwave_sqlparser::ast::Statement;
30use serde::Deserialize;
31use thiserror_ext::AsReport;
32
33use crate::error::{PsqlError, PsqlResult};
34use crate::ldap_auth::LdapAuthenticator;
35use crate::net::{AddressRef, Listener, TcpKeepalive};
36use crate::pg_field_descriptor::PgFieldDescriptor;
37use crate::pg_message::TransactionStatus;
38use crate::pg_protocol::{ConnectionContext, PgByteStream, PgProtocol};
39use crate::pg_response::{PgResponse, ValuesStream};
40use crate::types::Format;
41
42pub type BoxedError = Box<dyn std::error::Error + Send + Sync>;
43type ProcessId = i32;
44type SecretKey = i32;
45pub type SessionId = (ProcessId, SecretKey);
46
47pub trait SessionManager: Send + Sync + 'static {
50 type Error: Into<BoxedError>;
51 type Session: Session<Error = Self::Error>;
52
53 fn create_dummy_session(
56 &self,
57 database_id: DatabaseId,
58 ) -> Result<Arc<Self::Session>, Self::Error>;
59
60 fn connect(
61 &self,
62 database: &str,
63 user_name: &str,
64 peer_addr: AddressRef,
65 ) -> Result<Arc<Self::Session>, Self::Error>;
66
67 fn cancel_queries_in_session(&self, session_id: SessionId);
68
69 fn cancel_creating_jobs_in_session(&self, session_id: SessionId);
70
71 fn end_session(&self, session: &Self::Session);
72
73 fn shutdown(&self) -> impl Future<Output = ()> + Send {
75 async {}
76 }
77}
78
79pub trait Session: Send + Sync {
82 type Error: Into<BoxedError>;
83 type ValuesStream: ValuesStream;
84 type PreparedStatement: Send + Clone + 'static;
85 type Portal: Send + Clone + std::fmt::Display + 'static;
86
87 fn run_one_query(
90 self: Arc<Self>,
91 stmt: Statement,
92 format: Format,
93 ) -> impl Future<Output = Result<PgResponse<Self::ValuesStream>, Self::Error>> + Send;
94
95 fn parse(
96 self: Arc<Self>,
97 sql: Option<Statement>,
98 params_types: Vec<Option<DataType>>,
99 ) -> impl Future<Output = Result<Self::PreparedStatement, Self::Error>> + Send;
100
101 fn next_notice(self: &Arc<Self>) -> impl Future<Output = String> + Send;
105
106 fn bind(
107 self: Arc<Self>,
108 prepare_statement: Self::PreparedStatement,
109 params: Vec<Option<Bytes>>,
110 param_formats: Vec<Format>,
111 result_formats: Vec<Format>,
112 ) -> Result<Self::Portal, Self::Error>;
113
114 fn execute(
115 self: Arc<Self>,
116 portal: Self::Portal,
117 ) -> impl Future<Output = Result<PgResponse<Self::ValuesStream>, Self::Error>> + Send;
118
119 fn describe_statement(
120 self: Arc<Self>,
121 prepare_statement: Self::PreparedStatement,
122 ) -> Result<(Vec<DataType>, Vec<PgFieldDescriptor>), Self::Error>;
123
124 fn describe_portal(
125 self: Arc<Self>,
126 portal: Self::Portal,
127 ) -> Result<Vec<PgFieldDescriptor>, Self::Error>;
128
129 fn user_authenticator(&self) -> &UserAuthenticator;
130
131 fn id(&self) -> SessionId;
132
133 fn get_config(&self, key: &str) -> Result<String, Self::Error>;
134
135 fn set_config(&self, key: &str, value: String) -> Result<String, Self::Error>;
136
137 fn transaction_status(&self) -> TransactionStatus;
138
139 fn init_exec_context(&self, sql: Arc<str>) -> ExecContextGuard;
140
141 fn check_idle_in_transaction_timeout(&self) -> PsqlResult<()>;
142
143 fn user(&self) -> String;
144}
145
146pub struct ExecContext {
149 pub running_sql: Arc<str>,
150 pub last_instant: Instant,
152 pub last_idle_instant: Arc<Mutex<Option<Instant>>>,
154}
155
156pub struct ExecContextGuard(#[expect(dead_code)] Arc<ExecContext>);
159
160impl ExecContextGuard {
161 pub fn new(exec_context: Arc<ExecContext>) -> Self {
162 Self(exec_context)
163 }
164}
165
166impl Drop for ExecContext {
167 fn drop(&mut self) {
168 *self.last_idle_instant.lock() = Some(Instant::now());
169 }
170}
171
172#[derive(Debug, Clone)]
173pub enum UserAuthenticator {
174 None,
176 ClearText(Vec<u8>),
178 Md5WithSalt {
180 encrypted_password: Vec<u8>,
181 salt: [u8; 4],
182 },
183 OAuth {
184 metadata: HashMap<String, String>,
185 cluster_id: String,
186 },
187 Ldap(String, HbaEntry),
188}
189
190#[derive(Debug, Deserialize)]
194struct Jwks {
195 keys: Vec<Jwk>,
196}
197
198#[derive(Debug, Deserialize)]
201struct Jwk {
202 kty: Option<String>, kid: Option<String>, alg: Option<String>, n: Option<String>, e: Option<String>, }
208
209const ALLOWED_JWT_ALGORITHMS: &[Algorithm] = &[
218 Algorithm::RS256,
219 Algorithm::RS384,
220 Algorithm::RS512,
221 Algorithm::PS256,
222 Algorithm::PS384,
223 Algorithm::PS512,
224];
225const RSA_JWK_KEY_TYPE: &str = "RSA";
226const OAUTH_AUDIENCE_KEY: &str = "audience";
228
229async fn validate_jwt(
230 jwt: &str,
231 jwks_url: &str,
232 issuer: &str,
233 audience: &str,
234 metadata: &HashMap<String, String>,
235) -> Result<bool, BoxedError> {
236 let jwks: Jwks = reqwest::get(jwks_url).await?.json().await?;
237 validate_jwt_with_jwks(jwt, &jwks, issuer, audience, metadata)
238}
239
240fn audience_from_cluster_id(cluster_id: &str) -> String {
241 format!("urn:risingwave:cluster:{}", cluster_id)
242}
243
244fn resolve_oauth_audience(
245 configured: Option<String>,
246 cluster_id: &str,
247) -> Result<String, BoxedError> {
248 let Some(configured_audience) = configured else {
249 return Ok(audience_from_cluster_id(cluster_id));
250 };
251
252 let audience = configured_audience.trim();
253 if audience.is_empty() {
254 return Err(format!("OAuth option `{OAUTH_AUDIENCE_KEY}` cannot be empty").into());
255 }
256 Ok(audience.to_owned())
257}
258
259fn validate_jwt_with_jwks(
260 jwt: &str,
261 jwks: &Jwks,
262 issuer: &str,
263 audience: &str,
264 metadata: &HashMap<String, String>,
265) -> Result<bool, BoxedError> {
266 let header = decode_header(jwt)?;
267
268 let kid = header.kid.ok_or("JWT header missing 'kid' field")?;
270
271 if !ALLOWED_JWT_ALGORITHMS.contains(&header.alg) {
280 return Err(format!("JWT alg {:?} is not allowed", header.alg).into());
281 }
282
283 let jwk = jwks
288 .keys
289 .iter()
290 .filter(|jwk| jwk.kid.as_deref() == Some(kid.as_str()))
291 .find(|jwk| {
292 jwk.kty.as_deref() == Some(RSA_JWK_KEY_TYPE)
293 && jwk.n.is_some()
294 && jwk.e.is_some()
295 && match jwk.alg.as_deref() {
296 Some(alg) => Algorithm::from_str(alg).is_ok_and(|alg| alg == header.alg),
297 None => true,
298 }
299 })
300 .ok_or_else(|| format!("No compatible RSA key found in JWKS for kid: '{}'", kid))?;
301
302 let n = jwk.n.as_deref().expect("RSA candidate must have a modulus");
304 let e = jwk
305 .e
306 .as_deref()
307 .expect("RSA candidate must have an exponent");
308 let decoding_key = DecodingKey::from_rsa_components(n, e)?;
309 let mut validation = Validation::new(header.alg);
310 validation.set_issuer(&[issuer]);
311 validation.set_audience(&[audience]);
312 validation.set_required_spec_claims(&["exp", "iss", "aud"]);
313 let token_data = decode::<HashMap<String, serde_json::Value>>(jwt, &decoding_key, &validation)?;
314
315 if !metadata.iter().all(
317 |(k, v)| matches!(token_data.claims.get(k), Some(serde_json::Value::String(s)) if s == v),
318 ) {
319 return Err("metadata in jwt does not match with metadata declared with user".into());
320 }
321 Ok(true)
322}
323
324impl UserAuthenticator {
325 pub async fn authenticate(&self, password: &[u8]) -> PsqlResult<()> {
326 let success = match self {
327 UserAuthenticator::None => true,
328 UserAuthenticator::ClearText(text) => password == text,
329 UserAuthenticator::Md5WithSalt {
330 encrypted_password, ..
331 } => encrypted_password == password,
332 UserAuthenticator::OAuth {
333 metadata,
334 cluster_id,
335 } => {
336 let mut metadata = metadata.clone();
337 let jwks_url = metadata.remove("jwks_url").unwrap();
338 let issuer = metadata.remove("issuer").unwrap();
339 let audience =
340 resolve_oauth_audience(metadata.remove(OAUTH_AUDIENCE_KEY), cluster_id)
341 .map_err(PsqlError::StartupError)?;
342 validate_jwt(
343 &String::from_utf8_lossy(password),
344 &jwks_url,
345 &issuer,
346 &audience,
347 &metadata,
348 )
349 .await
350 .map_err(PsqlError::StartupError)?
351 }
352 UserAuthenticator::Ldap(user_name, hba_entry) => {
353 let ldap_auth = LdapAuthenticator::new(hba_entry)?;
354 let password_str = String::from_utf8_lossy(password).into_owned();
356 ldap_auth.authenticate(user_name, &password_str).await?
357 }
358 };
359 if !success {
360 return Err(PsqlError::PasswordError);
361 }
362 Ok(())
363 }
364}
365
366pub async fn pg_serve(
370 addr: &str,
371 tcp_keepalive: TcpKeepalive,
372 session_mgr: Arc<impl SessionManager>,
373 context: ConnectionContext,
374 shutdown: CancellationToken,
375) -> Result<(), BoxedError> {
376 let listener = Listener::bind(addr).await?;
377 tracing::info!(addr, "server started");
378
379 let acceptor_runtime = BackgroundShutdownRuntime::from({
380 let mut builder = tokio::runtime::Builder::new_multi_thread();
381 builder.worker_threads(1);
382 builder
383 .thread_name("rw-acceptor")
384 .enable_all()
385 .build()
386 .unwrap()
387 });
388
389 #[cfg(not(madsim))]
390 let worker_runtime = tokio::runtime::Handle::current();
391 #[cfg(madsim)]
392 let worker_runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap();
393 let session_mgr_clone = session_mgr.clone();
394 let f = async move {
395 loop {
396 let conn_ret = listener.accept(&tcp_keepalive).await;
397 match conn_ret {
398 Ok((stream, peer_addr)) => {
399 tracing::info!(%peer_addr, "accept connection");
400 worker_runtime.spawn(handle_connection(
401 stream,
402 session_mgr_clone.clone(),
403 Arc::new(peer_addr),
404 context.clone(),
405 ));
406 }
407
408 Err(e) => {
409 tracing::error!(error = %e.as_report(), "failed to accept connection",);
410 }
411 }
412 }
413 };
414 acceptor_runtime.spawn(f);
415
416 shutdown.cancelled().await;
418
419 drop(acceptor_runtime);
421 session_mgr.shutdown().await;
423
424 Ok(())
425}
426
427pub async fn handle_connection<S, SM>(
428 stream: S,
429 session_mgr: Arc<SM>,
430 peer_addr: AddressRef,
431 context: ConnectionContext,
432) where
433 S: PgByteStream,
434 SM: SessionManager,
435{
436 PgProtocol::new(stream, session_mgr, peer_addr, context)
437 .run()
438 .await;
439}
440#[cfg(test)]
441mod tests {
442 use std::sync::Arc;
443 use std::time::{Duration, Instant};
444
445 use bytes::Bytes;
446 use futures::StreamExt;
447 use futures::stream::BoxStream;
448 use risingwave_common::id::DatabaseId;
449 use risingwave_common::types::DataType;
450 use risingwave_common::util::tokio_util::sync::CancellationToken;
451 use risingwave_sqlparser::ast::Statement;
452 use tokio_postgres::NoTls;
453
454 use crate::error::PsqlResult;
455 use crate::memory_manager::MessageMemoryManager;
456 use crate::pg_field_descriptor::PgFieldDescriptor;
457 use crate::pg_message::TransactionStatus;
458 use crate::pg_protocol::ConnectionContext;
459 use crate::pg_response::{PgResponse, RowSetResult, StatementType};
460 use crate::pg_server::{
461 BoxedError, ExecContext, ExecContextGuard, Session, SessionId, SessionManager,
462 UserAuthenticator, pg_serve,
463 };
464 use crate::types;
465 use crate::types::Row;
466
467 struct MockSessionManager {}
468 struct MockSession {}
469
470 const STREAMING_TEST_QUERY: &str = "SELECT 'pgwire_streaming_test'";
471
472 impl SessionManager for MockSessionManager {
473 type Error = BoxedError;
474 type Session = MockSession;
475
476 fn create_dummy_session(
477 &self,
478 _database_id: DatabaseId,
479 ) -> Result<Arc<Self::Session>, Self::Error> {
480 unimplemented!()
481 }
482
483 fn connect(
484 &self,
485 _database: &str,
486 _user_name: &str,
487 _peer_addr: crate::net::AddressRef,
488 ) -> Result<Arc<Self::Session>, Self::Error> {
489 Ok(Arc::new(MockSession {}))
490 }
491
492 fn cancel_queries_in_session(&self, _session_id: SessionId) {
493 todo!()
494 }
495
496 fn cancel_creating_jobs_in_session(&self, _session_id: SessionId) {
497 todo!()
498 }
499
500 fn end_session(&self, _session: &Self::Session) {}
501 }
502
503 impl Session for MockSession {
504 type Error = BoxedError;
505 type Portal = String;
506 type PreparedStatement = String;
507 type ValuesStream = BoxStream<'static, RowSetResult>;
508
509 async fn run_one_query(
510 self: Arc<Self>,
511 _stmt: Statement,
512 _format: types::Format,
513 ) -> Result<PgResponse<BoxStream<'static, RowSetResult>>, Self::Error> {
514 Ok(PgResponse::builder(StatementType::SELECT)
515 .values(
516 futures::stream::iter(vec![Ok(vec![Row::new(vec![Some(Bytes::new())])])])
517 .boxed(),
518 vec![
519 PgFieldDescriptor::new("".to_owned(), 1043, -1);
522 1
523 ],
524 )
525 .into())
526 }
527
528 async fn parse(
529 self: Arc<Self>,
530 sql: Option<Statement>,
531 _params_types: Vec<Option<DataType>>,
532 ) -> Result<String, Self::Error> {
533 Ok(sql.map(|stmt| stmt.to_string()).unwrap_or_default())
534 }
535
536 fn bind(
537 self: Arc<Self>,
538 prepare_statement: String,
539 _params: Vec<Option<Bytes>>,
540 _param_formats: Vec<types::Format>,
541 _result_formats: Vec<types::Format>,
542 ) -> Result<String, Self::Error> {
543 Ok(prepare_statement)
544 }
545
546 async fn execute(
547 self: Arc<Self>,
548 portal: String,
549 ) -> Result<PgResponse<BoxStream<'static, RowSetResult>>, Self::Error> {
550 if portal == STREAMING_TEST_QUERY {
551 let first_row = futures::stream::once(async {
552 Ok(vec![Row::new(vec![Some(Bytes::from(vec![
553 b'x';
554 128 * 1024
555 ]))])])
556 });
557 let remaining_rows = futures::stream::pending();
558 return Ok(PgResponse::builder(StatementType::SELECT)
559 .values(
560 first_row.chain(remaining_rows).boxed(),
561 vec![PgFieldDescriptor::new("".to_owned(), 1043, -1)],
562 )
563 .into());
564 }
565
566 Ok(PgResponse::builder(StatementType::SELECT)
567 .values(
568 futures::stream::iter(vec![Ok(vec![Row::new(vec![Some(Bytes::new())])])])
569 .boxed(),
570 vec![
571 PgFieldDescriptor::new("".to_owned(), 1043, -1);
574 1
575 ],
576 )
577 .into())
578 }
579
580 fn describe_statement(
581 self: Arc<Self>,
582 _statement: String,
583 ) -> Result<(Vec<DataType>, Vec<PgFieldDescriptor>), Self::Error> {
584 Ok((
585 vec![],
586 vec![PgFieldDescriptor::new("".to_owned(), 1043, -1)],
587 ))
588 }
589
590 fn describe_portal(
591 self: Arc<Self>,
592 _portal: String,
593 ) -> Result<Vec<PgFieldDescriptor>, Self::Error> {
594 Ok(vec![PgFieldDescriptor::new("".to_owned(), 1043, -1)])
595 }
596
597 fn user_authenticator(&self) -> &UserAuthenticator {
598 &UserAuthenticator::None
599 }
600
601 fn id(&self) -> SessionId {
602 (0, 0)
603 }
604
605 fn get_config(&self, key: &str) -> Result<String, Self::Error> {
606 match key {
607 "timezone" => Ok("UTC".to_owned()),
608 _ => Err(format!("Unknown config key: {key}").into()),
609 }
610 }
611
612 fn set_config(&self, _key: &str, _value: String) -> Result<String, Self::Error> {
613 Ok("".to_owned())
614 }
615
616 async fn next_notice(self: &Arc<Self>) -> String {
617 std::future::pending().await
618 }
619
620 fn transaction_status(&self) -> TransactionStatus {
621 TransactionStatus::Idle
622 }
623
624 fn init_exec_context(&self, sql: Arc<str>) -> ExecContextGuard {
625 let exec_context = Arc::new(ExecContext {
626 running_sql: sql,
627 last_instant: Instant::now(),
628 last_idle_instant: Default::default(),
629 });
630 ExecContextGuard::new(exec_context)
631 }
632
633 fn check_idle_in_transaction_timeout(&self) -> PsqlResult<()> {
634 Ok(())
635 }
636
637 fn user(&self) -> String {
638 "mock".to_owned()
639 }
640 }
641
642 async fn do_test_query(bind_addr: impl Into<String>, pg_config: impl Into<String>) {
643 let bind_addr = bind_addr.into();
644 let pg_config = pg_config.into();
645
646 let session_mgr = MockSessionManager {};
647 tokio::spawn(async move {
648 pg_serve(
649 &bind_addr,
650 socket2::TcpKeepalive::new(),
651 Arc::new(session_mgr),
652 ConnectionContext {
653 tls_config: None,
654 sql_option_redaction: None,
655 message_memory_manager: MessageMemoryManager::new(u64::MAX, u64::MAX, u64::MAX)
656 .into(),
657 stream_flush_threshold_bytes: 64 * 1024,
658 },
659 CancellationToken::new(), )
661 .await
662 });
663 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
665
666 let (client, connection) = tokio_postgres::connect(&pg_config, NoTls).await.unwrap();
668
669 tokio::spawn(async move {
672 if let Err(e) = connection.await {
673 eprintln!("connection error: {}", e);
674 }
675 });
676
677 let rows = client
678 .simple_query("SELECT ''")
679 .await
680 .expect("Error executing query");
681 assert_eq!(rows.len(), 2);
683
684 let rows = client
685 .query("SELECT ''", &[])
686 .await
687 .expect("Error executing query");
688 assert_eq!(rows.len(), 1);
689 }
690
691 #[tokio::test]
692 async fn test_query_tcp() {
693 do_test_query("127.0.0.1:10000", "host=localhost port=10000").await;
694 }
695
696 #[cfg(not(madsim))]
697 #[tokio::test]
698 async fn test_query_unix() {
699 let port: i16 = 10000;
700 let dir = tempfile::TempDir::new().unwrap();
701 let sock = dir.path().join(format!(".s.PGSQL.{port}"));
702
703 do_test_query(
704 format!("unix:{}", sock.to_str().unwrap()),
705 format!("host={} port={}", dir.path().to_str().unwrap(), port),
706 )
707 .await;
708 }
709
710 #[cfg(not(madsim))]
711 #[tokio::test]
712 async fn test_large_query_row_is_streamed_before_query_finishes() {
713 let port: i16 = 10001;
714 let dir = tempfile::TempDir::new().unwrap();
715 let sock = dir.path().join(format!(".s.PGSQL.{port}"));
716 let bind_addr = format!("unix:{}", sock.to_str().unwrap());
717 let pg_config = format!("host={} port={port}", dir.path().to_str().unwrap());
718 let cancellation = CancellationToken::new();
719
720 let server_cancellation = cancellation.clone();
721 tokio::spawn(async move {
722 pg_serve(
723 &bind_addr,
724 socket2::TcpKeepalive::new(),
725 Arc::new(MockSessionManager {}),
726 ConnectionContext {
727 tls_config: None,
728 sql_option_redaction: None,
729 message_memory_manager: MessageMemoryManager::new(u64::MAX, u64::MAX, u64::MAX)
730 .into(),
731 stream_flush_threshold_bytes: 64 * 1024,
732 },
733 server_cancellation,
734 )
735 .await
736 });
737 tokio::time::sleep(Duration::from_millis(100)).await;
738
739 let (client, connection) = tokio_postgres::connect(&pg_config, NoTls).await.unwrap();
740 let connection_handle = tokio::spawn(connection);
741 let params: &[&str] = &[];
742 let rows = client
743 .query_raw(STREAMING_TEST_QUERY, params)
744 .await
745 .unwrap();
746 futures::pin_mut!(rows);
747
748 let first_row = tokio::time::timeout(Duration::from_secs(5), rows.next())
749 .await
750 .expect("first row should be sent before the result stream finishes")
751 .unwrap()
752 .unwrap();
753 assert_eq!(first_row.get::<_, &str>(0).len(), 128 * 1024);
754
755 drop(client);
756 cancellation.cancel();
757 connection_handle.abort();
758 }
759
760 mod jwt_validation_tests {
761 use std::collections::HashMap;
762 use std::time::{SystemTime, UNIX_EPOCH};
763
764 use base64::Engine;
765 use jsonwebtoken::{Algorithm, EncodingKey, Header};
766 use openssl::pkey::{Private, Public};
767 use openssl::rsa::Rsa;
768 use serde_json::json;
769
770 use crate::pg_server::{Jwk, Jwks, resolve_oauth_audience, validate_jwt_with_jwks};
771
772 fn create_test_rsa_keys() -> (Rsa<Private>, Rsa<Public>) {
773 let private_key = Rsa::generate(2048).expect("failed to generate a key");
774 let public_key = Rsa::from_public_components(
775 private_key.n().to_owned().unwrap(),
776 private_key.e().to_owned().unwrap(),
777 )
778 .unwrap();
779 (private_key, public_key)
780 }
781
782 fn create_test_jwks(public_key: &Rsa<Public>, kid: &str, alg: Option<&str>) -> Jwks {
783 let n =
784 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(public_key.n().to_vec());
785 let e =
786 base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(public_key.e().to_vec());
787
788 Jwks {
789 keys: vec![Jwk {
790 kty: Some("RSA".to_owned()),
791 kid: Some(kid.to_owned()),
792 alg: alg.map(ToOwned::to_owned),
793 n: Some(n),
794 e: Some(e),
795 }],
796 }
797 }
798
799 fn create_jwt_token(
800 private_key: &Rsa<Private>,
801 kid: &str,
802 algorithm: Algorithm,
803 issuer: &str,
804 audience: Option<&str>,
805 exp: u64,
806 additional_claims: HashMap<String, serde_json::Value>,
807 ) -> String {
808 let mut header = Header::new(algorithm);
809 header.kid = Some(kid.to_owned());
810
811 let mut claims = json!({
812 "iss": issuer,
813 "exp": exp,
814 });
815
816 if let Some(aud) = audience {
817 claims["aud"] = json!(aud);
818 }
819
820 for (key, value) in additional_claims {
821 claims[key] = value;
822 }
823
824 let encoding_key =
825 EncodingKey::from_rsa_pem(&private_key.private_key_to_pem().unwrap()).unwrap();
826
827 jsonwebtoken::encode(&header, &claims, &encoding_key).unwrap()
828 }
829
830 fn get_future_timestamp() -> u64 {
831 SystemTime::now()
832 .duration_since(UNIX_EPOCH)
833 .unwrap()
834 .as_secs()
835 + 3600 }
837
838 fn get_past_timestamp() -> u64 {
839 SystemTime::now()
840 .duration_since(UNIX_EPOCH)
841 .unwrap()
842 .as_secs()
843 - 3600 }
845
846 #[test]
847 fn test_jwt_with_invalid_audience() {
848 let (private_key, public_key) = create_test_rsa_keys();
849 let jwks = create_test_jwks(&public_key, "test-kid", Some("RS256"));
850
851 let metadata = HashMap::new();
852
853 let jwt = create_jwt_token(
854 &private_key,
855 "test-kid",
856 Algorithm::RS256,
857 "https://test-issuer.com",
858 Some("urn:risingwave:cluster:wrong-cluster-id"),
859 get_future_timestamp(),
860 HashMap::new(),
861 );
862
863 let result = validate_jwt_with_jwks(
864 &jwt,
865 &jwks,
866 "https://test-issuer.com",
867 "urn:risingwave:cluster:test-cluster-id",
868 &metadata,
869 );
870
871 let error = result.unwrap_err();
872 assert!(error.to_string().contains("InvalidAudience"));
873 }
874
875 #[test]
876 fn test_jwt_with_configured_audience() {
877 let (private_key, public_key) = create_test_rsa_keys();
878 let jwks = create_test_jwks(&public_key, "test-kid", Some("RS256"));
879
880 let mut additional_claims = HashMap::new();
881 additional_claims.insert("aud".to_owned(), json!(["urn:sn:cloud:o-for6u"]));
882 let jwt = create_jwt_token(
883 &private_key,
884 "test-kid",
885 Algorithm::RS256,
886 "https://test-issuer.com",
887 None,
888 get_future_timestamp(),
889 additional_claims,
890 );
891
892 let result = validate_jwt_with_jwks(
893 &jwt,
894 &jwks,
895 "https://test-issuer.com",
896 "urn:sn:cloud:o-for6u",
897 &HashMap::new(),
898 );
899
900 assert!(result.unwrap());
901 }
902
903 #[test]
904 fn test_empty_configured_audience() {
905 let error =
906 resolve_oauth_audience(Some(" ".to_owned()), "test-cluster-id").unwrap_err();
907 assert!(error.to_string().contains("cannot be empty"));
908 }
909
910 #[test]
911 fn test_default_audience_from_cluster_id() {
912 let audience = resolve_oauth_audience(None, "test-cluster-id").unwrap();
913 assert_eq!(audience, "urn:risingwave:cluster:test-cluster-id");
914 }
915
916 #[test]
917 fn test_jwt_with_missing_audience() {
918 let (private_key, public_key) = create_test_rsa_keys();
919 let jwks = create_test_jwks(&public_key, "test-kid", Some("RS256"));
920
921 let metadata = HashMap::new();
922
923 let jwt = create_jwt_token(
924 &private_key,
925 "test-kid",
926 Algorithm::RS256,
927 "https://test-issuer.com",
928 None, get_future_timestamp(),
930 HashMap::new(),
931 );
932
933 let result = validate_jwt_with_jwks(
934 &jwt,
935 &jwks,
936 "https://test-issuer.com",
937 "urn:risingwave:cluster:test-cluster-id",
938 &metadata,
939 );
940
941 let error = result.unwrap_err();
942 assert!(error.to_string().contains("Missing required claim: aud"));
943 }
944
945 #[test]
946 fn test_jwt_with_invalid_issuer() {
947 let (private_key, public_key) = create_test_rsa_keys();
948 let jwks = create_test_jwks(&public_key, "test-kid", Some("RS256"));
949
950 let metadata = HashMap::new();
951
952 let jwt = create_jwt_token(
953 &private_key,
954 "test-kid",
955 Algorithm::RS256,
956 "https://wrong-issuer.com",
957 Some("urn:risingwave:cluster:test-cluster-id"),
958 get_future_timestamp(),
959 HashMap::new(),
960 );
961
962 let result = validate_jwt_with_jwks(
963 &jwt,
964 &jwks,
965 "https://test-issuer.com",
966 "urn:risingwave:cluster:test-cluster-id",
967 &metadata,
968 );
969
970 let error = result.unwrap_err();
971 assert!(error.to_string().contains("InvalidIssuer"));
972 }
973
974 #[test]
975 fn test_jwt_with_kid_not_found_in_jwks() {
976 let (private_key, public_key) = create_test_rsa_keys();
977 let jwks = create_test_jwks(&public_key, "different-kid", Some("RS256"));
978
979 let metadata = HashMap::new();
980
981 let jwt = create_jwt_token(
982 &private_key,
983 "missing-kid",
984 Algorithm::RS256,
985 "https://test-issuer.com",
986 Some("urn:risingwave:cluster:test-cluster-id"),
987 get_future_timestamp(),
988 HashMap::new(),
989 );
990
991 let result = validate_jwt_with_jwks(
992 &jwt,
993 &jwks,
994 "https://test-issuer.com",
995 "urn:risingwave:cluster:test-cluster-id",
996 &metadata,
997 );
998
999 let error = result.unwrap_err();
1000 assert!(
1001 error
1002 .to_string()
1003 .contains("No compatible RSA key found in JWKS for kid: 'missing-kid'")
1004 );
1005 }
1006
1007 #[test]
1008 fn test_jwt_with_empty_jwks_reports_no_matching_key() {
1009 let (private_key, _) = create_test_rsa_keys();
1010 let jwks = Jwks { keys: vec![] };
1011
1012 let jwt = create_jwt_token(
1013 &private_key,
1014 "missing-kid",
1015 Algorithm::RS256,
1016 "https://test-issuer.com",
1017 Some("urn:risingwave:cluster:test-cluster-id"),
1018 get_future_timestamp(),
1019 HashMap::new(),
1020 );
1021
1022 let result = validate_jwt_with_jwks(
1023 &jwt,
1024 &jwks,
1025 "https://test-issuer.com",
1026 "urn:risingwave:cluster:test-cluster-id",
1027 &HashMap::new(),
1028 );
1029
1030 let error = result.unwrap_err();
1031 assert!(
1032 error
1033 .to_string()
1034 .contains("No compatible RSA key found in JWKS for kid: 'missing-kid'")
1035 );
1036 }
1037
1038 #[test]
1039 fn test_jwt_with_expired_token() {
1040 let (private_key, public_key) = create_test_rsa_keys();
1041 let jwks = create_test_jwks(&public_key, "test-kid", Some("RS256"));
1042
1043 let metadata = HashMap::new();
1044
1045 let jwt = create_jwt_token(
1046 &private_key,
1047 "test-kid",
1048 Algorithm::RS256,
1049 "https://test-issuer.com",
1050 Some("urn:risingwave:cluster:test-cluster-id"),
1051 get_past_timestamp(), HashMap::new(),
1053 );
1054
1055 let result = validate_jwt_with_jwks(
1056 &jwt,
1057 &jwks,
1058 "https://test-issuer.com",
1059 "urn:risingwave:cluster:test-cluster-id",
1060 &metadata,
1061 );
1062
1063 let error = result.unwrap_err();
1064 assert!(error.to_string().contains("ExpiredSignature"));
1065 }
1066
1067 #[test]
1068 fn test_jwt_with_invalid_signature() {
1069 let (_, public_key) = create_test_rsa_keys();
1070 let (wrong_private_key, _) = create_test_rsa_keys(); let jwks = create_test_jwks(&public_key, "test-kid", Some("RS256"));
1072
1073 let metadata = HashMap::new();
1074
1075 let jwt = create_jwt_token(
1077 &wrong_private_key,
1078 "test-kid",
1079 Algorithm::RS256,
1080 "https://test-issuer.com",
1081 Some("urn:risingwave:cluster:test-cluster-id"),
1082 get_future_timestamp(),
1083 HashMap::new(),
1084 );
1085
1086 let result = validate_jwt_with_jwks(
1087 &jwt,
1088 &jwks,
1089 "https://test-issuer.com",
1090 "urn:risingwave:cluster:test-cluster-id",
1091 &metadata,
1092 );
1093
1094 let error = result.unwrap_err();
1095 assert!(error.to_string().contains("InvalidSignature"));
1096 }
1097
1098 #[test]
1099 fn test_metadata_validation_success() {
1100 let (private_key, public_key) = create_test_rsa_keys();
1101 let jwks = create_test_jwks(&public_key, "test-kid", Some("RS256"));
1102
1103 let mut metadata = HashMap::new();
1104 metadata.insert("role".to_owned(), "admin".to_owned());
1105 metadata.insert("department".to_owned(), "security".to_owned());
1106
1107 let mut claims = HashMap::new();
1108 claims.insert("role".to_owned(), json!("admin"));
1109 claims.insert("department".to_owned(), json!("security"));
1110 claims.insert("extra_claim".to_owned(), json!("ignored")); let jwt = create_jwt_token(
1113 &private_key,
1114 "test-kid",
1115 Algorithm::RS256,
1116 "https://test-issuer.com",
1117 Some("urn:risingwave:cluster:test-cluster-id"),
1118 get_future_timestamp(),
1119 claims,
1120 );
1121
1122 let result = validate_jwt_with_jwks(
1123 &jwt,
1124 &jwks,
1125 "https://test-issuer.com",
1126 "urn:risingwave:cluster:test-cluster-id",
1127 &metadata,
1128 );
1129
1130 assert!(result.unwrap());
1131 }
1132
1133 #[test]
1134 fn test_metadata_validation_failure() {
1135 let (private_key, public_key) = create_test_rsa_keys();
1136 let jwks = create_test_jwks(&public_key, "test-kid", Some("RS256"));
1137
1138 let mut metadata = HashMap::new();
1139 metadata.insert("role".to_owned(), "admin".to_owned());
1140 metadata.insert("department".to_owned(), "security".to_owned());
1141
1142 let mut claims = HashMap::new();
1143 claims.insert("role".to_owned(), json!("user")); claims.insert("department".to_owned(), json!("security"));
1145
1146 let jwt = create_jwt_token(
1147 &private_key,
1148 "test-kid",
1149 Algorithm::RS256,
1150 "https://test-issuer.com",
1151 Some("urn:risingwave:cluster:test-cluster-id"),
1152 get_future_timestamp(),
1153 claims,
1154 );
1155
1156 let result = validate_jwt_with_jwks(
1157 &jwt,
1158 &jwks,
1159 "https://test-issuer.com",
1160 "urn:risingwave:cluster:test-cluster-id",
1161 &metadata,
1162 );
1163
1164 let error = result.unwrap_err();
1165 assert_eq!(
1166 error.to_string(),
1167 "metadata in jwt does not match with metadata declared with user"
1168 );
1169 }
1170
1171 #[test]
1172 fn test_jwt_with_jwk_missing_alg_succeeds() {
1173 let (private_key, public_key) = create_test_rsa_keys();
1174 let jwks = create_test_jwks(&public_key, "test-kid", None);
1175
1176 let jwt = create_jwt_token(
1177 &private_key,
1178 "test-kid",
1179 Algorithm::RS256,
1180 "https://test-issuer.com",
1181 Some("urn:risingwave:cluster:test-cluster-id"),
1182 get_future_timestamp(),
1183 HashMap::new(),
1184 );
1185
1186 let result = validate_jwt_with_jwks(
1187 &jwt,
1188 &jwks,
1189 "https://test-issuer.com",
1190 "urn:risingwave:cluster:test-cluster-id",
1191 &HashMap::new(),
1192 );
1193
1194 assert!(result.unwrap());
1195 }
1196
1197 #[test]
1198 fn test_jwt_with_mixed_jwks_ignores_non_matching_ec_key() {
1199 let (private_key, public_key) = create_test_rsa_keys();
1200 let mut jwks = create_test_jwks(&public_key, "rsa-kid", Some("RS256"));
1201 jwks.keys.push(Jwk {
1202 kty: Some("EC".to_owned()),
1203 kid: Some("ec-kid".to_owned()),
1204 alg: Some("ES256".to_owned()),
1205 n: None,
1206 e: None,
1207 });
1208
1209 let jwt = create_jwt_token(
1210 &private_key,
1211 "rsa-kid",
1212 Algorithm::RS256,
1213 "https://test-issuer.com",
1214 Some("urn:risingwave:cluster:test-cluster-id"),
1215 get_future_timestamp(),
1216 HashMap::new(),
1217 );
1218
1219 let result = validate_jwt_with_jwks(
1220 &jwt,
1221 &jwks,
1222 "https://test-issuer.com",
1223 "urn:risingwave:cluster:test-cluster-id",
1224 &HashMap::new(),
1225 );
1226
1227 assert!(result.unwrap());
1228 }
1229
1230 #[test]
1231 fn test_jwks_deserializes_ec_key_without_kid() {
1232 let jwks: Jwks = serde_json::from_value(json!({
1233 "keys": [
1234 {
1235 "alg": "ES256",
1236 "kty": "EC",
1237 "crv": "P-256",
1238 "x": "x-coordinate",
1239 "y": "y-coordinate"
1240 },
1241 {
1242 "kid": "rsa-kid",
1243 "alg": "RS256",
1244 "kty": "RSA",
1245 "n": "modulus",
1246 "e": "AQAB"
1247 }
1248 ]
1249 }))
1250 .unwrap();
1251
1252 assert_eq!(jwks.keys.len(), 2);
1253 assert!(jwks.keys[0].kid.is_none());
1254 assert_eq!(jwks.keys[1].kid.as_deref(), Some("rsa-kid"));
1255 assert_eq!(jwks.keys[1].n.as_deref(), Some("modulus"));
1256 }
1257
1258 #[test]
1259 fn test_jwt_uses_compatible_rsa_key_when_ec_key_has_same_kid() {
1260 let (private_key, public_key) = create_test_rsa_keys();
1261 let mut jwks = create_test_jwks(&public_key, "shared-kid", Some("RS256"));
1262 jwks.keys.insert(
1263 0,
1264 Jwk {
1265 kty: Some("EC".to_owned()),
1266 kid: Some("shared-kid".to_owned()),
1267 alg: Some("ES256".to_owned()),
1268 n: None,
1269 e: None,
1270 },
1271 );
1272
1273 let jwt = create_jwt_token(
1274 &private_key,
1275 "shared-kid",
1276 Algorithm::RS256,
1277 "https://test-issuer.com",
1278 Some("urn:risingwave:cluster:test-cluster-id"),
1279 get_future_timestamp(),
1280 HashMap::new(),
1281 );
1282
1283 let result = validate_jwt_with_jwks(
1284 &jwt,
1285 &jwks,
1286 "https://test-issuer.com",
1287 "urn:risingwave:cluster:test-cluster-id",
1288 &HashMap::new(),
1289 );
1290
1291 assert!(result.unwrap());
1292 }
1293
1294 #[test]
1295 fn test_jwt_with_jwk_missing_alg_rejects_disallowed_header_alg() {
1296 let (_, public_key) = create_test_rsa_keys();
1297 let jwks = create_test_jwks(&public_key, "test-kid", None);
1298
1299 let mut header = Header::new(Algorithm::HS256);
1304 header.kid = Some("test-kid".to_owned());
1305 let claims = json!({
1306 "iss": "https://test-issuer.com",
1307 "aud": "urn:risingwave:cluster:test-cluster-id",
1308 "exp": get_future_timestamp(),
1309 });
1310 let jwt = jsonwebtoken::encode(
1311 &header,
1312 &claims,
1313 &EncodingKey::from_secret(b"attacker-chosen"),
1314 )
1315 .unwrap();
1316
1317 let result = validate_jwt_with_jwks(
1318 &jwt,
1319 &jwks,
1320 "https://test-issuer.com",
1321 "urn:risingwave:cluster:test-cluster-id",
1322 &HashMap::new(),
1323 );
1324
1325 let error = result.unwrap_err();
1326 assert!(
1327 error.to_string().contains("is not allowed"),
1328 "unexpected error: {}",
1329 error
1330 );
1331 }
1332
1333 #[test]
1334 fn test_jwt_alg_mismatch_between_header_and_jwk() {
1335 let (private_key, public_key) = create_test_rsa_keys();
1336 let jwks = create_test_jwks(&public_key, "test-kid", Some("RS384"));
1338
1339 let jwt = create_jwt_token(
1340 &private_key,
1341 "test-kid",
1342 Algorithm::RS256,
1343 "https://test-issuer.com",
1344 Some("urn:risingwave:cluster:test-cluster-id"),
1345 get_future_timestamp(),
1346 HashMap::new(),
1347 );
1348
1349 let result = validate_jwt_with_jwks(
1350 &jwt,
1351 &jwks,
1352 "https://test-issuer.com",
1353 "urn:risingwave:cluster:test-cluster-id",
1354 &HashMap::new(),
1355 );
1356
1357 let error = result.unwrap_err();
1358 assert_eq!(
1359 error.to_string(),
1360 "No compatible RSA key found in JWKS for kid: 'test-kid'"
1361 );
1362 }
1363 }
1364}