-
Notifications
You must be signed in to change notification settings - Fork 1
Closed
Labels
priority:highHigh priority issueHigh priority issuestatus:doneCompletedCompletedtype:enhancementNew feature or requestNew feature or request
Description
Summary
Implement the core SSH server handler using the russh library's server API. This is the foundation for all SSH server functionality including authentication, command execution, and subsystems.
Parent Epic
- Implement bssh-server with SFTP/SCP support #123 - bssh-server 추가 구현
- Depends on: Create shared module structure for client/server code reuse #124 (shared module structure)
Background
The russh library (v0.56.0) provides full SSH server support through the russh::server module. We need to implement:
russh::server::Servertrait - Creates handler instances for connectionsrussh::server::Handlertrait - Handles SSH protocol events
Implementation Details
1. Create Server Module Structure
src/server/
├── mod.rs # Module exports
├── handler.rs # SSH handler implementation
└── session.rs # Session state management
2. Implement Server Struct
// src/server/mod.rs
use std::sync::Arc;
use tokio::sync::RwLock;
pub struct BsshServer {
config: Arc<ServerConfig>,
sessions: Arc<RwLock<SessionManager>>,
}
impl BsshServer {
pub fn new(config: ServerConfig) -> Self {
Self {
config: Arc::new(config),
sessions: Arc::new(RwLock::new(SessionManager::new())),
}
}
pub async fn run(&self, addr: impl ToSocketAddrs) -> Result<()> {
let russh_config = self.build_russh_config()?;
self.run_on_address(Arc::new(russh_config), addr).await
}
fn build_russh_config(&self) -> Result<russh::server::Config> {
let mut keys = Vec::new();
for key_path in &self.config.host_keys {
let key = russh::keys::load_secret_key(key_path, None)
.context("Failed to load host key")?;
keys.push(key);
}
Ok(russh::server::Config {
keys,
auth_rejection_time: Duration::from_secs(3),
auth_rejection_time_initial: Some(Duration::from_secs(0)),
..Default::default()
})
}
}3. Implement russh::server::Server Trait
impl russh::server::Server for BsshServer {
type Handler = SshHandler;
fn new_client(&mut self, peer_addr: Option<SocketAddr>) -> Self::Handler {
SshHandler::new(
peer_addr,
Arc::clone(&self.config),
Arc::clone(&self.sessions),
)
}
}4. Implement SSH Handler
// src/server/handler.rs
use russh::server::{Auth, Handler, Session};
use russh::{Channel, ChannelId};
pub struct SshHandler {
peer_addr: Option<SocketAddr>,
config: Arc<ServerConfig>,
sessions: Arc<RwLock<SessionManager>>,
user: Option<String>,
channels: HashMap<ChannelId, ChannelState>,
}
#[async_trait]
impl Handler for SshHandler {
type Error = anyhow::Error;
/// Called when client requests authentication
async fn auth_none(&mut self, user: &str) -> Result<Auth, Self::Error> {
tracing::debug!("auth_none attempt for user: {}", user);
Ok(Auth::Reject {
proceed_with_methods: Some(MethodSet::PUBLICKEY | MethodSet::PASSWORD),
})
}
/// Public key authentication - to be implemented in #126
async fn auth_publickey(
&mut self,
user: &str,
public_key: &russh::keys::ssh_key::PublicKey,
) -> Result<Auth, Self::Error> {
// Placeholder - will be implemented in #126
Ok(Auth::Reject {
proceed_with_methods: Some(MethodSet::PASSWORD),
})
}
/// Password authentication - to be implemented in #127
async fn auth_password(
&mut self,
user: &str,
password: &str,
) -> Result<Auth, Self::Error> {
// Placeholder - will be implemented in #127
Ok(Auth::Reject {
proceed_with_methods: None,
})
}
/// Called when a channel is opened
async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
session: &mut Session,
) -> Result<bool, Self::Error> {
let channel_id = channel.id();
self.channels.insert(channel_id, ChannelState::new(channel));
Ok(true)
}
/// Handle exec requests - placeholder for #128
async fn exec_request(
&mut self,
channel_id: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
// Placeholder - will be implemented in #128
session.channel_failure(channel_id)?;
Ok(())
}
/// Handle shell requests - placeholder for #129
async fn shell_request(
&mut self,
channel_id: ChannelId,
session: &mut Session,
) -> Result<(), Self::Error> {
// Placeholder - will be implemented in #129
session.channel_failure(channel_id)?;
Ok(())
}
/// Handle PTY requests - placeholder for #129
async fn pty_request(
&mut self,
channel_id: ChannelId,
term: &str,
col_width: u32,
row_height: u32,
pix_width: u32,
pix_height: u32,
modes: &[(russh::Pty, u32)],
session: &mut Session,
) -> Result<(), Self::Error> {
// Placeholder - will be implemented in #129
session.channel_success(channel_id)?;
Ok(())
}
/// Handle subsystem requests (sftp) - placeholder for #132
async fn subsystem_request(
&mut self,
channel_id: ChannelId,
name: &str,
session: &mut Session,
) -> Result<(), Self::Error> {
// Placeholder - will be implemented in #132
session.channel_failure(channel_id)?;
Ok(())
}
/// Handle data from client
async fn data(
&mut self,
channel_id: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
// Forward to appropriate handler based on channel state
if let Some(channel_state) = self.channels.get_mut(&channel_id) {
channel_state.handle_data(data, session).await?;
}
Ok(())
}
/// Handle channel close
async fn channel_close(
&mut self,
channel_id: ChannelId,
session: &mut Session,
) -> Result<(), Self::Error> {
self.channels.remove(&channel_id);
Ok(())
}
/// Handle channel EOF
async fn channel_eof(
&mut self,
channel_id: ChannelId,
session: &mut Session,
) -> Result<(), Self::Error> {
if let Some(channel_state) = self.channels.get_mut(&channel_id) {
channel_state.handle_eof(session).await?;
}
Ok(())
}
}5. Session State Management
// src/server/session.rs
pub struct SessionManager {
sessions: HashMap<SessionId, SessionInfo>,
max_sessions: usize,
}
pub struct SessionInfo {
pub id: SessionId,
pub user: String,
pub peer_addr: SocketAddr,
pub started_at: Instant,
pub authenticated: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SessionId(u64);
pub struct ChannelState {
channel: Channel<Msg>,
state: ChannelMode,
}
pub enum ChannelMode {
Idle,
Exec { command: String },
Shell,
Sftp,
}Reference Implementation
See russh examples:
- https://github.com/Eugeny/russh/blob/main/russh/examples/sftp_server.rs
- https://github.com/Eugeny/russh/blob/main/russh/examples/server.rs
Files to Create/Modify
| File | Action |
|---|---|
src/server/mod.rs |
Create - Module exports and BsshServer struct |
src/server/handler.rs |
Create - SshHandler implementation |
src/server/session.rs |
Create - Session management |
src/lib.rs |
Modify - Add server module |
Testing Requirements
- Unit tests for session management
- Integration test: Connect with OpenSSH client, verify handshake completes
- Integration test: Verify auth rejection returns proper methods
# Test basic connection (should fail auth but complete handshake)
ssh -v -o StrictHostKeyChecking=no -p 2222 testuser@localhostAcceptance Criteria
-
BsshServerstruct created with russh configuration -
russh::server::Servertrait implemented -
russh::server::Handlertrait implemented with all required methods (placeholders OK) - Host key loading from file
- Session management infrastructure in place
- Channel state tracking
- Server can accept connections and complete SSH handshake
- Authentication rejection returns available methods
- Logging for connection events
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
priority:highHigh priority issueHigh priority issuestatus:doneCompletedCompletedtype:enhancementNew feature or requestNew feature or request