VFS/DataProvider/Config refactoring + SSH public key authentication
Some checks failed
Test / test (push) Has been cancelled
Test / build (push) Has been cancelled

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:
Warren
2026-06-18 23:35:18 +08:00
parent 83fb0de78a
commit f90e4f496c
25 changed files with 2039 additions and 612 deletions

View File

@@ -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);