diff --git a/libs/git/ssh/authz.rs b/libs/git/ssh/authz.rs index c137ea6..62d28d4 100644 --- a/libs/git/ssh/authz.rs +++ b/libs/git/ssh/authz.rs @@ -14,6 +14,12 @@ pub struct SshAuthService { db: AppDatabase, } +pub struct SshKeyUser { + pub user: user::Model, + pub key_id: i64, + pub key_title: String, +} + impl SshAuthService { pub fn new(db: AppDatabase) -> Self { Self { db } @@ -97,7 +103,7 @@ impl SshAuthService { pub async fn find_user_by_public_key( &self, public_key_str: &str, - ) -> Result, DbErr> { + ) -> Result, DbErr> { let fingerprint = match self.generate_fingerprint_from_public_key(public_key_str) { Ok(fp) => fp, Err(e) => { @@ -144,16 +150,20 @@ impl SshAuthService { .one(self.db.reader()) .await?; - if let Some(ref user) = user_model { + if let Some(user) = user_model { tracing::info!( - "user authenticated via SSH key user={} key={}", + "SSH key matched user={} key={}", user.username, ssh_key.title ); - self.update_key_last_used_async(ssh_key.id); + return Ok(Some(SshKeyUser { + user, + key_id: ssh_key.id, + key_title: ssh_key.title, + })); } - Ok(user_model) + Ok(None) } fn is_key_expired(&self, ssh_key: &user_ssh_key::Model) -> bool { @@ -165,7 +175,7 @@ impl SshAuthService { } } - fn update_key_last_used_async(&self, key_id: i64) { + pub fn update_key_last_used_async(&self, key_id: i64) { let db_clone = self.db.clone(); tokio::spawn(async move { if let Err(e) = Self::update_key_last_used_sync(db_clone, key_id).await { diff --git a/libs/git/ssh/handle.rs b/libs/git/ssh/handle.rs index 7b87fda..a4bb317 100644 --- a/libs/git/ssh/handle.rs +++ b/libs/git/ssh/handle.rs @@ -163,7 +163,52 @@ impl russh::server::Handler for SSHandle { user: &str, public_key: &PublicKey, ) -> Result { - self.auth_publickey(user, public_key).await + let client_info = self + .client_addr + .map(|addr| format!("{}", addr)) + .unwrap_or_else(|| "unknown".to_string()); + + if user != "git" { + tracing::warn!( + "auth_publickey_offer_rejected_invalid_username user={} client={}", + user, + client_info + ); + return Err(russh::Error::NotAuthenticated); + } + + let public_key_str = public_key.to_string(); + if public_key_str.len() < 32 { + tracing::warn!( + "auth_publickey_offer_rejected_invalid_key_length key_length={}", + public_key_str.len() + ); + return Err(russh::Error::NotAuthenticated); + } + + tracing::info!("auth_publickey_offer client={}", client_info); + match self.auth.find_user_by_public_key(&public_key_str).await { + Ok(Some(key_user)) => { + tracing::info!( + "auth_publickey_offer_accepted user={} key={} client={}", + key_user.user.username, + key_user.key_title, + client_info + ); + Ok(Auth::Accept) + } + Ok(None) => { + tracing::warn!( + "auth_publickey_offer_rejected_key_not_found client={}", + client_info + ); + Err(russh::Error::NotAuthenticated) + } + Err(e) => { + tracing::error!("auth_publickey_offer_error error={}", e); + Err(russh::Error::NotAuthenticated) + } + } } async fn auth_publickey( &mut self, @@ -193,8 +238,8 @@ impl russh::server::Handler for SSHandle { } tracing::info!("auth_publickey_attempt client={}", client_info); - let user_model = match self.auth.find_user_by_public_key(&public_key_str).await { - Ok(Some(model)) => model, + let key_user = match self.auth.find_user_by_public_key(&public_key_str).await { + Ok(Some(key_user)) => key_user, Ok(None) => { tracing::warn!("auth_rejected_key_not_found client={}", client_info); return Err(russh::Error::NotAuthenticated); @@ -207,10 +252,11 @@ impl russh::server::Handler for SSHandle { tracing::info!( "auth_publickey_success user={} client={}", - user_model.username, + key_user.user.username, client_info ); - self.operator = Some(user_model); + self.auth.update_key_last_used_async(key_user.key_id); + self.operator = Some(key_user.user); Ok(Auth::Accept) } async fn auth_openssh_certificate( @@ -241,8 +287,8 @@ impl russh::server::Handler for SSHandle { } tracing::info!("auth_publickey_attempt client={}", client_info); - let user_model = match self.auth.find_user_by_public_key(&public_key_str).await { - Ok(Some(model)) => model, + let key_user = match self.auth.find_user_by_public_key(&public_key_str).await { + Ok(Some(key_user)) => key_user, Ok(None) => { tracing::warn!("auth_rejected_key_not_found client={}", client_info); return Err(russh::Error::NotAuthenticated); @@ -255,10 +301,11 @@ impl russh::server::Handler for SSHandle { tracing::info!( "auth_publickey_success user={} client={}", - user_model.username, + key_user.user.username, client_info ); - self.operator = Some(user_model); + self.auth.update_key_last_used_async(key_user.key_id); + self.operator = Some(key_user.user); Ok(Auth::Accept) } async fn authentication_banner(&mut self) -> Result, Self::Error> { diff --git a/libs/git/ssh/server.rs b/libs/git/ssh/server.rs index 12e5abb..552a23f 100644 --- a/libs/git/ssh/server.rs +++ b/libs/git/ssh/server.rs @@ -82,7 +82,9 @@ impl russh::server::Server for SSHServer { ); if io_err.kind() == io::ErrorKind::UnexpectedEof { - tracing::warn!("Client disconnected during handshake or before authentication"); + tracing::warn!( + "SSH peer closed the connection before a clean disconnect was received" + ); } } _ => {