Skip to content

Conversation

@inureyes
Copy link
Member

Summary

  • Implement core SSH server handler using russh library's server API
  • Add BsshServer struct with configuration and session management
  • Implement russh::server::Server and Handler traits
  • Add session management infrastructure with SessionManager
  • Include placeholder implementations for all authentication methods

Changes

This PR implements the foundation for SSH server functionality in bssh-server:

Server Module Structure

src/server/
├── mod.rs       # Module exports and BsshServer struct
├── config.rs    # ServerConfig with builder pattern
├── handler.rs   # SSH handler implementation (russh::server::Handler)
└── session.rs   # Session state management

Key Components

BsshServer (mod.rs)

  • Main server struct that manages SSH server lifecycle
  • Builds russh configuration from ServerConfig
  • Loads host keys from OpenSSH format files
  • Implements russh::server::Server trait through BsshServerRunner

SshHandler (handler.rs)

  • Implements russh::server::Handler trait
  • Handles authentication methods (auth_none, auth_publickey, auth_password)
  • Manages channel operations (open, close, eof, data)
  • Handles PTY, exec, shell, and subsystem requests
  • All auth methods are placeholder rejections for now

SessionManager (session.rs)

  • Tracks active sessions with configurable capacity
  • SessionInfo stores user, peer address, timestamps
  • ChannelState tracks channel mode (Idle/Exec/Shell/Sftp)
  • Cleanup mechanism for idle sessions

ServerConfig (config.rs)

  • Builder pattern for server configuration
  • Host key paths, listen address, connection limits
  • Authentication method toggles
  • Timeout configurations

Test plan

  • 25 unit tests covering session management, configuration, handler
  • All tests pass (cargo test server:: shows 25 passing)
  • Code compiles without warnings
  • Clippy passes with -D warnings
  • Manual testing: Server can accept connections (requires host key setup)

Related Issues

Closes #125

Follow-up Issues

Add the core SSH server implementation using the russh library's server
API. This provides the foundation for all SSH server functionality
including authentication, command execution, and subsystems.

Implementation includes:

- BsshServer struct with russh configuration and session management
- russh::server::Server trait implementation for creating handlers
- russh::server::Handler trait with all required methods:
  - auth_none, auth_publickey, auth_password (placeholder rejections)
  - channel_open_session, channel_close, channel_eof
  - pty_request, exec_request, shell_request, subsystem_request
  - data handling
- ServerConfig with builder pattern for configuration
- SessionManager for tracking active sessions with capacity limits
- SessionInfo and SessionId for session metadata
- ChannelState and ChannelMode for channel tracking
- PtyConfig for terminal configuration storage
- Host key loading from OpenSSH format files
- Comprehensive logging for connection events

All authentication methods are placeholder implementations that reject
and advertise available methods. These will be implemented in follow-up
issues (#126 for publickey, #127 for password).

The implementation follows russh 0.56.0 API patterns and includes 25
unit tests covering session management, configuration, and handler
creation.

Closes #125
@inureyes inureyes added type:enhancement New feature or request priority:high High priority issue labels Jan 22, 2026
@inureyes
Copy link
Member Author

Security and Performance Review: PR #146

Date: 2026-01-22
Reviewer: AI Code Reviewer
Status: Analysis Complete
Languages: Rust
Risk Level: Medium


Executive Summary

This PR implements a foundational SSH server handler using the russh library. The code is well-structured with proper use of Rust idioms, comprehensive unit tests (25 passing), and passes all clippy checks. While this is a placeholder implementation with authentication methods that reject all attempts, there are several security and quality considerations for the foundation that should be addressed before real authentication is implemented.


1. Analysis Summary

  • PR Branch: feature/issue-125-ssh-server-handler
  • Scope: changed-files (5 files, +1646 lines)
  • Total issues identified: 7
  • Critical: 0 | High: 2 | Medium: 3 | Low: 2

2. Prioritized Issue Roadmap

HIGH (2 issues)

H1: Session Not Removed on Handler Drop

File: src/server/handler.rs:455-466
Issue: When SshHandler is dropped, the session is logged but NOT removed from SessionManager. This causes a resource leak where session slots accumulate over time, eventually preventing new connections when max_sessions is reached.

impl Drop for SshHandler {
    fn drop(&mut self) {
        if let Some(ref info) = self.session_info {
            tracing::info!(...);
            // Missing: sessions.remove(info.id)
        }
    }
}

Impact: Server will become unable to accept new connections after serving max_sessions total connections (not concurrent), requiring restart.

Recommendation: The session must be removed from SessionManager when the handler is dropped. However, this requires async cleanup since SessionManager is behind RwLock. Consider:

  1. Using a synchronous cleanup mechanism
  2. Spawning a cleanup task on drop
  3. Using Arc<RwLock<SessionManager>> with a weak reference pattern

H2: Authenticated Count Method Inefficiency

File: src/server/session.rs:301-303
Issue: The authenticated_count() method collects filtered items into a Vec before counting, which is unnecessary allocation.

pub fn authenticated_count(&self) -> usize {
    self.sessions.values().filter(|s| s.authenticated).collect::<Vec<_>>().len()
}

Impact: Unnecessary memory allocation on every call. In high-load scenarios with many sessions, this wastes memory and CPU cycles.

Recommendation: Use .count() directly:

pub fn authenticated_count(&self) -> usize {
    self.sessions.values().filter(|s| s.authenticated).count()
}

MEDIUM (3 issues)

M1: SessionId Counter Overflow Not Handled

File: src/server/session.rs:44-47
Issue: SessionId::new() uses AtomicU64::fetch_add with wrapping semantics. While u64 overflow is extremely unlikely in practice (~584 billion years at 1M sessions/second), the counter could theoretically wrap around and create duplicate session IDs.

pub fn new() -> Self {
    static COUNTER: AtomicU64 = AtomicU64::new(1);
    Self(COUNTER.fetch_add(1, Ordering::Relaxed))
}

Impact: Very low probability but could cause session confusion if IDs collide.

Recommendation: For long-running servers, consider adding a timestamp component or using UUIDs. This is acceptable for now but should be documented.


M2: Missing Rate Limiting for Authentication Attempts

File: src/server/handler.rs
Issue: While max_auth_attempts is enforced per-session, there's no rate limiting across sessions. An attacker could rapidly create new connections to bypass the per-session limit.

Impact: The server is vulnerable to brute-force attacks that cycle through many connections. The russh library provides auth_rejection_time (set to 3 seconds), but this only delays responses on the same connection.

Recommendation: For future implementation, add:

  1. Per-IP rate limiting
  2. Connection rate limiting
  3. Exponential backoff per IP for failed authentications

M3: Configuration Validation Missing

File: src/server/config.rs
Issue: The builder pattern accepts any values without validation. Invalid configurations could cause unexpected behavior:

  • max_connections: 0 would prevent any connections
  • max_auth_attempts: 0 would immediately reject all auth attempts
  • Empty listen_address would fail at runtime

Impact: Runtime failures instead of early validation errors.

Recommendation: Add validation in ServerConfigBuilder::build():

pub fn build(self) -> Result<ServerConfig, ConfigError> {
    if self.config.max_connections == 0 {
        return Err(ConfigError::InvalidMaxConnections);
    }
    // ... more validation
    Ok(self.config)
}

LOW (2 issues)

L1: Potential Timing Attack in Auth Attempts Check

File: src/server/handler.rs:109-113
Issue: The auth_attempts_exceeded() check happens after logging, which could leak timing information about whether a session exists.

fn auth_attempts_exceeded(&self) -> bool {
    self.session_info
        .as_ref()
        .is_some_and(|s| s.auth_attempts >= self.config.max_auth_attempts)
}

Impact: Minor - russh already provides auth_rejection_time for timing normalization.

Recommendation: Ensure consistent timing regardless of session state (already handled by russh's rejection delay).


L2: Missing Debug Implementation for BsshServer

File: src/server/mod.rs:68-74
Issue: BsshServer doesn't derive Debug, making it harder to debug in logs.

pub struct BsshServer {
    config: Arc<ServerConfig>,
    sessions: Arc<RwLock<SessionManager>>,
}

Impact: Inconvenient for debugging but not a functional issue.

Recommendation: Add #[derive(Debug)] or implement manually with appropriate redaction of sensitive data.


3. Positive Observations

Security Strengths

  • Authentication is properly rejected by default (fail-safe design)
  • Session limits are enforced at connection time
  • Host key validation is required before server starts
  • Proper use of russh's built-in timing protections (auth_rejection_time)
  • No hardcoded credentials or secrets
  • Proper error handling with anyhow::Result

Code Quality

  • Comprehensive documentation with examples
  • 25 unit tests covering all modules
  • All clippy checks pass with -D warnings
  • Proper use of Arc/RwLock for shared state
  • Clean separation of concerns (config/handler/session)
  • Builder pattern for configuration
  • Proper lifetime management

Performance

  • Efficient use of async/await
  • Proper use of Arc for shared data
  • HashMap for O(1) session/channel lookups
  • No unnecessary cloning (except where required for async blocks)

4. Recommendations Summary

Priority Issue Effort Status
HIGH H1: Session leak on drop Medium Open
HIGH H2: Inefficient count method Low Open
MEDIUM M1: SessionId overflow Low (doc only) Open
MEDIUM M2: Missing rate limiting High (future) Deferred
MEDIUM M3: Config validation Medium Open
LOW L1: Timing attack N/A Acceptable
LOW L2: Missing Debug impl Low Open

5. Action Items

Must Fix Before Merge

  1. H1: Session cleanup on handler drop - critical for production use
  2. H2: Fix authenticated_count() to avoid unnecessary allocation

Should Fix

  1. M3: Add configuration validation in builder

Nice to Have

  1. L2: Add Debug derive for BsshServer
  2. M1: Document SessionId overflow behavior

Future Work (not blocking this PR)

  1. M2: Rate limiting should be implemented when real authentication is added (Implement public key authentication for server #126, Implement password authentication for server #127)

6. Verdict

The PR is well-implemented with good Rust practices and comprehensive testing. The identified issues are primarily around resource management (session cleanup) and minor performance optimizations.

Recommendation: Fix H1 and H2 before merging. The session leak (H1) is particularly important as it would cause production issues.

Fixed two HIGH severity issues found in PR #146 review:

1. Session resource leak: SshHandler Drop now properly removes sessions
   from SessionManager using try_write(). Previously, sessions were only
   logged on drop but never removed, causing session slots to accumulate.
   The implementation uses try_write() to avoid blocking in Drop and logs
   a warning if lock acquisition fails.

2. Inefficient authenticated_count(): Replaced the inefficient
   .collect::<Vec<_>>().len() pattern with direct .count() method.
   This eliminates unnecessary heap allocation and improves performance.

Both changes maintain all existing tests and pass clippy checks.
- Add 17 new unit tests for comprehensive server module coverage (42 total)
- Update ARCHITECTURE.md with SSH server module documentation
- Update docs/architecture/README.md with server component references
- Fix code formatting issues in server module
- All tests passing, clippy clean, format verified
@inureyes
Copy link
Member Author

PR Finalization Report

Project Structure

  • Project Type: Rust
  • Test Framework: cargo test
  • Documentation System: Markdown (ARCHITECTURE.md, docs/architecture/)
  • Lint Tools: cargo fmt, cargo clippy

Checklist

Tests

  • Analyzed existing test structure (25 unit tests in server module)
  • Identified missing test coverage
  • Generated 17 new tests for comprehensive coverage (42 total)
  • All tests passing

New tests added:

  • test_session_id_as_u64
  • test_session_info_no_peer_addr
  • test_session_info_duration
  • test_session_manager_default
  • test_session_manager_iter
  • test_session_manager_cleanup_idle
  • test_session_manager_cleanup_preserves_authenticated
  • test_channel_mode_exec
  • test_channel_mode_shell
  • test_channel_mode_sftp
  • test_handler_no_peer_addr
  • test_allowed_methods_publickey_only
  • test_allowed_methods_password_only
  • test_config_new
  • test_builder_host_keys_vec
  • test_builder_auth_timeout
  • test_builder_idle_timeout

Documentation

  • ARCHITECTURE.md updated with SSH Server Module section
  • docs/architecture/README.md updated with server component references
  • Code structure documentation in source files complete

Code Quality

  • cargo fmt: All files formatted
  • cargo clippy -- -D warnings: Clean (no warnings)
  • All 42 server module tests passing

Changes Made

  1. Tests: Added 17 new unit tests to increase coverage of edge cases
  2. Documentation: Added comprehensive SSH Server Module section to ARCHITECTURE.md
  3. Formatting: Fixed formatting issues in handler.rs, mod.rs, session.rs

Summary

The PR now has:

  • 42 unit tests covering session management, configuration, and handler functionality
  • Complete documentation of the new server module architecture
  • Clean code formatting and no clippy warnings

Ready for merge.

@inureyes inureyes merged commit 1f2ac4f into main Jan 22, 2026
1 of 2 checks passed
@inureyes inureyes deleted the feature/issue-125-ssh-server-handler branch January 22, 2026 07:50
@inureyes inureyes self-assigned this Jan 24, 2026
@inureyes inureyes added the status:done Completed label Jan 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority:high High priority issue status:done Completed type:enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement basic SSH server handler with russh

1 participant