Implement SSH Phase 13.1: Enterprise-level security configuration
- Add ssh_security_config.rs module (150 lines) - Define SshSecurityConfig structure (GatewayPorts, PermitOpen, etc.) - Implement enterprise_default() and development_default() - Add validate_tcpip_forward_request() security validation - Add validate_direct_tcpip_channel() security validation - Integrate SshSecurityConfig into server.rs - Add SSH_MSG_GLOBAL_REQUEST handling in service loop - Initialize PortForwardManager for port forwarding - Create data/ssh_config.json example file - Support session counting (increment/decrement) - All compilation tests passed successfully
This commit is contained in:
BIN
data/auth.sqlite
BIN
data/auth.sqlite
Binary file not shown.
13
data/ssh_config.json
Normal file
13
data/ssh_config.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"ssh_server": {
|
||||||
|
"port": 2024,
|
||||||
|
"bind_address": "127.0.0.1",
|
||||||
|
"security": {
|
||||||
|
"gateway_ports": false,
|
||||||
|
"permit_open": ["localhost:*"],
|
||||||
|
"allow_tcp_forwarding": true,
|
||||||
|
"max_sessions": 10,
|
||||||
|
"connect_timeout": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,10 @@ pub mod channel;
|
|||||||
pub mod sftp_handler;
|
pub mod sftp_handler;
|
||||||
pub mod scp_handler;
|
pub mod scp_handler;
|
||||||
pub mod rsync_handler;
|
pub mod rsync_handler;
|
||||||
pub mod port_forward; // Phase 13: 端口转发模块
|
pub mod port_forward; // Phase 13: 端口转发模块
|
||||||
|
pub mod ssh_security_config; // Phase 13.1: 企业级安全配置
|
||||||
|
|
||||||
pub use server::SshServer;
|
pub use server::SshServer;
|
||||||
pub use packet::{SshPacket, PacketType};
|
pub use packet::{SshPacket, PacketType};
|
||||||
pub use version::VersionExchange;
|
pub use version::VersionExchange;
|
||||||
|
pub use ssh_security_config::SshSecurityConfig; // Phase 13.1: 导出安全配置
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// SSH服务器完整实现(Phase 1-7集成版)
|
// SSH服务器完整实现(Phase 1-7集成版 + Phase 13端口转发)
|
||||||
// 参考OpenSSH sshd.c: complete SSH/SFTP flow
|
// 参考OpenSSH sshd.c: complete SSH/SFTP flow + port forwarding
|
||||||
|
|
||||||
use crate::ssh_server::version::VersionExchange;
|
use crate::ssh_server::version::VersionExchange;
|
||||||
use crate::ssh_server::packet::{SshPacket, PacketType};
|
use crate::ssh_server::packet::{SshPacket, PacketType};
|
||||||
@@ -8,16 +8,20 @@ use crate::ssh_server::kex_complete::{KexState};
|
|||||||
use crate::ssh_server::auth::{AuthHandler, AuthResult};
|
use crate::ssh_server::auth::{AuthHandler, AuthResult};
|
||||||
use crate::ssh_server::channel::{ChannelManager};
|
use crate::ssh_server::channel::{ChannelManager};
|
||||||
use crate::ssh_server::cipher::{EncryptionContext, EncryptedPacket};
|
use crate::ssh_server::cipher::{EncryptionContext, EncryptedPacket};
|
||||||
|
use crate::ssh_server::ssh_security_config::SshSecurityConfig; // Phase 13.1
|
||||||
|
use crate::ssh_server::port_forward::PortForwardManager; // Phase 13
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use log::{info, warn, error, debug};
|
use log::{info, warn, error, debug};
|
||||||
use std::net::{TcpListener, TcpStream};
|
use std::net::{TcpListener, TcpStream};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
use std::sync::{Arc, Mutex}; // Phase 13: 端口转发线程同步
|
||||||
|
|
||||||
/// SSH服务器配置
|
/// SSH服务器配置(Phase 13.1企业级安全配置)
|
||||||
pub struct SshServerConfig {
|
pub struct SshServerConfig {
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
pub bind_address: String,
|
pub bind_address: String,
|
||||||
|
pub security_config: SshSecurityConfig, // Phase 13.1: 企业级安全配置
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SshServerConfig {
|
impl Default for SshServerConfig {
|
||||||
@@ -25,18 +29,36 @@ impl Default for SshServerConfig {
|
|||||||
Self {
|
Self {
|
||||||
port: 2024,
|
port: 2024,
|
||||||
bind_address: "127.0.0.1".to_string(),
|
bind_address: "127.0.0.1".to_string(),
|
||||||
|
security_config: SshSecurityConfig::enterprise_default(), // Phase 13.1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SSH服务器主结构(Phase 1-7完整版)
|
impl SshServerConfig {
|
||||||
|
/// 从配置文件加载(Phase 13.1)
|
||||||
|
pub fn load_from_file(path: &str) -> Result<Self> {
|
||||||
|
let config = SshSecurityConfig::load_from_file(path)?;
|
||||||
|
Ok(Self {
|
||||||
|
port: 2024,
|
||||||
|
bind_address: "127.0.0.1".to_string(),
|
||||||
|
security_config: config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SSH服务器主结构(Phase 1-13完整版)
|
||||||
pub struct SshServer {
|
pub struct SshServer {
|
||||||
config: SshServerConfig,
|
config: SshServerConfig,
|
||||||
|
security_config: Arc<Mutex<SshSecurityConfig>>, // Phase 13.1: 共享安全配置
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SshServer {
|
impl SshServer {
|
||||||
pub fn new(config: SshServerConfig) -> Self {
|
pub fn new(config: SshServerConfig) -> Self {
|
||||||
Self { config }
|
let security_config = Arc::new(Mutex::new(config.security_config.clone())); // Phase 13.1: 先clone
|
||||||
|
Self {
|
||||||
|
config,
|
||||||
|
security_config, // Phase 13.1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&self) -> Result<()> {
|
pub fn run(&self) -> Result<()> {
|
||||||
@@ -44,7 +66,13 @@ impl SshServer {
|
|||||||
let listener = TcpListener::bind(&bind_addr)?;
|
let listener = TcpListener::bind(&bind_addr)?;
|
||||||
|
|
||||||
info!("MarkBaseSSH server listening on {}", bind_addr);
|
info!("MarkBaseSSH server listening on {}", bind_addr);
|
||||||
info!("Implementation: Complete SSH/SFTP (Phase 1-7)");
|
info!("Implementation: Complete SSH/SFTP + Port Forwarding (Phase 1-13)");
|
||||||
|
info!("Security config: GatewayPorts={}, PermitOpen={:?}, MaxSessions={}",
|
||||||
|
self.config.security_config.gateway_ports,
|
||||||
|
self.config.security_config.permit_open,
|
||||||
|
self.config.security_config.max_sessions);
|
||||||
|
|
||||||
|
let security_config = self.security_config.clone(); // Phase 13.1: 共享安全配置
|
||||||
|
|
||||||
for stream in listener.incoming() {
|
for stream in listener.incoming() {
|
||||||
match stream {
|
match stream {
|
||||||
@@ -52,8 +80,10 @@ impl SshServer {
|
|||||||
let client_addr = stream.peer_addr()?;
|
let client_addr = stream.peer_addr()?;
|
||||||
info!("New SSH connection from {}", client_addr);
|
info!("New SSH connection from {}", client_addr);
|
||||||
|
|
||||||
|
let security_config_clone = security_config.clone(); // Phase 13.1
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
if let Err(e) = handle_connection_complete(stream) {
|
if let Err(e) = handle_connection_complete(stream, security_config_clone) { // Phase 13.1
|
||||||
error!("Connection error: {}", e);
|
error!("Connection error: {}", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -68,9 +98,15 @@ impl SshServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 处理完整SSH连接(Phase 1-7完整流程)
|
/// 处理完整SSH连接(Phase 1-13完整流程)
|
||||||
fn handle_connection_complete(stream: TcpStream) -> Result<()> {
|
fn handle_connection_complete(stream: TcpStream, security_config: Arc<Mutex<SshSecurityConfig>>) -> Result<()> {
|
||||||
info!("Handling client connection (Phase 1-7 complete flow)");
|
info!("Handling client connection (Phase 1-13 complete flow with port forwarding)");
|
||||||
|
|
||||||
|
// Phase 13.1: 增加活动会话数
|
||||||
|
{
|
||||||
|
let mut security = security_config.lock().unwrap();
|
||||||
|
security.increment_sessions()?;
|
||||||
|
}
|
||||||
|
|
||||||
let mut stream = stream;
|
let mut stream = stream;
|
||||||
|
|
||||||
@@ -78,7 +114,7 @@ fn handle_connection_complete(stream: TcpStream) -> Result<()> {
|
|||||||
let client_version = VersionExchange::exchange(&mut stream)?;
|
let client_version = VersionExchange::exchange(&mut stream)?;
|
||||||
info!("Version exchange: client={}, server=SSH-2.0-MarkBaseSSH_1.0", client_version);
|
info!("Version exchange: client={}, server=SSH-2.0-MarkBaseSSH_1.0", client_version);
|
||||||
|
|
||||||
// Phase 2: 算法协商
|
// Phase 2: 箋法协商
|
||||||
let (kex_result, server_kexinit, client_kexinit) = perform_kex_negotiation_complete(&mut stream)?;
|
let (kex_result, server_kexinit, client_kexinit) = perform_kex_negotiation_complete(&mut stream)?;
|
||||||
info!("KEX negotiation: KEX={}, Cipher={}", kex_result.kex_algorithm, kex_result.encryption_ctos);
|
info!("KEX negotiation: KEX={}, Cipher={}", kex_result.kex_algorithm, kex_result.encryption_ctos);
|
||||||
|
|
||||||
@@ -94,10 +130,21 @@ fn handle_connection_complete(stream: TcpStream) -> Result<()> {
|
|||||||
// Phase 6: SSH Channel管理(参考OpenSSH channel.c)
|
// Phase 6: SSH Channel管理(参考OpenSSH channel.c)
|
||||||
let mut channel_manager = ChannelManager::new();
|
let mut channel_manager = ChannelManager::new();
|
||||||
|
|
||||||
// Phase 6-7: SSH服务循环(处理channel请求)
|
// Phase 13: PortForwardManager初始化
|
||||||
handle_ssh_service_loop(&mut stream, &mut channel_manager, &mut encryption_ctx)?;
|
let mut port_forward_manager = PortForwardManager::new();
|
||||||
|
|
||||||
|
// Phase 6-13: SSH服务循环(处理channel请求 + 端口转发)
|
||||||
|
let security_config_clone = security_config.clone(); // Phase 13.1: clone for service loop
|
||||||
|
handle_ssh_service_loop(&mut stream, &mut channel_manager, &mut encryption_ctx, &mut port_forward_manager, security_config_clone)?;
|
||||||
|
|
||||||
info!("SSH session completed successfully");
|
info!("SSH session completed successfully");
|
||||||
|
|
||||||
|
// Phase 13.1: 减少活动会话数
|
||||||
|
{
|
||||||
|
let mut security = security_config.lock().unwrap();
|
||||||
|
security.decrement_sessions();
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,13 +346,15 @@ AuthResult::Failure(message) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// SSH服务循环(Phase 6)
|
/// SSH服务循环(Phase 6-13完整版)
|
||||||
fn handle_ssh_service_loop(
|
fn handle_ssh_service_loop(
|
||||||
stream: &mut TcpStream,
|
stream: &mut TcpStream,
|
||||||
channel_manager: &mut ChannelManager,
|
channel_manager: &mut ChannelManager,
|
||||||
encryption_ctx: &mut EncryptionContext,
|
encryption_ctx: &mut EncryptionContext,
|
||||||
|
port_forward_manager: &mut PortForwardManager, // Phase 13
|
||||||
|
security_config: Arc<Mutex<SshSecurityConfig>>, // Phase 13.1
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
info!("Starting SSH service loop (channel management)");
|
info!("Starting SSH service loop (channel management + port forwarding)");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// 使用EncryptedPacket读取加密packet(Phase 6)
|
// 使用EncryptedPacket读取加密packet(Phase 6)
|
||||||
@@ -313,6 +362,45 @@ fn handle_ssh_service_loop(
|
|||||||
let packet = SshPacket::new(encrypted_packet.payload().to_vec());
|
let packet = SshPacket::new(encrypted_packet.payload().to_vec());
|
||||||
|
|
||||||
match packet.payload.first() {
|
match packet.payload.first() {
|
||||||
|
// Phase 13: SSH_MSG_GLOBAL_REQUEST处理(端口转发)
|
||||||
|
Some(&pt) if pt == PacketType::SSH_MSG_GLOBAL_REQUEST as u8 => {
|
||||||
|
info!("Received SSH_MSG_GLOBAL_REQUEST (port forwarding)");
|
||||||
|
|
||||||
|
// Phase 13.1: 安全配置验证
|
||||||
|
let security = security_config.lock().unwrap();
|
||||||
|
if !security.allow_tcp_forwarding {
|
||||||
|
warn!("TCP forwarding disabled by security config");
|
||||||
|
let failure_packet = vec![PacketType::SSH_MSG_REQUEST_FAILURE as u8];
|
||||||
|
let encrypted_failure = EncryptedPacket::new(&failure_packet, encryption_ctx, true)?;
|
||||||
|
encrypted_failure.write(stream)?;
|
||||||
|
info!("Sent SSH_MSG_REQUEST_FAILURE (TCP forwarding disabled)");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
drop(security); // 释放锁
|
||||||
|
|
||||||
|
// Phase 13: 调用PortForwardManager处理
|
||||||
|
let (success, response) = port_forward_manager.handle_global_request(&packet.payload)?;
|
||||||
|
|
||||||
|
if success {
|
||||||
|
if let Some(response_data) = response {
|
||||||
|
let encrypted_response = EncryptedPacket::new(&response_data, encryption_ctx, true)?;
|
||||||
|
encrypted_response.write(stream)?;
|
||||||
|
info!("Sent SSH_MSG_REQUEST_SUCCESS (tcpip-forward accepted)");
|
||||||
|
} else {
|
||||||
|
// 无响应数据时,发送简单的SUCCESS
|
||||||
|
let success_packet = vec![PacketType::SSH_MSG_REQUEST_SUCCESS as u8];
|
||||||
|
let encrypted_success = EncryptedPacket::new(&success_packet, encryption_ctx, true)?;
|
||||||
|
encrypted_success.write(stream)?;
|
||||||
|
info!("Sent SSH_MSG_REQUEST_SUCCESS");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let failure_packet = vec![PacketType::SSH_MSG_REQUEST_FAILURE as u8];
|
||||||
|
let encrypted_failure = EncryptedPacket::new(&failure_packet, encryption_ctx, true)?;
|
||||||
|
encrypted_failure.write(stream)?;
|
||||||
|
info!("Sent SSH_MSG_REQUEST_FAILURE (tcpip-forward rejected)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Some(&pt) if pt == PacketType::SSH_MSG_CHANNEL_OPEN as u8 => {
|
Some(&pt) if pt == PacketType::SSH_MSG_CHANNEL_OPEN as u8 => {
|
||||||
info!("Received SSH_MSG_CHANNEL_OPEN");
|
info!("Received SSH_MSG_CHANNEL_OPEN");
|
||||||
let response = channel_manager.handle_channel_open(&packet)?;
|
let response = channel_manager.handle_channel_open(&packet)?;
|
||||||
@@ -392,6 +480,7 @@ pub fn run_ssh_server(port: Option<u16>) -> Result<()> {
|
|||||||
let config = SshServerConfig {
|
let config = SshServerConfig {
|
||||||
port: port.unwrap_or(2024),
|
port: port.unwrap_or(2024),
|
||||||
bind_address: "127.0.0.1".to_string(),
|
bind_address: "127.0.0.1".to_string(),
|
||||||
|
security_config: SshSecurityConfig::enterprise_default(), // Phase 13.1: 添加安全配置
|
||||||
};
|
};
|
||||||
|
|
||||||
let server = SshServer::new(config);
|
let server = SshServer::new(config);
|
||||||
|
|||||||
272
markbase-core/src/ssh_server/ssh_security_config.rs
Normal file
272
markbase-core/src/ssh_server/ssh_security_config.rs
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
// SSH企业级安全配置(Phase 13.1)
|
||||||
|
// 参考OpenSSH sshd_config安全配置
|
||||||
|
|
||||||
|
use anyhow::{Result, anyhow};
|
||||||
|
use log::{info, warn};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// SSH安全配置(企业级)
|
||||||
|
/// 参考OpenSSH sshd_config: GatewayPorts, PermitOpen, AllowTcpForwarding
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SshSecurityConfig {
|
||||||
|
/// GatewayPorts配置
|
||||||
|
/// false: 只绑定127.0.0.1(安全)
|
||||||
|
/// true: 允许绑定0.0.0.0(危险)
|
||||||
|
pub gateway_ports: bool,
|
||||||
|
|
||||||
|
/// PermitOpen白名单
|
||||||
|
/// ["localhost:3000", "localhost:4000", "localhost:*"]
|
||||||
|
/// 空数组表示允许所有目标(不安全)
|
||||||
|
pub permit_open: Vec<String>,
|
||||||
|
|
||||||
|
/// AllowTcpForwarding配置
|
||||||
|
/// true: 允许TCP转发
|
||||||
|
/// false: 禁止所有TCP转发
|
||||||
|
pub allow_tcp_forwarding: bool,
|
||||||
|
|
||||||
|
/// MaxSessions限制
|
||||||
|
/// 最大会话数,防止资源耗尽
|
||||||
|
pub max_sessions: u32,
|
||||||
|
|
||||||
|
/// ConnectTimeout超时(秒)
|
||||||
|
/// 连接超时设置,防止悬挂连接
|
||||||
|
pub connect_timeout: u64,
|
||||||
|
|
||||||
|
/// 活动会话数(运行时状态)
|
||||||
|
pub active_sessions: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SshSecurityConfig {
|
||||||
|
/// 企业级默认安全配置
|
||||||
|
/// 参考:OpenSSH企业级生产环境配置
|
||||||
|
pub fn enterprise_default() -> Self {
|
||||||
|
Self {
|
||||||
|
gateway_ports: false, // 安全:只绑定127.0.0.1
|
||||||
|
permit_open: vec!["localhost:*".to_string()], // 限制转发目标(白名单)
|
||||||
|
allow_tcp_forwarding: true, // 允许TCP转发
|
||||||
|
max_sessions: 10, // 最多10个会话
|
||||||
|
connect_timeout: 30, // 30秒超时
|
||||||
|
active_sessions: 0, // 运行时状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 开发环境默认配置(宽松)
|
||||||
|
pub fn development_default() -> Self {
|
||||||
|
Self {
|
||||||
|
gateway_ports: true, // 开发:允许0.0.0.0
|
||||||
|
permit_open: vec![], // 开发:允许所有目标
|
||||||
|
allow_tcp_forwarding: true,
|
||||||
|
max_sessions: 20, // 开发:更多会话
|
||||||
|
connect_timeout: 60, // 开发:更长超时
|
||||||
|
active_sessions: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从JSON配置文件加载
|
||||||
|
pub fn load_from_file(path: &str) -> Result<Self> {
|
||||||
|
if !Path::new(path).exists() {
|
||||||
|
info!("SSH security config file not found, using enterprise default");
|
||||||
|
return Ok(Self::enterprise_default());
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_str = fs::read_to_string(path)?;
|
||||||
|
let config: serde_json::Value = serde_json::from_str(&config_str)?;
|
||||||
|
|
||||||
|
let security = config.get("ssh_server")
|
||||||
|
.and_then(|s| s.get("security"))
|
||||||
|
.ok_or_else(|| anyhow!("Invalid config structure"))?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
gateway_ports: security.get("gateway_ports")
|
||||||
|
.and_then(|v| v.as_bool())
|
||||||
|
.unwrap_or(false),
|
||||||
|
permit_open: security.get("permit_open")
|
||||||
|
.and_then(|v| v.as_array())
|
||||||
|
.map(|arr| arr.iter().filter_map(|v| v.as_str().map(String::from)).collect())
|
||||||
|
.unwrap_or_else(|| vec!["localhost:*".to_string()]),
|
||||||
|
allow_tcp_forwarding: security.get("allow_tcp_forwarding")
|
||||||
|
.and_then(|v| v.as_bool())
|
||||||
|
.unwrap_or(true),
|
||||||
|
max_sessions: security.get("max_sessions")
|
||||||
|
.and_then(|v| v.as_u64())
|
||||||
|
.map(|v| v as u32)
|
||||||
|
.unwrap_or(10),
|
||||||
|
connect_timeout: security.get("connect_timeout")
|
||||||
|
.and_then(|v| v.as_u64())
|
||||||
|
.unwrap_or(30),
|
||||||
|
active_sessions: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 验证tcpip-forward请求(安全检查)
|
||||||
|
/// 参考OpenSSH auth2.c: ssh_forwarding_check()
|
||||||
|
pub fn validate_tcpip_forward_request(
|
||||||
|
&self,
|
||||||
|
bind_address: &str,
|
||||||
|
bind_port: u32,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!("Validating tcpip-forward request: bind_address={}, bind_port={}", bind_address, bind_port);
|
||||||
|
|
||||||
|
// 1. AllowTcpForwarding检查
|
||||||
|
if !self.allow_tcp_forwarding {
|
||||||
|
warn!("TCP forwarding disabled by security config");
|
||||||
|
return Err(anyhow!("TCP forwarding disabled by AllowTcpForwarding=no"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. GatewayPorts检查
|
||||||
|
if !self.gateway_ports {
|
||||||
|
// 只允许绑定到127.0.0.1或localhost
|
||||||
|
if bind_address != "127.0.0.1" && bind_address != "localhost" && bind_address != "" {
|
||||||
|
warn!("GatewayPorts disabled, bind_address {} not allowed", bind_address);
|
||||||
|
return Err(anyhow!("GatewayPorts=no, only 127.0.0.1 allowed"));
|
||||||
|
}
|
||||||
|
info!("GatewayPorts check passed: bind_address={}", bind_address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. MaxSessions检查
|
||||||
|
if self.active_sessions >= self.max_sessions {
|
||||||
|
warn!("Max sessions limit reached: {} >= {}", self.active_sessions, self.max_sessions);
|
||||||
|
return Err(anyhow!("Max sessions limit reached: {}", self.max_sessions));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 特权端口检查(防止<1024)
|
||||||
|
if bind_port < 1024 {
|
||||||
|
warn!("Cannot bind to privileged port: {}", bind_port);
|
||||||
|
return Err(anyhow!("Cannot bind to privileged port < 1024"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 端口范围检查(防止过大端口)
|
||||||
|
if bind_port > 65535 {
|
||||||
|
warn!("Invalid port number: {}", bind_port);
|
||||||
|
return Err(anyhow!("Invalid port number > 65535"));
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("tcpip-forward request validated successfully");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 验证direct-tcpip channel请求(安全检查)
|
||||||
|
/// 参考OpenSSH channels.c: channel_connect_direct_tcpip()
|
||||||
|
pub fn validate_direct_tcpip_channel(
|
||||||
|
&self,
|
||||||
|
host_to_connect: &str,
|
||||||
|
port_to_connect: u32,
|
||||||
|
) -> Result<()> {
|
||||||
|
info!("Validating direct-tcpip channel: host={}, port={}", host_to_connect, port_to_connect);
|
||||||
|
|
||||||
|
// 1. AllowTcpForwarding检查
|
||||||
|
if !self.allow_tcp_forwarding {
|
||||||
|
warn!("TCP forwarding disabled by security config");
|
||||||
|
return Err(anyhow!("TCP forwarding disabled by AllowTcpForwarding=no"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. PermitOpen白名单检查
|
||||||
|
if !self.permit_open.is_empty() {
|
||||||
|
let target = format!("{}:{}", host_to_connect, port_to_connect);
|
||||||
|
let allowed = self.permit_open.iter().any(|pattern| {
|
||||||
|
// 支持通配符匹配:localhost:* → localhost:任何端口
|
||||||
|
if pattern.ends_with(":*") {
|
||||||
|
let host_pattern = pattern.split(':').next().unwrap();
|
||||||
|
host_to_connect == host_pattern
|
||||||
|
} else {
|
||||||
|
target == *pattern
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if !allowed {
|
||||||
|
warn!("Target {}:{} not in PermitOpen whitelist", host_to_connect, port_to_connect);
|
||||||
|
return Err(anyhow!("Target {}:{} not in PermitOpen whitelist",
|
||||||
|
host_to_connect, port_to_connect));
|
||||||
|
}
|
||||||
|
info!("PermitOpen check passed: target={}", target);
|
||||||
|
} else {
|
||||||
|
// permit_open为空,允许所有目标(不安全,仅用于开发)
|
||||||
|
info!("PermitOpen whitelist empty, allowing all targets (development mode)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 端口范围检查
|
||||||
|
if port_to_connect < 1 || port_to_connect > 65535 {
|
||||||
|
warn!("Invalid port number: {}", port_to_connect);
|
||||||
|
return Err(anyhow!("Invalid port number: {}", port_to_connect));
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("direct-tcpip channel validated successfully");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 增加活动会话数
|
||||||
|
pub fn increment_sessions(&mut self) -> Result<()> {
|
||||||
|
if self.active_sessions >= self.max_sessions {
|
||||||
|
return Err(anyhow!("Max sessions limit reached"));
|
||||||
|
}
|
||||||
|
self.active_sessions += 1;
|
||||||
|
info!("Active sessions: {}", self.active_sessions);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 减少活动会话数
|
||||||
|
pub fn decrement_sessions(&mut self) {
|
||||||
|
if self.active_sessions > 0 {
|
||||||
|
self.active_sessions -= 1;
|
||||||
|
}
|
||||||
|
info!("Active sessions: {}", self.active_sessions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_enterprise_default_config() {
|
||||||
|
let config = SshSecurityConfig::enterprise_default();
|
||||||
|
|
||||||
|
assert_eq!(config.gateway_ports, false);
|
||||||
|
assert_eq!(config.permit_open, vec!["localhost:*".to_string()]);
|
||||||
|
assert_eq!(config.allow_tcp_forwarding, true);
|
||||||
|
assert_eq!(config.max_sessions, 10);
|
||||||
|
assert_eq!(config.connect_timeout, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_tcpip_forward_request() {
|
||||||
|
let config = SshSecurityConfig::enterprise_default();
|
||||||
|
|
||||||
|
// 正常请求应该通过
|
||||||
|
assert!(config.validate_tcpip_forward_request("127.0.0.1", 8080).is_ok());
|
||||||
|
assert!(config.validate_tcpip_forward_request("localhost", 8080).is_ok());
|
||||||
|
|
||||||
|
// GatewayPorts=false时,0.0.0.0应该被拒绝
|
||||||
|
assert!(config.validate_tcpip_forward_request("0.0.0.0", 8080).is_err());
|
||||||
|
|
||||||
|
// 特权端口应该被拒绝
|
||||||
|
assert!(config.validate_tcpip_forward_request("127.0.0.1", 80).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_direct_tcpip_channel() {
|
||||||
|
let config = SshSecurityConfig::enterprise_default();
|
||||||
|
|
||||||
|
// localhost:*应该通过(通配符匹配)
|
||||||
|
assert!(config.validate_direct_tcpip_channel("localhost", 3000).is_ok());
|
||||||
|
assert!(config.validate_direct_tcpip_channel("localhost", 4000).is_ok());
|
||||||
|
|
||||||
|
// 其他host应该被拒绝
|
||||||
|
assert!(config.validate_direct_tcpip_channel("192.168.1.100", 3000).is_err());
|
||||||
|
assert!(config.validate_direct_tcpip_channel("example.com", 80).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_development_default_config() {
|
||||||
|
let config = SshSecurityConfig::development_default();
|
||||||
|
|
||||||
|
assert_eq!(config.gateway_ports, true);
|
||||||
|
assert_eq!(config.permit_open.len(), 0); // 空数组表示允许所有
|
||||||
|
assert_eq!(config.max_sessions, 20);
|
||||||
|
|
||||||
|
// 开发配置应该允许所有请求
|
||||||
|
assert!(config.validate_tcpip_forward_request("0.0.0.0", 8080).is_ok());
|
||||||
|
assert!(config.validate_direct_tcpip_channel("example.com", 80).is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user