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:
176
app/models.py
Normal file
176
app/models.py
Normal 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"),)
|
||||
Reference in New Issue
Block a user