use std::str::FromStr; use models::users::user; use russh::keys::PublicKey; use russh::keys::ssh_key::HashAlg; use russh::server::Auth; use sea_orm::EntityTrait; use crate::share::errors::GitError; use crate::ssh::handler::GitSshHandler; impl GitSshHandler { /// Resolve the account owning `public_key` through KenOAuth. The SSH /// username is ignored: GitData virtual accounts (`git@host`) identify /// users by key, and KenOAuth owns the key → user mapping. async fn find_user_by_public_key( &self, public_key: &PublicKey, ) -> Result, GitError> { let git = &self.service.smart.git; let Some(client) = git.state.kenauth.as_ref() else { return Err(GitError::Internal( "kenoauth is not configured for ssh authentication".into(), )); }; let principal = match client.lookup_ssh_key(&public_key.to_string()).await { Ok(Some(principal)) => principal, Ok(None) => return Ok(None), Err(error) => { tracing::warn!(%error, "ssh key lookup failed"); return Ok(None); } }; let user = user::Entity::find_by_id(principal.user_id) .one(git.db_read()) .await?; Ok(user.filter(|row| row.is_active)) } pub async fn auth_publickey_offered_impl( &mut self, user: &str, public_key: &PublicKey, ) -> Result { let fingerprint = public_key.fingerprint(HashAlg::Sha256).to_string(); if self.over_capacity { tracing::debug!(user, %fingerprint, "publickey offer rejected: server over capacity"); return Ok(Auth::reject()); } if user.is_empty() || public_key.to_string().len() < 32 { tracing::debug!(user, %fingerprint, "publickey offer rejected: malformed request"); return Ok(Auth::reject()); } match self.find_user_by_public_key(public_key).await { Ok(Some(_)) => { tracing::debug!(user, %fingerprint, "publickey offer matched a known key"); Ok(Auth::Accept) } Ok(None) => { tracing::info!(user, %fingerprint, client = ?self.client_addr, "publickey offer rejected: key not registered"); Ok(Auth::reject()) } Err(error) => { tracing::warn!(user, %fingerprint, "ssh auth lookup failed: {error}"); Err(russh::Error::NotAuthenticated) } } } pub async fn auth_publickey_impl( &mut self, user: &str, public_key: &PublicKey, ) -> Result { let fingerprint = public_key.fingerprint(HashAlg::Sha256).to_string(); if self.over_capacity { tracing::debug!(user, %fingerprint, "publickey auth rejected: server over capacity"); return Ok(Auth::reject()); } if user.is_empty() || public_key.to_string().len() < 32 { tracing::debug!(user, %fingerprint, "publickey auth rejected: malformed request"); return Ok(Auth::reject()); } match self.find_user_by_public_key(public_key).await { Ok(Some(found)) => { if found.is_restricted || found.prohibit_login { tracing::warn!( user, user_id = %found.uid, %fingerprint, "publickey auth rejected: user restricted or login prohibited" ); return Ok(Auth::reject()); } tracing::info!( user, user_id = %found.uid, %fingerprint, client = ?self.client_addr, "ssh publickey auth accepted" ); self.operator = Some(found); Ok(Auth::Accept) } Ok(None) => { tracing::info!(user, %fingerprint, client = ?self.client_addr, "publickey auth rejected: key not registered"); Ok(Auth::reject()) } Err(error) => { tracing::warn!(user, %fingerprint, "ssh auth lookup failed: {error}"); Err(russh::Error::NotAuthenticated) } } } }