VFS/DataProvider/Config refactoring + SSH public key authentication
Phase 1-6 of refactoring plan: - VFS abstraction (VfsBackend trait + LocalFs + OpenFlags builder) - DataProvider trait (SqliteProvider + PgProvider, SFTPGo-compatible) - Config refactoring (AppConfig unified sections, env overrides) - SSH handlers (sftp/scp/rsync) migrated to VFS + DataProvider - SSH public key authentication (Ed25519 signature verification) - SSH stderr → CHANNEL_EXTENDED_DATA support - Web auth uses DataProvider instead of direct SQL - User home directory from provider (per-user isolation) - PostgreSQL auth provider for SFTPGo compatibility
This commit is contained in:
@@ -6,6 +6,9 @@ use crate::ssh_server::packet::{SshPacket, PacketType};
|
||||
use crate::ssh_server::kex::{KexResult, KexProposal};
|
||||
use crate::ssh_server::kex_complete::{KexState};
|
||||
use crate::ssh_server::auth::{AuthHandler, AuthResult};
|
||||
use crate::provider::sqlite::SqliteProvider;
|
||||
use crate::provider::pg::PgProvider;
|
||||
use crate::provider::DataProvider;
|
||||
use crate::ssh_server::channel::{ChannelManager};
|
||||
use crate::ssh_server::cipher::{EncryptionContext, EncryptedPacket};
|
||||
use crate::ssh_server::ssh_security_config::SshSecurityConfig; // Phase 13.1
|
||||
@@ -13,6 +16,7 @@ use crate::ssh_server::port_forward::PortForwardManager; // Phase 13
|
||||
use anyhow::{Result, anyhow};
|
||||
use log::{info, warn, error, debug};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::path::PathBuf;
|
||||
use std::thread;
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::{Arc, Mutex}; // Phase 13: 端口转发线程同步
|
||||
@@ -22,6 +26,7 @@ pub struct SshServerConfig {
|
||||
pub port: u16,
|
||||
pub bind_address: String,
|
||||
pub security_config: SshSecurityConfig, // Phase 13.1: 企业级安全配置
|
||||
pub pg_conn: Option<String>, // PostgreSQL连接字符串(SFTPGo兼容认证)
|
||||
}
|
||||
|
||||
impl Default for SshServerConfig {
|
||||
@@ -30,6 +35,7 @@ impl Default for SshServerConfig {
|
||||
port: 2024,
|
||||
bind_address: "127.0.0.1".to_string(),
|
||||
security_config: SshSecurityConfig::enterprise_default(), // Phase 13.1
|
||||
pg_conn: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +48,7 @@ impl SshServerConfig {
|
||||
port: 2024,
|
||||
bind_address: "127.0.0.1".to_string(),
|
||||
security_config: config,
|
||||
pg_conn: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -73,6 +80,7 @@ impl SshServer {
|
||||
self.config.security_config.max_sessions);
|
||||
|
||||
let security_config = self.security_config.clone(); // Phase 13.1: 共享安全配置
|
||||
let pg_conn = self.config.pg_conn.clone();
|
||||
|
||||
for stream in listener.incoming() {
|
||||
match stream {
|
||||
@@ -81,9 +89,10 @@ impl SshServer {
|
||||
info!("New SSH connection from {}", client_addr);
|
||||
|
||||
let security_config_clone = security_config.clone(); // Phase 13.1
|
||||
let pg_conn_clone = pg_conn.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
if let Err(e) = handle_connection_complete(stream, security_config_clone) { // Phase 13.1
|
||||
if let Err(e) = handle_connection_complete(stream, security_config_clone, pg_conn_clone) { // Phase 13.1
|
||||
error!("Connection error: {}", e);
|
||||
}
|
||||
});
|
||||
@@ -99,7 +108,7 @@ impl SshServer {
|
||||
}
|
||||
|
||||
/// 处理完整SSH连接(Phase 1-13完整流程)
|
||||
fn handle_connection_complete(stream: TcpStream, security_config: Arc<Mutex<SshSecurityConfig>>) -> Result<()> {
|
||||
fn handle_connection_complete(stream: TcpStream, security_config: Arc<Mutex<SshSecurityConfig>>, pg_conn: Option<String>) -> Result<()> {
|
||||
info!("Handling client connection (Phase 1-13 complete flow with port forwarding)");
|
||||
|
||||
// Phase 13.1: 增加活动会话数
|
||||
@@ -122,13 +131,22 @@ fn handle_connection_complete(stream: TcpStream, security_config: Arc<Mutex<SshS
|
||||
let mut encryption_ctx = perform_complete_kex_exchange(&mut stream, client_version.clone(), kex_result, server_kexinit, client_kexinit)?;
|
||||
info!("Key exchange completed, encryption channel ready");
|
||||
|
||||
// Phase 5: SSH认证(参考OpenSSH auth2.c)
|
||||
let mut auth_handler = AuthHandler::new()?;
|
||||
// Phase 5: SSH认证(SFTPGo兼容 — PostgreSQL或SQLite)
|
||||
let provider: Box<dyn DataProvider> = if let Some(ref conn_str) = pg_conn {
|
||||
info!("Using PostgreSQL auth provider (SFTPGo-compatible): {}", conn_str);
|
||||
Box::new(PgProvider::new(conn_str)
|
||||
.map_err(|e| anyhow!("Failed to init PgProvider: {}", e))?)
|
||||
} else {
|
||||
info!("Using SQLite auth provider");
|
||||
Box::new(SqliteProvider::new("data/auth.sqlite")
|
||||
.map_err(|e| anyhow!("Failed to init SqliteProvider: {}", e))?)
|
||||
};
|
||||
let mut auth_handler = AuthHandler::new(provider);
|
||||
let auth_user = perform_ssh_auth(&mut stream, &mut auth_handler, &mut encryption_ctx)?;
|
||||
info!("SSH authentication succeeded: user={}", auth_user);
|
||||
info!("SSH authentication succeeded: user={}", auth_user.username);
|
||||
|
||||
// Phase 6: SSH Channel管理(参考OpenSSH channel.c)
|
||||
let mut channel_manager = ChannelManager::new();
|
||||
let mut channel_manager = ChannelManager::new(auth_user.home_dir.clone());
|
||||
|
||||
// Phase 13: PortForwardManager初始化
|
||||
let mut port_forward_manager = PortForwardManager::new();
|
||||
@@ -226,11 +244,16 @@ fn perform_complete_kex_exchange(
|
||||
}
|
||||
|
||||
/// SSH认证流程(Phase 5)
|
||||
pub struct AuthUser {
|
||||
pub username: String,
|
||||
pub home_dir: PathBuf,
|
||||
}
|
||||
|
||||
fn perform_ssh_auth(
|
||||
stream: &mut TcpStream,
|
||||
auth_handler: &mut AuthHandler,
|
||||
encryption_ctx: &mut EncryptionContext,
|
||||
) -> Result<String> {
|
||||
) -> Result<AuthUser> {
|
||||
info!("Starting SSH authentication");
|
||||
info!("Encryption context: key_ctos_len={}, key_stoc_len={}, iv_ctos_len={}, iv_stoc_len={}",
|
||||
encryption_ctx.encryption_key_ctos.len(),
|
||||
@@ -279,6 +302,8 @@ fn perform_ssh_auth(
|
||||
encrypted_accept.write(stream)?;
|
||||
info!("Sent encrypted SSH_MSG_SERVICE_ACCEPT");
|
||||
|
||||
let session_id = encryption_ctx.session_id.clone();
|
||||
|
||||
loop {
|
||||
let auth_packet = EncryptedPacket::read(stream, encryption_ctx, true)?; // Reading from client, use cipher_ctos
|
||||
let auth_payload = auth_packet.payload();
|
||||
@@ -286,7 +311,7 @@ fn perform_ssh_auth(
|
||||
|
||||
let auth_request = SshPacket::new(auth_payload.to_vec());
|
||||
|
||||
match auth_handler.handle_userauth_request(&auth_request)? {
|
||||
match auth_handler.handle_userauth_request(&auth_request, &session_id)? {
|
||||
AuthResult::Success => {
|
||||
let success_payload = vec![PacketType::SSH_MSG_USERAUTH_SUCCESS as u8];
|
||||
let encrypted_success = EncryptedPacket::new(
|
||||
@@ -297,7 +322,16 @@ fn perform_ssh_auth(
|
||||
encrypted_success.write(stream)?;
|
||||
info!("Sent encrypted SSH_MSG_USERAUTH_SUCCESS");
|
||||
|
||||
return Ok("demo".to_string());
|
||||
// Extract username from auth request
|
||||
let user = extract_username_from_auth_request(&auth_request)
|
||||
.unwrap_or_else(|_| "unknown".to_string());
|
||||
let home_dir = auth_handler.get_home_dir(&user)
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("/Users/accusys/markbase"));
|
||||
info!("Auth success: user={}, home_dir={:?}", user, home_dir);
|
||||
return Ok(AuthUser { username: user, home_dir });
|
||||
}
|
||||
AuthResult::Failure(message) => {
|
||||
// message包含可用的认证方法列表(如"password,publickey")
|
||||
@@ -519,7 +553,9 @@ fn handle_ssh_service_loop(
|
||||
}
|
||||
Some(&pt) if pt == PacketType::SSH_MSG_CHANNEL_EOF as u8 => {
|
||||
info!("Received SSH_MSG_CHANNEL_EOF");
|
||||
// EOF means client won't send more data, just acknowledge and continue
|
||||
// Phase 17: EOF means client won't send more data → close child stdin
|
||||
// (Essential for SCP upload where scp -t waits for EOF on stdin)
|
||||
channel_manager.close_child_stdin();
|
||||
}
|
||||
Some(&pt) if pt == PacketType::SSH_MSG_DISCONNECT as u8 => {
|
||||
info!("Received SSH_MSG_DISCONNECT");
|
||||
@@ -543,12 +579,27 @@ fn handle_ssh_service_loop(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 从SSH_MSG_USERAUTH_REQUEST payload中提取用户名
|
||||
fn extract_username_from_auth_request(packet: &crate::ssh_server::packet::SshPacket) -> Result<String> {
|
||||
let payload = &packet.payload;
|
||||
if payload.len() < 5 {
|
||||
return Err(anyhow!("Auth request too short"));
|
||||
}
|
||||
let name_len = u32::from_be_bytes([payload[1], payload[2], payload[3], payload[4]]) as usize;
|
||||
if payload.len() < 5 + name_len {
|
||||
return Err(anyhow!("Auth request truncated"));
|
||||
}
|
||||
let username = String::from_utf8_lossy(&payload[5..5 + name_len]).to_string();
|
||||
Ok(username)
|
||||
}
|
||||
|
||||
/// SSH服务器CLI入口
|
||||
pub fn run_ssh_server(port: Option<u16>) -> Result<()> {
|
||||
pub fn run_ssh_server(port: Option<u16>, pg_conn: Option<&str>) -> Result<()> {
|
||||
let config = SshServerConfig {
|
||||
port: port.unwrap_or(2024),
|
||||
bind_address: "127.0.0.1".to_string(),
|
||||
security_config: SshSecurityConfig::enterprise_default(), // Phase 13.1: 添加安全配置
|
||||
pg_conn: pg_conn.map(|s| s.to_string()),
|
||||
};
|
||||
|
||||
let server = SshServer::new(config);
|
||||
|
||||
Reference in New Issue
Block a user