Separate SSH key probe from authentication
This commit is contained in:
parent
b8bd0ec545
commit
a3ecf0c88b
@ -14,6 +14,12 @@ pub struct SshAuthService {
|
|||||||
db: AppDatabase,
|
db: AppDatabase,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SshKeyUser {
|
||||||
|
pub user: user::Model,
|
||||||
|
pub key_id: i64,
|
||||||
|
pub key_title: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl SshAuthService {
|
impl SshAuthService {
|
||||||
pub fn new(db: AppDatabase) -> Self {
|
pub fn new(db: AppDatabase) -> Self {
|
||||||
Self { db }
|
Self { db }
|
||||||
@ -97,7 +103,7 @@ impl SshAuthService {
|
|||||||
pub async fn find_user_by_public_key(
|
pub async fn find_user_by_public_key(
|
||||||
&self,
|
&self,
|
||||||
public_key_str: &str,
|
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) {
|
let fingerprint = match self.generate_fingerprint_from_public_key(public_key_str) {
|
||||||
Ok(fp) => fp,
|
Ok(fp) => fp,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -144,16 +150,20 @@ impl SshAuthService {
|
|||||||
.one(self.db.reader())
|
.one(self.db.reader())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(ref user) = user_model {
|
if let Some(user) = user_model {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"user authenticated via SSH key user={} key={}",
|
"SSH key matched user={} key={}",
|
||||||
user.username,
|
user.username,
|
||||||
ssh_key.title
|
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 {
|
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();
|
let db_clone = self.db.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Err(e) = Self::update_key_last_used_sync(db_clone, key_id).await {
|
if let Err(e) = Self::update_key_last_used_sync(db_clone, key_id).await {
|
||||||
|
|||||||
@ -163,7 +163,52 @@ impl russh::server::Handler for SSHandle {
|
|||||||
user: &str,
|
user: &str,
|
||||||
public_key: &PublicKey,
|
public_key: &PublicKey,
|
||||||
) -> Result<Auth, Self::Error> {
|
) -> 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(
|
async fn auth_publickey(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -193,8 +238,8 @@ impl russh::server::Handler for SSHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("auth_publickey_attempt client={}", client_info);
|
tracing::info!("auth_publickey_attempt client={}", client_info);
|
||||||
let user_model = match self.auth.find_user_by_public_key(&public_key_str).await {
|
let key_user = match self.auth.find_user_by_public_key(&public_key_str).await {
|
||||||
Ok(Some(model)) => model,
|
Ok(Some(key_user)) => key_user,
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
tracing::warn!("auth_rejected_key_not_found client={}", client_info);
|
tracing::warn!("auth_rejected_key_not_found client={}", client_info);
|
||||||
return Err(russh::Error::NotAuthenticated);
|
return Err(russh::Error::NotAuthenticated);
|
||||||
@ -207,10 +252,11 @@ impl russh::server::Handler for SSHandle {
|
|||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"auth_publickey_success user={} client={}",
|
"auth_publickey_success user={} client={}",
|
||||||
user_model.username,
|
key_user.user.username,
|
||||||
client_info
|
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)
|
Ok(Auth::Accept)
|
||||||
}
|
}
|
||||||
async fn auth_openssh_certificate(
|
async fn auth_openssh_certificate(
|
||||||
@ -241,8 +287,8 @@ impl russh::server::Handler for SSHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("auth_publickey_attempt client={}", client_info);
|
tracing::info!("auth_publickey_attempt client={}", client_info);
|
||||||
let user_model = match self.auth.find_user_by_public_key(&public_key_str).await {
|
let key_user = match self.auth.find_user_by_public_key(&public_key_str).await {
|
||||||
Ok(Some(model)) => model,
|
Ok(Some(key_user)) => key_user,
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
tracing::warn!("auth_rejected_key_not_found client={}", client_info);
|
tracing::warn!("auth_rejected_key_not_found client={}", client_info);
|
||||||
return Err(russh::Error::NotAuthenticated);
|
return Err(russh::Error::NotAuthenticated);
|
||||||
@ -255,10 +301,11 @@ impl russh::server::Handler for SSHandle {
|
|||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"auth_publickey_success user={} client={}",
|
"auth_publickey_success user={} client={}",
|
||||||
user_model.username,
|
key_user.user.username,
|
||||||
client_info
|
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)
|
Ok(Auth::Accept)
|
||||||
}
|
}
|
||||||
async fn authentication_banner(&mut self) -> Result<Option<String>, Self::Error> {
|
async fn authentication_banner(&mut self) -> Result<Option<String>, Self::Error> {
|
||||||
|
|||||||
@ -82,7 +82,9 @@ impl russh::server::Server for SSHServer {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if io_err.kind() == io::ErrorKind::UnexpectedEof {
|
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"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user