feat: MeshSafe 文件摆渡安全卫士 v1.0 初始提交
核心功能: - 6 层检测管线: L0 结构分析 → L1 ClamAV → L2 文档深度检测 → L3 关键词匹配 → L4 YARA → L5 PE/二进制 - FastAPI + PostgreSQL + Redis + Docker Compose 架构 - 客户端 HMAC-SHA256 签名鉴权 + 限流 + 幂等性 - 控制台 UI (登录/Dashboard/诊断中心/策略管理/任务列表/测试中心/产品介绍页) - 三层诊断定时器 (error_scan 30s / db_aggregate 30s / container_check 5min) 并发与稳定性修复: - 诊断定时器 Redis 时间槽位锁 (多 worker 同一槽位只有一个执行) - JSONL 存储 fcntl 跨进程排他锁 + 容错解码 + 原子重写 - 移除 SQLite fallback, 统一 PostgreSQL - API_CLIENTS 脱敏为环境变量, .env 已 .gitignore
This commit is contained in:
150
app/config.py
Normal file
150
app/config.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""应用配置加载。
|
||||
|
||||
所有配置项均以 ``MESHSAFE_`` 前缀从环境变量读取,敏感项不提供默认值,
|
||||
缺失时启动即报错,避免出现"用默认弱口令上线"的情况。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import functools
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="MESHSAFE_",
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
# 运行环境
|
||||
env: str = "production"
|
||||
log_level: str = "INFO"
|
||||
timezone: str = "Asia/Shanghai"
|
||||
|
||||
# 服务监听
|
||||
host: str = "0.0.0.0"
|
||||
port: int = 8443
|
||||
root_path: str = "/"
|
||||
|
||||
# 基础设施
|
||||
db_url: str
|
||||
db_echo: bool = False
|
||||
redis_url: str = "redis://redis:6379/0"
|
||||
|
||||
# 主密钥
|
||||
master_key: str
|
||||
|
||||
# API 调用方
|
||||
api_clients: str = ""
|
||||
signature_window: int = 300
|
||||
require_signature: bool = True
|
||||
sign_response: bool = True
|
||||
|
||||
# 控制台
|
||||
admin_username: str = "admin"
|
||||
admin_password: str
|
||||
login_max_attempts: int = 5
|
||||
login_lock_minutes: int = 15
|
||||
session_timeout_minutes: int = 30
|
||||
|
||||
# ClamAV
|
||||
clamav_host: str = "clamav"
|
||||
clamav_port: int = 3310
|
||||
clamav_timeout: int = 120
|
||||
clamav_use_clamd: bool = True
|
||||
|
||||
# 策略
|
||||
policy_file: str = "/app/config/policy.yaml"
|
||||
rules_dir: str = "/app/rules"
|
||||
sandbox_enabled: bool = False
|
||||
|
||||
# 文件处理
|
||||
max_upload_mb: int = 2048
|
||||
max_extract_depth: int = 5
|
||||
max_extract_total_mb: int = 4096
|
||||
max_extract_ratio: int = 100
|
||||
temp_ttl_minutes: int = 60
|
||||
|
||||
# 合规
|
||||
audit_retention_days: int = 180
|
||||
audit_hash_chain: bool = True
|
||||
report_retention_days: int = 180
|
||||
|
||||
# TLS
|
||||
enable_tls: bool = False
|
||||
tls_cert_file: str = "/app/certs/server.crt"
|
||||
tls_key_file: str = "/app/certs/server.key"
|
||||
trusted_proxy_count: int = 1
|
||||
|
||||
@field_validator("master_key")
|
||||
@classmethod
|
||||
def _validate_master_key(cls, v: str) -> str:
|
||||
try:
|
||||
raw = base64.b64decode(v, validate=True)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
raise ValueError("MESHSAFE_MASTER_KEY 必须是合法的 base64 字符串") from exc
|
||||
if len(raw) < 32:
|
||||
raise ValueError("MESHSAFE_MASTER_KEY 解码后长度不得少于 32 字节")
|
||||
return v
|
||||
|
||||
@field_validator("policy_file")
|
||||
@classmethod
|
||||
def _normalize_policy_file(cls, v: str) -> str:
|
||||
return v.strip()
|
||||
|
||||
@property
|
||||
def master_key_bytes(self) -> bytes:
|
||||
return base64.b64decode(self.master_key, validate=True)
|
||||
|
||||
@property
|
||||
def max_upload_bytes(self) -> int:
|
||||
return self.max_upload_mb * 1024 * 1024
|
||||
|
||||
@property
|
||||
def max_extract_total_bytes(self) -> int:
|
||||
return self.max_extract_total_mb * 1024 * 1024
|
||||
|
||||
@property
|
||||
def parsed_api_clients(self) -> List[Dict[str, str]]:
|
||||
"""解析 ``client_id:secret,client_id2:secret2`` 形式的调用方清单。"""
|
||||
result: List[Dict[str, str]] = []
|
||||
for item in self.api_clients.split(","):
|
||||
item = item.strip()
|
||||
if not item:
|
||||
continue
|
||||
if ":" not in item:
|
||||
raise ValueError(f"非法的 API 调用方配置项: {item!r}")
|
||||
client_id, secret = item.split(":", 1)
|
||||
client_id = client_id.strip()
|
||||
secret = secret.strip()
|
||||
if not client_id or not secret:
|
||||
raise ValueError(f"非法的 API 调用方配置项: {item!r}")
|
||||
result.append({"client_id": client_id, "secret": secret})
|
||||
return result
|
||||
|
||||
def redacted(self) -> Dict[str, object]:
|
||||
"""用于 /health 等接口展示的脱敏配置快照。"""
|
||||
return {
|
||||
"env": self.env,
|
||||
"require_signature": self.require_signature,
|
||||
"sign_response": self.sign_response,
|
||||
"signature_window": self.signature_window,
|
||||
"max_upload_mb": self.max_upload_mb,
|
||||
"max_extract_depth": self.max_extract_depth,
|
||||
"max_extract_ratio": self.max_extract_ratio,
|
||||
"sandbox_enabled": self.sandbox_enabled,
|
||||
"audit_retention_days": self.audit_retention_days,
|
||||
"audit_hash_chain": self.audit_hash_chain,
|
||||
"api_clients_count": len(self.parsed_api_clients),
|
||||
}
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=1)
|
||||
def get_settings() -> Settings:
|
||||
return Settings() # type: ignore[call-arg]
|
||||
Reference in New Issue
Block a user