@@ -32,6 +32,7 @@ use super::auth::AuthProvider;
3232use super :: config:: ServerConfig ;
3333use super :: exec:: CommandExecutor ;
3434use super :: pty:: PtyConfig as PtyMasterConfig ;
35+ use super :: security:: AuthRateLimiter ;
3536use super :: session:: { ChannelState , PtyConfig , SessionId , SessionInfo , SessionManager } ;
3637use super :: sftp:: SftpHandler ;
3738use super :: shell:: ShellSession ;
@@ -57,6 +58,9 @@ pub struct SshHandler {
5758 /// Rate limiter for authentication attempts.
5859 rate_limiter : RateLimiter < String > ,
5960
61+ /// Auth rate limiter with ban support (fail2ban-like).
62+ auth_rate_limiter : Option < AuthRateLimiter > ,
63+
6064 /// Session information for this connection.
6165 session_info : Option < SessionInfo > ,
6266
@@ -83,6 +87,7 @@ impl SshHandler {
8387 sessions,
8488 auth_provider,
8589 rate_limiter,
90+ auth_rate_limiter : None ,
8691 session_info : Some ( SessionInfo :: new ( peer_addr) ) ,
8792 channels : HashMap :: new ( ) ,
8893 }
@@ -106,6 +111,33 @@ impl SshHandler {
106111 sessions,
107112 auth_provider,
108113 rate_limiter,
114+ auth_rate_limiter : None ,
115+ session_info : Some ( SessionInfo :: new ( peer_addr) ) ,
116+ channels : HashMap :: new ( ) ,
117+ }
118+ }
119+
120+ /// Create a new SSH handler with shared rate limiters including auth ban support.
121+ ///
122+ /// This is the preferred constructor for production use as it shares
123+ /// both rate limiters across all handlers, providing server-wide rate limiting
124+ /// and fail2ban-like functionality.
125+ pub fn with_rate_limiters (
126+ peer_addr : Option < SocketAddr > ,
127+ config : Arc < ServerConfig > ,
128+ sessions : Arc < RwLock < SessionManager > > ,
129+ rate_limiter : RateLimiter < String > ,
130+ auth_rate_limiter : AuthRateLimiter ,
131+ ) -> Self {
132+ let auth_provider = config. create_auth_provider ( ) ;
133+
134+ Self {
135+ peer_addr,
136+ config,
137+ sessions,
138+ auth_provider,
139+ rate_limiter,
140+ auth_rate_limiter : Some ( auth_rate_limiter) ,
109141 session_info : Some ( SessionInfo :: new ( peer_addr) ) ,
110142 channels : HashMap :: new ( ) ,
111143 }
@@ -128,6 +160,7 @@ impl SshHandler {
128160 sessions,
129161 auth_provider,
130162 rate_limiter,
163+ auth_rate_limiter : None ,
131164 session_info : Some ( SessionInfo :: new ( peer_addr) ) ,
132165 channels : HashMap :: new ( ) ,
133166 }
@@ -284,6 +317,7 @@ impl russh::server::Handler for SshHandler {
284317 // Clone what we need for the async block
285318 let auth_provider = Arc :: clone ( & self . auth_provider ) ;
286319 let rate_limiter = self . rate_limiter . clone ( ) ;
320+ let auth_rate_limiter = self . auth_rate_limiter . clone ( ) ;
287321 let peer_addr = self . peer_addr ;
288322 let user = user. to_string ( ) ;
289323 let public_key = public_key. clone ( ) ;
@@ -292,6 +326,23 @@ impl russh::server::Handler for SshHandler {
292326 let session_info = & mut self . session_info ;
293327
294328 async move {
329+ // Check if IP is banned (fail2ban-like check)
330+ if let Some ( ref limiter) = auth_rate_limiter {
331+ if let Some ( ip) = peer_addr. map ( |a| a. ip ( ) ) {
332+ if limiter. is_banned ( & ip) . await {
333+ tracing:: warn!(
334+ user = %user,
335+ peer = ?peer_addr,
336+ "Rejected auth from banned IP"
337+ ) ;
338+ return Ok ( Auth :: Reject {
339+ proceed_with_methods : None ,
340+ partial_success : false ,
341+ } ) ;
342+ }
343+ }
344+ }
345+
295346 if exceeded {
296347 tracing:: warn!(
297348 user = %user,
@@ -349,6 +400,13 @@ impl russh::server::Handler for SshHandler {
349400 info. authenticate ( & user) ;
350401 }
351402
403+ // Record success to reset failure counter
404+ if let Some ( ref limiter) = auth_rate_limiter {
405+ if let Some ( ip) = peer_addr. map ( |a| a. ip ( ) ) {
406+ limiter. record_success ( & ip) . await ;
407+ }
408+ }
409+
352410 Ok ( Auth :: Accept )
353411 }
354412 Ok ( _) => {
@@ -359,6 +417,20 @@ impl russh::server::Handler for SshHandler {
359417 "Public key authentication rejected"
360418 ) ;
361419
420+ // Record failure for ban tracking
421+ if let Some ( ref limiter) = auth_rate_limiter {
422+ if let Some ( ip) = peer_addr. map ( |a| a. ip ( ) ) {
423+ let banned = limiter. record_failure ( ip) . await ;
424+ if banned {
425+ tracing:: warn!(
426+ user = %user,
427+ peer = ?peer_addr,
428+ "IP banned due to too many failed auth attempts"
429+ ) ;
430+ }
431+ }
432+ }
433+
362434 let proceed = if methods. is_empty ( ) {
363435 None
364436 } else {
@@ -378,6 +450,13 @@ impl russh::server::Handler for SshHandler {
378450 "Error during public key verification"
379451 ) ;
380452
453+ // Record failure for ban tracking
454+ if let Some ( ref limiter) = auth_rate_limiter {
455+ if let Some ( ip) = peer_addr. map ( |a| a. ip ( ) ) {
456+ limiter. record_failure ( ip) . await ;
457+ }
458+ }
459+
381460 let proceed = if methods. is_empty ( ) {
382461 None
383462 } else {
@@ -421,6 +500,7 @@ impl russh::server::Handler for SshHandler {
421500 // Clone what we need for the async block
422501 let auth_provider = Arc :: clone ( & self . auth_provider ) ;
423502 let rate_limiter = self . rate_limiter . clone ( ) ;
503+ let auth_rate_limiter = self . auth_rate_limiter . clone ( ) ;
424504 let peer_addr = self . peer_addr ;
425505 let user = user. to_string ( ) ;
426506 // Use Zeroizing to ensure password is securely cleared from memory when dropped
@@ -431,6 +511,23 @@ impl russh::server::Handler for SshHandler {
431511 let session_info = & mut self . session_info ;
432512
433513 async move {
514+ // Check if IP is banned (fail2ban-like check)
515+ if let Some ( ref limiter) = auth_rate_limiter {
516+ if let Some ( ip) = peer_addr. map ( |a| a. ip ( ) ) {
517+ if limiter. is_banned ( & ip) . await {
518+ tracing:: warn!(
519+ user = %user,
520+ peer = ?peer_addr,
521+ "Rejected password auth from banned IP"
522+ ) ;
523+ return Ok ( Auth :: Reject {
524+ proceed_with_methods : None ,
525+ partial_success : false ,
526+ } ) ;
527+ }
528+ }
529+ }
530+
434531 // Check if password auth is enabled
435532 if !allow_password {
436533 tracing:: debug!(
@@ -504,6 +601,13 @@ impl russh::server::Handler for SshHandler {
504601 info. authenticate ( & user) ;
505602 }
506603
604+ // Record success to reset failure counter
605+ if let Some ( ref limiter) = auth_rate_limiter {
606+ if let Some ( ip) = peer_addr. map ( |a| a. ip ( ) ) {
607+ limiter. record_success ( & ip) . await ;
608+ }
609+ }
610+
507611 Ok ( Auth :: Accept )
508612 }
509613 Ok ( _) => {
@@ -513,6 +617,20 @@ impl russh::server::Handler for SshHandler {
513617 "Password authentication rejected"
514618 ) ;
515619
620+ // Record failure for ban tracking
621+ if let Some ( ref limiter) = auth_rate_limiter {
622+ if let Some ( ip) = peer_addr. map ( |a| a. ip ( ) ) {
623+ let banned = limiter. record_failure ( ip) . await ;
624+ if banned {
625+ tracing:: warn!(
626+ user = %user,
627+ peer = ?peer_addr,
628+ "IP banned due to too many failed password auth attempts"
629+ ) ;
630+ }
631+ }
632+ }
633+
516634 let proceed = if methods. is_empty ( ) {
517635 None
518636 } else {
@@ -532,6 +650,13 @@ impl russh::server::Handler for SshHandler {
532650 "Error during password verification"
533651 ) ;
534652
653+ // Record failure for ban tracking
654+ if let Some ( ref limiter) = auth_rate_limiter {
655+ if let Some ( ip) = peer_addr. map ( |a| a. ip ( ) ) {
656+ limiter. record_failure ( ip) . await ;
657+ }
658+ }
659+
535660 let proceed = if methods. is_empty ( ) {
536661 None
537662 } else {
0 commit comments