Separate SSH key probe from authentication

This commit is contained in:
ZhenYi 2026-05-14 21:44:55 +08:00
parent b8bd0ec545
commit a3ecf0c88b
3 changed files with 75 additions and 16 deletions

View File

@ -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<Option<user::Model>, DbErr> {
) -> Result<Option<SshKeyUser>, 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 {

View File

@ -163,7 +163,52 @@ impl russh::server::Handler for SSHandle {
user: &str,
public_key: &PublicKey,
) -> Result<Auth, Self::Error> {
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<Option<String>, Self::Error> {

View File

@ -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"
);
}
}
_ => {