Skip to content
Clone
194c44c
115 lines 4.3 KB Rust
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<Option<user::Model>, 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<Auth, russh::Error> {
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<Auth, russh::Error> {
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)
}
}
}
}