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:
2026-10-28 19:07:11 +08:00
commit 16f134e328
113 changed files with 24604 additions and 0 deletions

176
app/models.py Normal file
View File

@@ -0,0 +1,176 @@
"""ORM 模型定义。
涉及表:
- ``scan_tasks`` 扫描任务(同步与异步任务共用一张表)
- ``scan_findings`` 单条检测信号
- ``audit_logs`` 审计日志(带哈希链,防篡改)
- ``console_users`` 控制台账号
- ``login_attempts`` 登录失败记录(用于锁定)
- ``policy_versions`` 策略版本快照
- ``review_records`` 人工复核记录
"""
from __future__ import annotations
import uuid
from datetime import datetime, timezone
from sqlalchemy import (
JSON,
Boolean,
DateTime,
Float,
ForeignKey,
Index,
Integer,
String,
Text,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db import Base
def utcnow() -> datetime:
return datetime.now(timezone.utc)
def new_id() -> str:
return uuid.uuid4().hex
class ScanTask(Base):
__tablename__ = "scan_tasks"
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=new_id)
channel: Mapped[str] = mapped_column(String(64), default="default", index=True)
client_id: Mapped[str] = mapped_column(String(64), index=True)
filename: Mapped[str] = mapped_column(String(512))
declared_ext: Mapped[str] = mapped_column(String(32), default="")
size: Mapped[int] = mapped_column(Integer, default=0)
sha256: Mapped[str] = mapped_column(String(64), index=True)
md5: Mapped[str] = mapped_column(String(32), default="")
real_type: Mapped[str] = mapped_column(String(256), default="")
status: Mapped[str] = mapped_column(String(16), default="pending", index=True)
mode: Mapped[str] = mapped_column(String(16), default="sync")
verdict: Mapped[str] = mapped_column(String(24), default="pending")
action: Mapped[str] = mapped_column(String(32), default="pending")
score: Mapped[float] = mapped_column(Float, default=0.0)
reason: Mapped[str] = mapped_column(Text, default="")
policy_version: Mapped[str] = mapped_column(String(64), default="")
detail: Mapped[dict] = mapped_column(JSON, default=dict)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, index=True)
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
duration_ms: Mapped[int] = mapped_column(Integer, default=0)
findings: Mapped[list["ScanFinding"]] = relationship(
back_populates="task", cascade="all, delete-orphan", lazy="selectin"
)
class ScanFinding(Base):
__tablename__ = "scan_findings"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
task_id: Mapped[str] = mapped_column(
String(64), ForeignKey("scan_tasks.id", ondelete="CASCADE"), index=True
)
engine: Mapped[str] = mapped_column(String(64))
layer: Mapped[str] = mapped_column(String(8))
severity: Mapped[str] = mapped_column(String(16))
rule: Mapped[str] = mapped_column(String(256))
detail: Mapped[str] = mapped_column(Text, default="")
extracted_path: Mapped[str] = mapped_column(String(1024), default="")
weight: Mapped[float] = mapped_column(Float, default=0.0)
task: Mapped[ScanTask] = relationship(back_populates="findings")
class AuditLog(Base):
"""审计日志。
每条记录包含 ``prev_hash`` 与 ``row_hash``,形成哈希链。
任一条记录被篡改或删除,后续记录的链式校验都会失败,可被检测出来。
"""
__tablename__ = "audit_logs"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
ts: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, index=True)
actor: Mapped[str] = mapped_column(String(128), default="system")
actor_role: Mapped[str] = mapped_column(String(32), default="system")
action: Mapped[str] = mapped_column(String(64), index=True)
target: Mapped[str] = mapped_column(String(256), default="")
channel: Mapped[str] = mapped_column(String(64), default="")
client_ip: Mapped[str] = mapped_column(String(64), default="")
outcome: Mapped[str] = mapped_column(String(24), default="success")
detail: Mapped[dict] = mapped_column(JSON, default=dict)
prev_hash: Mapped[str] = mapped_column(String(64), default="")
row_hash: Mapped[str] = mapped_column(String(64), default="")
__table_args__ = (Index("ix_audit_logs_action_ts", "action", "ts"),)
class ConsoleUser(Base):
__tablename__ = "console_users"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
username: Mapped[str] = mapped_column(String(64), unique=True, index=True)
password_hash: Mapped[str] = mapped_column(String(256))
role: Mapped[str] = mapped_column(String(32), default="admin")
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
must_change_password: Mapped[bool] = mapped_column(Boolean, default=True)
failed_attempts: Mapped[int] = mapped_column(Integer, default=0)
locked_until: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
last_login_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
password_changed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
class LoginAttempt(Base):
__tablename__ = "login_attempts"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
username: Mapped[str] = mapped_column(String(64), index=True)
client_ip: Mapped[str] = mapped_column(String(64), default="")
success: Mapped[bool] = mapped_column(Boolean, default=False)
ts: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow, index=True)
class PolicyVersion(Base):
__tablename__ = "policy_versions"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
version: Mapped[str] = mapped_column(String(64), unique=True, index=True)
content: Mapped[dict] = mapped_column(JSON, default=dict)
sha256: Mapped[str] = mapped_column(String(64), default="")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
class ReviewRecord(Base):
__tablename__ = "review_records"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
task_id: Mapped[str] = mapped_column(String(64), ForeignKey("scan_tasks.id", ondelete="CASCADE"), index=True)
reviewer: Mapped[str] = mapped_column(String(64))
decision: Mapped[str] = mapped_column(String(32))
comment: Mapped[str] = mapped_column(Text, default="")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
class IdempotencyRecord(Base):
"""幂等记录:同一文件哈希 + 同一策略版本只扫描一次。"""
__tablename__ = "idempotency_records"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
sha256: Mapped[str] = mapped_column(String(64), index=True)
policy_version: Mapped[str] = mapped_column(String(64), index=True)
task_id: Mapped[str] = mapped_column(String(64))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utcnow)
__table_args__ = (UniqueConstraint("sha256", "policy_version", name="uq_idem_hash_policy"),)