Skip to content

Commit 082ffc6

Browse files
committed
chore: finalize auth rate limiter with docs and formatting fixes
- Fix code formatting (cargo fmt) - Update ARCHITECTURE.md with Server Security Module documentation - Update server-configuration.md with auth_window and whitelist_ips options - All 930 tests passing, clippy clean
1 parent 0dee594 commit 082ffc6

4 files changed

Lines changed: 33 additions & 9 deletions

File tree

ARCHITECTURE.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,20 @@ Common utilities for code reuse between bssh client and server implementations:
189189

190190
The `security` and `jump::rate_limiter` modules re-export from shared for backward compatibility.
191191

192+
### Server Security Module
193+
194+
Security features for the SSH server (`src/server/security/`):
195+
196+
- **AuthRateLimiter**: Fail2ban-like authentication rate limiting
197+
- Tracks failed authentication attempts per IP address
198+
- Automatic banning after exceeding configurable threshold
199+
- Time-windowed failure counting (failures outside window not counted)
200+
- Configurable ban duration with automatic expiration
201+
- IP whitelist for exempting trusted addresses from banning
202+
- Memory-safe with configurable maximum tracked IPs
203+
- Automatic cleanup of expired records via background task
204+
- Thread-safe async implementation with `Arc<RwLock<>>`
205+
192206
### Server CLI Binary
193207
**Binary**: `bssh-server`
194208

@@ -284,7 +298,8 @@ SSH server implementation using the russh library for accepting incoming connect
284298

285299
- **SshHandler**: Per-connection handler for SSH protocol events
286300
- Public key authentication via AuthProvider trait
287-
- Rate limiting for authentication attempts
301+
- Rate limiting for authentication attempts (token bucket)
302+
- Auth rate limiting with ban support (fail2ban-like)
288303
- Channel operations (open, close, EOF, data)
289304
- PTY, exec, shell, and subsystem request handling
290305
- Command execution with stdout/stderr streaming

docs/architecture/server-configuration.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,19 @@ security:
173173
# Max auth attempts before banning IP
174174
max_auth_attempts: 5 # Default: 5
175175

176+
# Time window for counting auth attempts (seconds)
177+
# Failed attempts outside this window are not counted
178+
auth_window: 300 # Default: 300 (5 minutes)
179+
176180
# Ban duration after exceeding max attempts (seconds)
177181
ban_time: 300 # Default: 300 (5 minutes)
178182

183+
# IPs that are never banned (whitelist)
184+
# These IPs are exempt from rate limiting and banning
185+
whitelist_ips:
186+
- "127.0.0.1"
187+
- "::1"
188+
179189
# Max concurrent sessions per user
180190
max_sessions_per_user: 10 # Default: 10
181191

src/server/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,8 @@ impl BsshServer {
234234
self.config.max_auth_attempts,
235235
self.config.auth_window_secs,
236236
self.config.ban_time_secs,
237-
).with_whitelist(whitelist_ips);
237+
)
238+
.with_whitelist(whitelist_ips);
238239

239240
let auth_rate_limiter = AuthRateLimiter::new(auth_config);
240241

src/server/security/rate_limit.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ impl Default for AuthRateLimitConfig {
5151
fn default() -> Self {
5252
Self {
5353
max_attempts: 5,
54-
window: Duration::from_secs(300), // 5 minutes
54+
window: Duration::from_secs(300), // 5 minutes
5555
ban_duration: Duration::from_secs(300), // 5 minutes
5656
whitelist: HashSet::new(),
5757
max_tracked_ips: 10000, // Limit memory usage
@@ -324,9 +324,8 @@ impl AuthRateLimiter {
324324
{
325325
let mut failures = self.failures.write().await;
326326
let before = failures.len();
327-
failures.retain(|_, record| {
328-
now.duration_since(record.last_failure) < self.config.window
329-
});
327+
failures
328+
.retain(|_, record| now.duration_since(record.last_failure) < self.config.window);
330329
let after = failures.len();
331330
if before > after {
332331
tracing::debug!(
@@ -458,8 +457,7 @@ mod tests {
458457

459458
#[tokio::test]
460459
async fn test_whitelist_ips() {
461-
let config = AuthRateLimitConfig::new(1, 300, 300)
462-
.with_whitelist(vec![localhost()]);
460+
let config = AuthRateLimitConfig::new(1, 300, 300).with_whitelist(vec![localhost()]);
463461
let limiter = AuthRateLimiter::new(config);
464462

465463
let whitelisted = localhost();
@@ -550,7 +548,7 @@ mod tests {
550548
limiter.record_failure(ip2).await; // This triggers ban
551549

552550
assert_eq!(limiter.tracked_count().await, 1); // ip1 still tracked
553-
assert_eq!(limiter.banned_count().await, 1); // ip2 banned
551+
assert_eq!(limiter.banned_count().await, 1); // ip2 banned
554552

555553
// Wait for records to expire
556554
tokio::time::sleep(Duration::from_millis(20)).await;

0 commit comments

Comments
 (0)