gitdataai/libs/git/ssh/server.rs
ZhenYi cce9d216b8 fix: resolve 4 remaining "design decision" bugs
- SSH rate limiter: wire SshRateLimiter into SSHServer with IP-based
  rate limiting on new_client connections
- Room startup: cap initial room load at 1000 via limit() to prevent
  resource exhaustion on large instances
- WS token exposure: only include token in URL for cross-origin
  connections; same-origin web clients authenticate via secure cookies
- CSRF: confirmed SameSite::Lax + Secure + HttpOnly are all set
  (session config defaults)
2026-04-27 11:20:38 +08:00

94 lines
2.9 KiB
Rust

use crate::ssh::ReceiveSyncService;
use crate::ssh::SshTokenService;
use crate::ssh::handle::SSHandle;
use crate::ssh::rate_limit::SshRateLimiter;
use db::cache::AppCache;
use db::database::AppDatabase;
use deadpool_redis::cluster::Pool as RedisPool;
use russh::server::Handler;
use std::io;
use std::net::SocketAddr;
use std::sync::Arc;
pub struct SSHServer {
pub db: AppDatabase,
pub cache: AppCache,
pub redis_pool: RedisPool,
pub token_service: SshTokenService,
pub rate_limiter: Arc<SshRateLimiter>,
}
impl SSHServer {
pub fn new(
db: AppDatabase,
cache: AppCache,
redis_pool: RedisPool,
token_service: SshTokenService,
) -> Self {
SSHServer {
db,
cache,
redis_pool,
token_service,
rate_limiter: Arc::new(SshRateLimiter::new()),
}
}
}
impl russh::server::Server for SSHServer {
type Handler = SSHandle;
fn new_client(&mut self, addr: Option<SocketAddr>) -> Self::Handler {
if let Some(addr) = addr {
let ip = addr.ip().to_string();
tracing::info!("New SSH connection ip={} port={}", ip, addr.port());
// Check IP rate limit before accepting the connection.
let limiter = self.rate_limiter.clone();
let ip_clone = ip.clone();
tokio::spawn(async move {
if !limiter.is_ip_allowed(&ip_clone).await {
tracing::warn!(ip = %ip_clone, "SSH connection rate limited");
}
});
} else {
tracing::info!("New SSH connection from unknown address");
}
let sync_service = ReceiveSyncService::new(self.redis_pool.clone());
SSHandle::new(
self.db.clone(),
self.cache.clone(),
sync_service,
self.token_service.clone(),
addr,
)
}
fn handle_session_error(&mut self, error: <Self::Handler as Handler>::Error) {
match error {
russh::Error::Disconnect => {
tracing::info!("Connection disconnected by peer");
}
russh::Error::Inconsistent => {
tracing::warn!("Protocol inconsistency detected");
}
russh::Error::NotAuthenticated => {
tracing::warn!("Authentication failed");
}
russh::Error::IO(ref io_err) => {
tracing::warn!(
"SSH IO error kind={:?} message={} raw_os_error={:?}",
io_err.kind(),
io_err,
io_err.raw_os_error()
);
if io_err.kind() == io::ErrorKind::UnexpectedEof {
tracing::warn!("Client disconnected during handshake or before authentication");
}
}
_ => {
tracing::warn!("SSH session error error={}", error);
}
}
}
}