Skip to main content

aster/sandbox/
config.rs

1//! 沙箱配置
2//!
3//! 提供沙箱配置管理、预设、验证功能
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::path::PathBuf;
8use std::sync::{Arc, RwLock};
9
10/// 资源限制
11#[derive(Debug, Clone, Default, Serialize, Deserialize)]
12pub struct ResourceLimits {
13    /// 最大内存(字节)
14    pub max_memory: Option<u64>,
15    /// 最大 CPU 使用率 (0-100)
16    pub max_cpu: Option<u32>,
17    /// 最大进程数
18    pub max_processes: Option<u32>,
19    /// 最大文件大小(字节)
20    pub max_file_size: Option<u64>,
21    /// 最大执行时间(毫秒)
22    pub max_execution_time: Option<u64>,
23    /// 最大文件描述符数
24    pub max_file_descriptors: Option<u32>,
25}
26
27/// 沙箱类型
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
29#[serde(rename_all = "lowercase")]
30pub enum SandboxType {
31    /// Bubblewrap (Linux)
32    Bubblewrap,
33    /// Docker 容器
34    Docker,
35    /// Firejail (Linux)
36    Firejail,
37    /// Seatbelt (macOS)
38    Seatbelt,
39    /// 无沙箱
40    #[default]
41    None,
42}
43
44/// 审计日志配置
45#[derive(Debug, Clone, Default, Serialize, Deserialize)]
46pub struct AuditLogging {
47    /// 是否启用
48    pub enabled: bool,
49    /// 日志文件路径
50    pub log_file: Option<PathBuf>,
51    /// 日志级别
52    pub log_level: LogLevel,
53}
54
55/// 日志级别
56#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
57#[serde(rename_all = "lowercase")]
58pub enum LogLevel {
59    Debug,
60    #[default]
61    Info,
62    Warn,
63    Error,
64}
65
66/// Docker 配置
67#[derive(Debug, Clone, Default, Serialize, Deserialize)]
68pub struct DockerConfig {
69    /// 镜像名称
70    pub image: Option<String>,
71    /// 容器名称
72    pub container_name: Option<String>,
73    /// 卷挂载
74    pub volumes: Vec<String>,
75    /// 端口映射
76    pub ports: Vec<String>,
77    /// 网络模式
78    pub network: Option<String>,
79    /// 用户
80    pub user: Option<String>,
81    /// 工作目录
82    pub workdir: Option<String>,
83}
84
85/// 沙箱配置
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct SandboxConfig {
88    /// 是否启用沙箱
89    pub enabled: bool,
90    /// 沙箱类型
91    pub sandbox_type: SandboxType,
92    /// 允许访问的路径
93    pub allowed_paths: Vec<PathBuf>,
94    /// 禁止访问的路径(优先级更高)
95    pub denied_paths: Vec<PathBuf>,
96    /// 是否允许网络访问
97    pub network_access: bool,
98    /// 环境变量
99    pub environment_variables: HashMap<String, String>,
100    /// 只读路径
101    pub read_only_paths: Vec<PathBuf>,
102    /// 可写路径
103    pub writable_paths: Vec<PathBuf>,
104    /// 是否允许 /dev 访问
105    pub allow_dev_access: bool,
106    /// 是否允许 /proc 访问
107    pub allow_proc_access: bool,
108    /// 是否允许 /sys 访问
109    pub allow_sys_access: bool,
110    /// 环境变量白名单
111    pub env_whitelist: Vec<String>,
112    /// tmpfs 大小
113    pub tmpfs_size: String,
114    /// 是否隔离所有命名空间
115    pub unshare_all: bool,
116    /// 是否随父进程退出
117    pub die_with_parent: bool,
118    /// 是否创建新会话
119    pub new_session: bool,
120    /// Docker 配置
121    pub docker: Option<DockerConfig>,
122    /// 自定义参数
123    pub custom_args: Vec<String>,
124    /// 审计日志
125    pub audit_logging: Option<AuditLogging>,
126    /// 资源限制
127    pub resource_limits: Option<ResourceLimits>,
128}
129
130impl Default for SandboxConfig {
131    fn default() -> Self {
132        Self {
133            enabled: true,
134            sandbox_type: SandboxType::None,
135            allowed_paths: Vec::new(),
136            denied_paths: Vec::new(),
137            network_access: false,
138            environment_variables: HashMap::new(),
139            read_only_paths: vec![
140                PathBuf::from("/usr"),
141                PathBuf::from("/lib"),
142                PathBuf::from("/lib64"),
143                PathBuf::from("/bin"),
144                PathBuf::from("/sbin"),
145                PathBuf::from("/etc"),
146            ],
147            writable_paths: vec![PathBuf::from("/tmp")],
148            allow_dev_access: true,
149            allow_proc_access: true,
150            allow_sys_access: false,
151            env_whitelist: Vec::new(),
152            tmpfs_size: "100M".to_string(),
153            unshare_all: true,
154            die_with_parent: true,
155            new_session: true,
156            docker: None,
157            custom_args: Vec::new(),
158            audit_logging: None,
159            resource_limits: None,
160        }
161    }
162}
163
164/// 沙箱预设类型
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
166#[serde(rename_all = "lowercase")]
167pub enum SandboxPreset {
168    /// 严格隔离
169    Strict,
170    /// 开发环境
171    Development,
172    /// 测试环境
173    Testing,
174    /// 生产环境
175    Production,
176    /// Docker 模式
177    Docker,
178    /// 无限制
179    Unrestricted,
180    /// Web 爬虫
181    WebScraping,
182    /// AI 代码执行
183    AiCode,
184}
185
186/// 预设配置集合
187pub static SANDBOX_PRESETS: once_cell::sync::Lazy<HashMap<SandboxPreset, SandboxConfig>> =
188    once_cell::sync::Lazy::new(|| {
189        let mut presets = HashMap::new();
190
191        // 严格隔离预设
192        presets.insert(
193            SandboxPreset::Strict,
194            SandboxConfig {
195                enabled: true,
196                sandbox_type: SandboxType::Bubblewrap,
197                allowed_paths: Vec::new(),
198                denied_paths: vec![PathBuf::from("/home"), PathBuf::from("/root")],
199                network_access: false,
200                read_only_paths: vec![
201                    PathBuf::from("/usr"),
202                    PathBuf::from("/lib"),
203                    PathBuf::from("/lib64"),
204                    PathBuf::from("/bin"),
205                    PathBuf::from("/sbin"),
206                    PathBuf::from("/etc"),
207                ],
208                writable_paths: vec![PathBuf::from("/tmp")],
209                allow_dev_access: false,
210                allow_proc_access: false,
211                allow_sys_access: false,
212                tmpfs_size: "50M".to_string(),
213                resource_limits: Some(ResourceLimits {
214                    max_memory: Some(512 * 1024 * 1024),
215                    max_cpu: Some(50),
216                    max_processes: Some(10),
217                    max_file_size: Some(10 * 1024 * 1024),
218                    max_execution_time: Some(60000),
219                    max_file_descriptors: Some(100),
220                }),
221                ..Default::default()
222            },
223        );
224
225        // 开发环境预设
226        presets.insert(
227            SandboxPreset::Development,
228            SandboxConfig {
229                enabled: true,
230                sandbox_type: SandboxType::Bubblewrap,
231                network_access: true,
232                allow_dev_access: true,
233                allow_proc_access: true,
234                tmpfs_size: "200M".to_string(),
235                resource_limits: Some(ResourceLimits {
236                    max_memory: Some(2 * 1024 * 1024 * 1024),
237                    max_cpu: Some(80),
238                    max_processes: Some(50),
239                    max_execution_time: Some(300000),
240                    ..Default::default()
241                }),
242                ..Default::default()
243            },
244        );
245
246        // 测试环境预设
247        presets.insert(
248            SandboxPreset::Testing,
249            SandboxConfig {
250                enabled: true,
251                sandbox_type: SandboxType::Bubblewrap,
252                network_access: true,
253                allow_dev_access: true,
254                allow_proc_access: true,
255                tmpfs_size: "200M".to_string(),
256                resource_limits: Some(ResourceLimits {
257                    max_memory: Some(1024 * 1024 * 1024),
258                    max_cpu: Some(75),
259                    max_processes: Some(30),
260                    max_execution_time: Some(120000),
261                    ..Default::default()
262                }),
263                ..Default::default()
264            },
265        );
266
267        presets
268    });
269
270/// 验证结果
271#[derive(Debug, Clone)]
272pub struct ValidationResult {
273    /// 是否有效
274    pub valid: bool,
275    /// 错误信息
276    pub errors: Vec<String>,
277    /// 警告信息
278    pub warnings: Vec<String>,
279}
280
281/// 沙箱配置管理器
282pub struct SandboxConfigManager {
283    /// 配置目录
284    config_dir: PathBuf,
285    /// 配置文件路径
286    config_file: PathBuf,
287    /// 当前配置
288    current_config: Arc<RwLock<SandboxConfig>>,
289}
290
291impl SandboxConfigManager {
292    /// 创建新的配置管理器
293    pub fn new(config_dir: Option<PathBuf>) -> Self {
294        let config_dir = config_dir.unwrap_or_else(|| {
295            dirs::home_dir()
296                .unwrap_or_else(|| PathBuf::from("~"))
297                .join(".aster")
298                .join("sandbox")
299        });
300        let config_file = config_dir.join("config.json");
301        let current_config = Arc::new(RwLock::new(SandboxConfig::default()));
302
303        let mut manager = Self {
304            config_dir,
305            config_file,
306            current_config,
307        };
308        manager.load_config_sync();
309        manager
310    }
311
312    /// 同步加载配置
313    fn load_config_sync(&mut self) {
314        if let Ok(content) = std::fs::read_to_string(&self.config_file) {
315            if let Ok(config) = serde_json::from_str::<SandboxConfig>(&content) {
316                if let Ok(mut current) = self.current_config.write() {
317                    *current = config;
318                }
319            }
320        }
321    }
322
323    /// 异步加载配置
324    pub async fn load_config(&self) -> anyhow::Result<SandboxConfig> {
325        let content = tokio::fs::read_to_string(&self.config_file).await?;
326        let config: SandboxConfig = serde_json::from_str(&content)?;
327        if let Ok(mut current) = self.current_config.write() {
328            *current = config.clone();
329        }
330        Ok(config)
331    }
332
333    /// 验证配置
334    pub fn validate_config(&self, config: &SandboxConfig) -> ValidationResult {
335        let errors = Vec::new();
336        let mut warnings = Vec::new();
337
338        // 检查平台兼容性
339        if config.enabled && config.sandbox_type == SandboxType::Bubblewrap {
340            #[cfg(not(target_os = "linux"))]
341            warnings.push("Bubblewrap 仅在 Linux 上可用,沙箱将被禁用".to_string());
342        }
343
344        if config.enabled && config.sandbox_type == SandboxType::Seatbelt {
345            #[cfg(not(target_os = "macos"))]
346            warnings.push("Seatbelt 仅在 macOS 上可用,沙箱将被禁用".to_string());
347        }
348
349        // 检查路径冲突
350        for allowed in &config.allowed_paths {
351            for denied in &config.denied_paths {
352                if allowed.starts_with(denied) || denied.starts_with(allowed) {
353                    warnings.push(format!(
354                        "路径冲突: {} vs {}",
355                        allowed.display(),
356                        denied.display()
357                    ));
358                }
359            }
360        }
361
362        // 检查资源限制
363        if let Some(ref limits) = config.resource_limits {
364            if let Some(max_memory) = limits.max_memory {
365                if max_memory > 4 * 1024 * 1024 * 1024 {
366                    warnings.push("max_memory > 4GB 可能在某些系统上导致问题".to_string());
367                }
368            }
369        }
370
371        ValidationResult {
372            valid: errors.is_empty(),
373            errors,
374            warnings,
375        }
376    }
377
378    /// 合并配置
379    pub fn merge_configs(
380        &self,
381        base: &SandboxConfig,
382        override_config: &SandboxConfig,
383    ) -> SandboxConfig {
384        SandboxConfig {
385            enabled: override_config.enabled,
386            sandbox_type: override_config.sandbox_type,
387            allowed_paths: if override_config.allowed_paths.is_empty() {
388                base.allowed_paths.clone()
389            } else {
390                override_config.allowed_paths.clone()
391            },
392            denied_paths: if override_config.denied_paths.is_empty() {
393                base.denied_paths.clone()
394            } else {
395                override_config.denied_paths.clone()
396            },
397            network_access: override_config.network_access,
398            environment_variables: {
399                let mut env = base.environment_variables.clone();
400                env.extend(override_config.environment_variables.clone());
401                env
402            },
403            read_only_paths: if override_config.read_only_paths.is_empty() {
404                base.read_only_paths.clone()
405            } else {
406                override_config.read_only_paths.clone()
407            },
408            writable_paths: if override_config.writable_paths.is_empty() {
409                base.writable_paths.clone()
410            } else {
411                override_config.writable_paths.clone()
412            },
413            allow_dev_access: override_config.allow_dev_access,
414            allow_proc_access: override_config.allow_proc_access,
415            allow_sys_access: override_config.allow_sys_access,
416            env_whitelist: if override_config.env_whitelist.is_empty() {
417                base.env_whitelist.clone()
418            } else {
419                override_config.env_whitelist.clone()
420            },
421            tmpfs_size: override_config.tmpfs_size.clone(),
422            unshare_all: override_config.unshare_all,
423            die_with_parent: override_config.die_with_parent,
424            new_session: override_config.new_session,
425            docker: override_config
426                .docker
427                .clone()
428                .or_else(|| base.docker.clone()),
429            custom_args: if override_config.custom_args.is_empty() {
430                base.custom_args.clone()
431            } else {
432                override_config.custom_args.clone()
433            },
434            audit_logging: override_config
435                .audit_logging
436                .clone()
437                .or_else(|| base.audit_logging.clone()),
438            resource_limits: override_config
439                .resource_limits
440                .clone()
441                .or_else(|| base.resource_limits.clone()),
442        }
443    }
444
445    /// 获取预设配置
446    pub fn get_preset(&self, preset: SandboxPreset) -> Option<SandboxConfig> {
447        SANDBOX_PRESETS.get(&preset).cloned()
448    }
449
450    /// 获取当前配置
451    pub fn get_config(&self) -> SandboxConfig {
452        self.current_config
453            .read()
454            .map(|c| c.clone())
455            .unwrap_or_default()
456    }
457
458    /// 更新配置
459    pub async fn update_config(&self, config: SandboxConfig) -> anyhow::Result<()> {
460        if let Ok(mut current) = self.current_config.write() {
461            *current = config;
462        }
463        self.save_config().await
464    }
465
466    /// 保存配置到文件
467    pub async fn save_config(&self) -> anyhow::Result<()> {
468        tokio::fs::create_dir_all(&self.config_dir).await?;
469        let config = self.get_config();
470        let content = serde_json::to_string_pretty(&config)?;
471        tokio::fs::write(&self.config_file, content).await?;
472        Ok(())
473    }
474
475    /// 重置为默认配置
476    pub async fn reset(&self) -> anyhow::Result<()> {
477        self.update_config(SandboxConfig::default()).await
478    }
479
480    /// 检查路径是否允许访问
481    pub fn is_path_allowed(&self, target_path: &std::path::Path) -> bool {
482        let config = self.get_config();
483
484        // 禁止路径优先
485        for denied in &config.denied_paths {
486            if target_path.starts_with(denied) {
487                return false;
488            }
489        }
490
491        // 检查允许路径
492        if config.allowed_paths.is_empty() {
493            return true;
494        }
495
496        for allowed in &config.allowed_paths {
497            if target_path.starts_with(allowed) {
498                return true;
499            }
500        }
501
502        false
503    }
504
505    /// 检查路径是否可写
506    pub fn is_path_writable(&self, target_path: &std::path::Path) -> bool {
507        if !self.is_path_allowed(target_path) {
508            return false;
509        }
510
511        let config = self.get_config();
512        for writable in &config.writable_paths {
513            if target_path.starts_with(writable) {
514                return true;
515            }
516        }
517
518        false
519    }
520}