Skip to content

Commit 1a472e1

Browse files
Implement trust authentication
1 parent c9270a4 commit 1a472e1

File tree

5 files changed

+162
-104
lines changed

5 files changed

+162
-104
lines changed

pgcat.minimal.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ admin_password = "pgcat"
1111
[pools.pgml.users.0]
1212
username = "postgres"
1313
password = "postgres"
14+
auth_type = "md5"
1415
pool_size = 10
1516
min_pool_size = 1
1617
pool_mode = "transaction"

pgcat.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,8 @@ result = [
274274
# if `server_username` is not set.
275275
username = "sharding_user"
276276

277+
auth_type = "md5"
278+
277279
# PostgreSQL password used to authenticate the user and connect to the server
278280
# if `server_password` is not set.
279281
password = "sharding_user"
@@ -299,6 +301,7 @@ statement_timeout = 0
299301
[pools.sharded_db.users.1]
300302
username = "other_user"
301303
password = "other_user"
304+
auth_type = "md5"
302305
pool_size = 21
303306
statement_timeout = 15000
304307
connect_timeout = 1000
@@ -337,6 +340,7 @@ sharding_function = "pg_bigint_hash"
337340
[pools.simple_db.users.0]
338341
username = "simple_user"
339342
password = "simple_user"
343+
auth_type = "md5"
340344
pool_size = 5
341345
min_pool_size = 3
342346
server_lifetime = 60000

src/auth_passthrough.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ impl AuthPassthrough {
7171
pub async fn fetch_hash(&self, address: &crate::config::Address) -> Result<String, Error> {
7272
let auth_user = crate::config::User {
7373
username: self.user.clone(),
74+
auth_type: "".to_string(),
7475
password: Some(self.password.clone()),
7576
server_username: None,
7677
server_password: None,

src/client.rs

Lines changed: 154 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -481,54 +481,57 @@ where
481481
let process_id: i32 = rand::random();
482482
let secret_key: i32 = rand::random();
483483

484-
// Perform MD5 authentication.
485-
// TODO: Add SASL support.
486-
let salt = md5_challenge(&mut write).await?;
484+
let mut prepared_statements_enabled = false;
487485

488-
let code = match read.read_u8().await {
489-
Ok(p) => p,
490-
Err(_) => {
491-
return Err(Error::ClientSocketError(
492-
"password code".into(),
493-
client_identifier,
494-
))
495-
}
496-
};
486+
// Authenticate admin user.
487+
let (transaction_mode, mut server_parameters) = if admin {
497488

498-
// PasswordMessage
499-
if code as char != 'p' {
500-
return Err(Error::ProtocolSyncError(format!(
501-
"Expected p, got {}",
502-
code as char
503-
)));
504-
}
505489

506-
let len = match read.read_i32().await {
507-
Ok(len) => len,
508-
Err(_) => {
509-
return Err(Error::ClientSocketError(
510-
"password message length".into(),
511-
client_identifier,
512-
))
513-
}
514-
};
490+
// Perform MD5 authentication.
491+
// TODO: Add SASL support.
492+
let salt = md5_challenge(&mut write).await?;
515493

516-
let mut password_response = vec![0u8; (len - 4) as usize];
494+
let code = match read.read_u8().await {
495+
Ok(p) => p,
496+
Err(_) => {
497+
return Err(Error::ClientSocketError(
498+
"password code".into(),
499+
client_identifier,
500+
))
501+
}
502+
};
517503

518-
match read.read_exact(&mut password_response).await {
519-
Ok(_) => (),
520-
Err(_) => {
521-
return Err(Error::ClientSocketError(
522-
"password message".into(),
523-
client_identifier,
524-
))
504+
// PasswordMessage
505+
if code as char != 'p' {
506+
return Err(Error::ProtocolSyncError(format!(
507+
"Expected p, got {}",
508+
code as char
509+
)));
525510
}
526-
};
527511

528-
let mut prepared_statements_enabled = false;
512+
let len = match read.read_i32().await {
513+
Ok(len) => len,
514+
Err(_) => {
515+
return Err(Error::ClientSocketError(
516+
"password message length".into(),
517+
client_identifier,
518+
))
519+
}
520+
};
521+
522+
let mut password_response = vec![0u8; (len - 4) as usize];
523+
524+
match read.read_exact(&mut password_response).await {
525+
Ok(_) => (),
526+
Err(_) => {
527+
return Err(Error::ClientSocketError(
528+
"password message".into(),
529+
client_identifier,
530+
))
531+
}
532+
};
533+
529534

530-
// Authenticate admin user.
531-
let (transaction_mode, mut server_parameters) = if admin {
532535
let config = get_config();
533536

534537
// Compare server and client hashes.
@@ -573,89 +576,136 @@ where
573576
// Obtain the hash to compare, we give preference to that written in cleartext in config
574577
// if there is nothing set in cleartext and auth passthrough (auth_query) is configured, we use the hash obtained
575578
// when the pool was created. If there is no hash there, we try to fetch it one more time.
576-
let password_hash = if let Some(password) = &pool.settings.user.password {
577-
Some(md5_hash_password(username, password, &salt))
578-
} else {
579-
if !get_config().is_auth_query_configured() {
580-
wrong_password(&mut write, username).await?;
581-
return Err(Error::ClientAuthImpossible(username.into()));
579+
if let "md5" = pool.settings.user.auth_type.as_str() {
580+
// Perform MD5 authentication.
581+
// TODO: Add SASL support.
582+
let salt = md5_challenge(&mut write).await?;
583+
584+
let code = match read.read_u8().await {
585+
Ok(p) => p,
586+
Err(_) => {
587+
return Err(Error::ClientSocketError(
588+
"password code".into(),
589+
client_identifier,
590+
))
591+
}
592+
};
593+
594+
// PasswordMessage
595+
if code as char != 'p' {
596+
return Err(Error::ProtocolSyncError(format!(
597+
"Expected p, got {}",
598+
code as char
599+
)));
582600
}
583601

584-
let mut hash = (*pool.auth_hash.read()).clone();
602+
let len = match read.read_i32().await {
603+
Ok(len) => len,
604+
Err(_) => {
605+
return Err(Error::ClientSocketError(
606+
"password message length".into(),
607+
client_identifier,
608+
))
609+
}
610+
};
585611

586-
if hash.is_none() {
587-
warn!(
588-
"Query auth configured \
589-
but no hash password found \
590-
for pool {}. Will try to refetch it.",
591-
pool_name
592-
);
612+
let mut password_response = vec![0u8; (len - 4) as usize];
593613

594-
match refetch_auth_hash(&pool).await {
595-
Ok(fetched_hash) => {
596-
warn!("Password for {}, obtained. Updating.", client_identifier);
614+
match read.read_exact(&mut password_response).await {
615+
Ok(_) => (),
616+
Err(_) => {
617+
return Err(Error::ClientSocketError(
618+
"password message".into(),
619+
client_identifier,
620+
))
621+
}
622+
};
597623

598-
{
599-
let mut pool_auth_hash = pool.auth_hash.write();
600-
*pool_auth_hash = Some(fetched_hash.clone());
601-
}
624+
let password_hash = if let Some(password) = &pool.settings.user.password {
602625

603-
hash = Some(fetched_hash);
604-
}
626+
Some(md5_hash_password(username, password, &salt))
627+
} else {
628+
if !get_config().is_auth_query_configured() {
629+
wrong_password(&mut write, username).await?;
630+
return Err(Error::ClientAuthImpossible(username.into()));
631+
}
605632

606-
Err(err) => {
607-
wrong_password(&mut write, username).await?;
633+
let mut hash = (*pool.auth_hash.read()).clone();
608634

609-
return Err(Error::ClientAuthPassthroughError(
610-
err.to_string(),
611-
client_identifier,
612-
));
613-
}
614-
}
615-
};
635+
if hash.is_none() {
636+
warn!(
637+
"Query auth configured \
638+
but no hash password found \
639+
for pool {}. Will try to refetch it.",
640+
pool_name
641+
);
616642

617-
Some(md5_hash_second_pass(&hash.unwrap(), &salt))
618-
};
643+
match refetch_auth_hash(&pool).await {
644+
Ok(fetched_hash) => {
645+
warn!("Password for {}, obtained. Updating.", client_identifier);
619646

620-
// Once we have the resulting hash, we compare with what the client gave us.
621-
// If they do not match and auth query is set up, we try to refetch the hash one more time
622-
// to see if the password has changed since the pool was created.
623-
//
624-
// @TODO: we could end up fetching again the same password twice (see above).
625-
if password_hash.unwrap() != password_response {
626-
warn!(
627-
"Invalid password {}, will try to refetch it.",
628-
client_identifier
629-
);
647+
{
648+
let mut pool_auth_hash = pool.auth_hash.write();
649+
*pool_auth_hash = Some(fetched_hash.clone());
650+
}
630651

631-
let fetched_hash = match refetch_auth_hash(&pool).await {
632-
Ok(fetched_hash) => fetched_hash,
633-
Err(err) => {
634-
wrong_password(&mut write, username).await?;
652+
hash = Some(fetched_hash);
653+
}
635654

636-
return Err(err);
637-
}
638-
};
655+
Err(err) => {
656+
wrong_password(&mut write, username).await?;
639657

640-
let new_password_hash = md5_hash_second_pass(&fetched_hash, &salt);
658+
return Err(Error::ClientAuthPassthroughError(
659+
err.to_string(),
660+
client_identifier,
661+
));
662+
}
663+
}
664+
};
641665

642-
// Ok password changed in server an auth is possible.
643-
if new_password_hash == password_response {
666+
Some(md5_hash_second_pass(&hash.unwrap(), &salt))
667+
};
668+
669+
// Once we have the resulting hash, we compare with what the client gave us.
670+
// If they do not match and auth query is set up, we try to refetch the hash one more time
671+
// to see if the password has changed since the pool was created.
672+
//
673+
// @TODO: we could end up fetching again the same password twice (see above).
674+
if password_hash.unwrap() != password_response {
644675
warn!(
645-
"Password for {}, changed in server. Updating.",
676+
"Invalid password {}, will try to refetch it.",
646677
client_identifier
647678
);
648679

649-
{
650-
let mut pool_auth_hash = pool.auth_hash.write();
651-
*pool_auth_hash = Some(fetched_hash);
680+
let fetched_hash = match refetch_auth_hash(&pool).await {
681+
Ok(fetched_hash) => fetched_hash,
682+
Err(err) => {
683+
wrong_password(&mut write, username).await?;
684+
685+
return Err(err);
686+
}
687+
};
688+
689+
let new_password_hash = md5_hash_second_pass(&fetched_hash, &salt);
690+
691+
// Ok password changed in server an auth is possible.
692+
if new_password_hash == password_response {
693+
warn!(
694+
"Password for {}, changed in server. Updating.",
695+
client_identifier
696+
);
697+
698+
{
699+
let mut pool_auth_hash = pool.auth_hash.write();
700+
*pool_auth_hash = Some(fetched_hash);
701+
}
702+
} else {
703+
wrong_password(&mut write, username).await?;
704+
return Err(Error::ClientGeneralError(
705+
"Invalid password".into(),
706+
client_identifier,
707+
));
652708
}
653-
} else {
654-
wrong_password(&mut write, username).await?;
655-
return Err(Error::ClientGeneralError(
656-
"Invalid password".into(),
657-
client_identifier,
658-
));
659709
}
660710
}
661711

src/config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ impl Address {
208208
pub struct User {
209209
pub username: String,
210210
pub password: Option<String>,
211+
pub auth_type: String,
211212
pub server_username: Option<String>,
212213
pub server_password: Option<String>,
213214
pub pool_size: u32,
@@ -225,6 +226,7 @@ impl Default for User {
225226
User {
226227
username: String::from("postgres"),
227228
password: None,
229+
auth_type: "md5".to_string(),
228230
server_username: None,
229231
server_password: None,
230232
pool_size: 15,

0 commit comments

Comments
 (0)