Skip to content

Commit 0862853

Browse files
authored
feat: Implement session management and limits (#142) (#159)
* feat: Implement session management and limits (#142) - Add SessionConfig for configurable session limits: - max_sessions_per_user: Limit concurrent sessions per user - max_total_sessions: Limit total concurrent sessions - idle_timeout: Detect idle sessions - session_timeout: Optional maximum session duration - Enhance SessionManager with per-user tracking: - user_sessions HashMap for tracking sessions by username - authenticate_session() method with per-user limit enforcement - touch() method for updating last_activity timestamp - get_idle_sessions() for timeout detection - get_stats() for session statistics - list_sessions() / list_user_sessions() for admin operations - kill_session() / kill_user_sessions() for forced disconnect - Add SessionError and SessionStats types - Enhance SessionInfo with activity tracking: - last_activity field for idle detection - idle_secs() and is_idle() methods - is_expired() for session timeout checks - Integrate per-user limits in SSH handler: - Check per-user session limits during authentication - Reject authentication if user has too many sessions - Update SessionManager's user tracking on auth success - Add session config to ServerConfig: - max_sessions_per_user field - session_timeout_secs field - session_config() helper method - Comprehensive test coverage (30+ new tests) * fix: Address security and performance review issues - Fix HIGH: Race condition in Drop - add retry mechanism with exponential backoff to ensure session cleanup under lock contention - Fix MEDIUM: Add input validation for SessionConfig - clamp max_sessions_per_user and max_total_sessions to minimum of 1 to prevent misconfiguration that could deny all connections - Add validate() method to SessionConfig for detecting potentially problematic configuration combinations - Fix MEDIUM: Atomic ordering - change SessionId counter from Ordering::Relaxed to Ordering::SeqCst for stricter ordering guarantees and consistent logging - Add tests for config validation and clamping behavior * chore: finalize PR with lint fixes and documentation - Fix code formatting issues (handler.rs, session.rs) - Fix unused variable warning in test (s2 -> _s2) - Add comprehensive session management documentation - Document session_timeout configuration option - Add session management API examples
1 parent 822b1fe commit 0862853

File tree

6 files changed

+1111
-79
lines changed

6 files changed

+1111
-79
lines changed

docs/architecture/server-configuration.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,10 @@ security:
192192
# Idle session timeout (seconds, 0 to disable)
193193
idle_timeout: 3600 # Default: 3600 (1 hour)
194194

195+
# Maximum session duration (seconds, 0 to disable)
196+
# Sessions are terminated after this duration regardless of activity
197+
session_timeout: 0 # Default: 0 (disabled)
198+
195199
# IP allowlist (CIDR notation, empty = allow all)
196200
# When configured, only connections from these ranges are allowed
197201
allowed_ips:
@@ -643,6 +647,100 @@ scp -rp ./data/ user@bssh-server:/storage/backup/
643647

644648
---
645649

650+
## Session Management
651+
652+
The server implements comprehensive session management with per-user limits, idle timeout detection, and session tracking.
653+
654+
### Session Configuration
655+
656+
Session management is configured through the `SessionConfig` structure:
657+
658+
```rust
659+
use bssh::server::session::SessionConfig;
660+
use std::time::Duration;
661+
662+
let config = SessionConfig::new()
663+
.with_max_sessions_per_user(10) // Max sessions per authenticated user
664+
.with_max_total_sessions(1000) // Max total concurrent sessions
665+
.with_idle_timeout(Duration::from_secs(3600)) // 1 hour idle timeout
666+
.with_session_timeout(Duration::from_secs(86400)); // 24 hour max duration
667+
```
668+
669+
### Session Limits
670+
671+
**Per-User Session Limits:**
672+
- Each authenticated user has a configurable maximum number of concurrent sessions
673+
- When a user exceeds their limit, authentication is rejected with an error
674+
- Default: 10 sessions per user
675+
676+
**Total Session Limits:**
677+
- The server enforces a global maximum number of concurrent sessions
678+
- New connections are rejected when the limit is reached
679+
- Default: 1000 total sessions (matches `max_connections`)
680+
681+
### Session Timeouts
682+
683+
**Idle Timeout:**
684+
- Sessions with no activity for the configured duration are marked as idle
685+
- The `cleanup_idle_sessions()` method removes idle unauthenticated sessions
686+
- Default: 1 hour (3600 seconds)
687+
688+
**Session Timeout:**
689+
- Optional maximum session duration regardless of activity
690+
- Sessions exceeding this duration are eligible for termination
691+
- Default: disabled (0)
692+
693+
### Session Activity Tracking
694+
695+
Each session tracks:
696+
- **Session ID**: Unique identifier for the session
697+
- **User**: Authenticated username (if authenticated)
698+
- **Peer Address**: Remote client IP and port
699+
- **Started At**: Timestamp of session creation
700+
- **Last Activity**: Timestamp of last activity (updated via `touch()`)
701+
- **Authentication State**: Whether the session is authenticated
702+
- **Auth Attempts**: Number of authentication attempts
703+
704+
### Session Statistics
705+
706+
The `SessionManager` provides session statistics:
707+
708+
```rust
709+
let stats = manager.get_stats();
710+
println!("Total sessions: {}", stats.total_sessions);
711+
println!("Authenticated: {}", stats.authenticated_sessions);
712+
println!("Unique users: {}", stats.unique_users);
713+
println!("Idle sessions: {}", stats.idle_sessions);
714+
```
715+
716+
### Admin Operations
717+
718+
The session manager supports administrative operations:
719+
720+
```rust
721+
// List all sessions
722+
let sessions = manager.list_sessions();
723+
724+
// List sessions for a specific user
725+
let user_sessions = manager.list_user_sessions("username");
726+
727+
// Force disconnect a session
728+
manager.kill_session(session_id);
729+
730+
// Force disconnect all sessions for a user
731+
let count = manager.kill_user_sessions("username");
732+
```
733+
734+
### Configuration Validation
735+
736+
The `SessionConfig::validate()` method checks for potentially problematic settings and returns warnings:
737+
738+
- Warning if `max_sessions_per_user` > `max_total_sessions` (per-user limit will never be reached)
739+
- Warning if `idle_timeout` is 0 (sessions immediately considered idle)
740+
- Warning if `session_timeout` < `idle_timeout` (sessions may be terminated before idle check)
741+
742+
---
743+
646744
**Related Documentation:**
647745
- [Server CLI Binary](../../ARCHITECTURE.md#server-cli-binary)
648746
- [SSH Server Module](../../ARCHITECTURE.md#ssh-server-module)

src/server/config/mod.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,23 @@ pub struct ServerConfig {
183183
/// Blocked IPs take priority over allowed IPs.
184184
#[serde(default)]
185185
pub blocked_ips: Vec<String>,
186+
187+
/// Maximum number of concurrent sessions per user.
188+
///
189+
/// Default: 10
190+
#[serde(default = "default_max_sessions_per_user")]
191+
pub max_sessions_per_user: usize,
192+
193+
/// Maximum session duration in seconds (optional).
194+
///
195+
/// If set to 0, sessions have no maximum duration.
196+
/// Default: 0 (disabled)
197+
#[serde(default)]
198+
pub session_timeout_secs: u64,
199+
}
200+
201+
fn default_max_sessions_per_user() -> usize {
202+
10
186203
}
187204

188205
/// Serializable configuration for public key authentication.
@@ -281,6 +298,8 @@ impl Default for ServerConfig {
281298
whitelist_ips: Vec::new(),
282299
allowed_ips: Vec::new(),
283300
blocked_ips: Vec::new(),
301+
max_sessions_per_user: default_max_sessions_per_user(),
302+
session_timeout_secs: 0,
284303
}
285304
}
286305
}
@@ -312,6 +331,34 @@ impl ServerConfig {
312331
}
313332
}
314333

334+
/// Get the session timeout as a Duration.
335+
///
336+
/// Returns `None` if session timeout is disabled (set to 0).
337+
pub fn session_timeout(&self) -> Option<Duration> {
338+
if self.session_timeout_secs == 0 {
339+
None
340+
} else {
341+
Some(Duration::from_secs(self.session_timeout_secs))
342+
}
343+
}
344+
345+
/// Create a SessionConfig from the server configuration.
346+
pub fn session_config(&self) -> super::session::SessionConfig {
347+
let mut config = super::session::SessionConfig::new()
348+
.with_max_sessions_per_user(self.max_sessions_per_user)
349+
.with_max_total_sessions(self.max_connections);
350+
351+
if self.idle_timeout_secs > 0 {
352+
config = config.with_idle_timeout(Duration::from_secs(self.idle_timeout_secs));
353+
}
354+
355+
if self.session_timeout_secs > 0 {
356+
config = config.with_session_timeout(Duration::from_secs(self.session_timeout_secs));
357+
}
358+
359+
config
360+
}
361+
315362
/// Check if any host keys are configured.
316363
pub fn has_host_keys(&self) -> bool {
317364
!self.host_keys.is_empty()
@@ -517,6 +564,18 @@ impl ServerConfigBuilder {
517564
self
518565
}
519566

567+
/// Set the maximum sessions per user.
568+
pub fn max_sessions_per_user(mut self, max: usize) -> Self {
569+
self.config.max_sessions_per_user = max;
570+
self
571+
}
572+
573+
/// Set the session timeout in seconds.
574+
pub fn session_timeout_secs(mut self, secs: u64) -> Self {
575+
self.config.session_timeout_secs = secs;
576+
self
577+
}
578+
520579
/// Build the ServerConfig.
521580
pub fn build(self) -> ServerConfig {
522581
self.config
@@ -581,6 +640,8 @@ impl ServerFileConfig {
581640
whitelist_ips: self.security.whitelist_ips,
582641
allowed_ips: self.security.allowed_ips,
583642
blocked_ips: self.security.blocked_ips,
643+
max_sessions_per_user: self.security.max_sessions_per_user,
644+
session_timeout_secs: self.security.session_timeout,
584645
}
585646
}
586647
}

src/server/config/types.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ pub struct SecurityConfig {
393393
/// Maximum number of concurrent sessions per user.
394394
///
395395
/// Default: 10
396-
#[serde(default = "default_max_sessions")]
396+
#[serde(default = "default_max_sessions_per_user")]
397397
pub max_sessions_per_user: usize,
398398

399399
/// Idle session timeout in seconds.
@@ -405,6 +405,15 @@ pub struct SecurityConfig {
405405
#[serde(default = "default_idle_timeout")]
406406
pub idle_timeout: u64,
407407

408+
/// Maximum session duration in seconds (optional).
409+
///
410+
/// If set, sessions are terminated after this duration regardless of activity.
411+
/// Set to 0 to disable.
412+
///
413+
/// Default: 0 (disabled)
414+
#[serde(default)]
415+
pub session_timeout: u64,
416+
408417
/// Allowed IP ranges in CIDR notation.
409418
///
410419
/// If non-empty, only connections from these ranges are allowed.
@@ -473,7 +482,7 @@ fn default_ban_time() -> u64 {
473482
300
474483
}
475484

476-
fn default_max_sessions() -> usize {
485+
fn default_max_sessions_per_user() -> usize {
477486
10
478487
}
479488

@@ -540,8 +549,9 @@ impl Default for SecurityConfig {
540549
auth_window: default_auth_window(),
541550
ban_time: default_ban_time(),
542551
whitelist_ips: Vec::new(),
543-
max_sessions_per_user: default_max_sessions(),
552+
max_sessions_per_user: default_max_sessions_per_user(),
544553
idle_timeout: default_idle_timeout(),
554+
session_timeout: 0,
545555
allowed_ips: Vec::new(),
546556
blocked_ips: Vec::new(),
547557
}

0 commit comments

Comments
 (0)