核心功能: - 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
177 lines
7.3 KiB
Python
177 lines
7.3 KiB
Python
"""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"),)
|