Files
MeshSate/app/config.py
zhaolei 16f134e328 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
2026-10-28 19:07:11 +08:00

151 lines
4.5 KiB
Python

"""应用配置加载。
所有配置项均以 ``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]