Files
MeshSate/app/templates/policy.html
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

224 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<link rel="icon" href="/static/logo.png">
<title>策略配置 - MeshSafe 控制台</title>
<style>
* { box-sizing: border-box; }
body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
margin: 0; background: #f5f6fa; color: #333; }
{% include "_nav_style.html" %}
main { padding: 24px; max-width: 1400px; margin: 0 auto; }
h2 { margin: 0 0 16px; font-size: 18px; }
.tabs { display: flex; border-bottom: 2px solid #e0e0e0; margin-bottom: 16px; }
.tab { padding: 10px 20px; cursor: pointer; font-size: 14px; color: #666; border-bottom: 2px solid transparent; margin-bottom: -2px; }
.tab.active { color: #1a2a6c; border-bottom-color: #1a2a6c; font-weight: 500; }
.panel { display: none; }
.panel.active { display: block; }
.card { background: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,.06); margin-bottom: 16px; }
.card h3 { margin: 0 0 12px; font-size: 14px; color: #555; font-weight: 600; }
textarea { width: 100%; min-height: 480px; font-family: "JetBrains Mono", Consolas, monospace;
font-size: 13px; padding: 14px; border: 1px solid #ddd; border-radius: 6px;
resize: vertical; line-height: 1.5; }
textarea:focus { border-color: #1a2a6c; outline: none; }
.toolbar { display: flex; gap: 10px; margin-top: 12px; align-items: center; }
button { padding: 9px 20px; border: none; border-radius: 6px; font-size: 14px; cursor: pointer; }
.btn-primary { background: #1a2a6c; color: white; font-weight: 500; }
.btn-primary:hover { background: #243a8c; }
.btn-ghost { background: #f0f0f0; color: #555; }
.btn-ghost:hover { background: #e4e4e4; }
.btn-danger { background: #c62828; color: white; }
.btn-danger:hover { background: #d32f2f; }
.versions { width: 100%; border-collapse: collapse; font-size: 13px; }
.versions th { background: #f8f9fa; padding: 8px 12px; text-align: left; color: #666;
font-weight: 500; border-bottom: 1px solid #eee; }
.versions td { padding: 8px 12px; border-bottom: 1px solid #f0f0f0; font-family: monospace; }
.chip { display: inline-block; padding: 2px 10px; border-radius: 12px; font-size: 12px;
margin: 2px; }
.chip-zt { background: #ffebee; color: #c62828; }
.chip-warn { background: #fff3e0; color: #ef6c00; }
.chip-info { background: #e3f2fd; color: #1565c0; }
.chip-ok { background: #e8f5e9; color: #2e7d32; }
.form-row { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; }
.form-row label { width: 160px; font-size: 13px; color: #555; }
.form-row input[type=number], .form-row input[type=text] { padding: 8px 10px; border: 1px solid #ddd;
border-radius: 5px; font-size: 13px; width: 120px; }
</style>
</head>
<body>
{% set nav_active = 'policy' %}{% include "_nav.html" %}
<main>
<div class="tabs">
<div class="tab active" data-tab="yaml">YAML 模式(直接编辑)</div>
<div class="tab" data-tab="rules">规则清单(只读预览)</div>
<div class="tab" data-tab="yara">YARA 规则库({{ yara_loaded_count }} 条已加载)</div>
<div class="tab" data-tab="versions">版本历史</div>
</div>
<!-- ============ YAML 模式 ============ -->
<div id="panel-yaml" class="panel active">
<form method="post" action="/console/policy/save">
<input type="hidden" name="mode" value="yaml">
<div class="card">
<h3>策略内容(数据库为准,YAML 作为导出备份)</h3>
<textarea name="yaml_content" spellcheck="false">{{ yaml_text }}</textarea>
<div class="toolbar">
<button type="submit" class="btn-primary" {% if user.role != 'admin' %}disabled title="admin 权限才能保存"{% endif %}>保存并热重载</button>
<button type="button" class="btn-ghost" onclick="document.querySelector('#panel-yaml textarea').value = document.querySelector('#panel-yaml textarea').value;">刷新</button>
<span style="color:#888;font-size:12px;">当前版本:<b>{{ policy.version }}</b> · SHA256:{{ policy.sha256[:12] }}...</span>
</div>
</div>
</form>
</div>
<!-- ============ 规则清单 ============ -->
<div id="panel-rules" class="panel">
<div class="card">
<h3>零容忍规则集({{ policy.zero_tolerance_rules|length }} 条 · 命中即拒绝)</h3>
<div>
{% if zt_rules_cn %}
{% for r in zt_rules_cn %}
<span class="chip chip-zt" title="{{ r.rule }} — {{ r.cn_desc }}">{{ r.cn_name }}</span>
{% endfor %}
{% else %}
{% for r in policy.zero_tolerance_rules %}
<span class="chip chip-zt">{{ r }}</span>
{% endfor %}
{% endif %}
</div>
</div>
<div class="card">
<h3>策略全局参数</h3>
<table class="versions">
<tr><th>参数名</th><th>说明</th><th>当前值</th></tr>
<tr><td>block_score</td><td>单引擎累计得分 ≥ 此阈值 → 判定为 malicious 直接拒绝</td><td><b>{{ policy.block_score }}</b></td></tr>
<tr><td>review_score</td><td>累计得分落在这个区间 → 判定为 suspicious 转人工复核</td><td>{{ policy.review_score[0] }} ~ {{ policy.review_score[1] }}</td></tr>
<tr><td>engine_failure_mode</td><td>检测引擎异常时的处理策略(fail_closed=拒绝 fail_open=放行)</td><td><b style="color:#c62828;">{{ policy.engine_failure_mode }}</b></td></tr>
<tr><td>max_file_size</td><td>允许上传的单文件最大体积(超过直接拒绝)</td><td>{% set kb = policy.max_file_size / 1024 %}{% set mb = kb / 1024 %}{% if mb >= 1 %}{{ "%.1f"|format(mb) }} MB{% else %}{{ "%.0f"|format(kb) }} KB{% endif %}({{ policy.max_file_size }} 字节)</td></tr>
<tr><td>allow_encrypted_archive</td><td>是否允许带密码的加密 ZIP/RAR/7z 压缩包通过</td><td>{% if policy.allow_encrypted_archive %}<span class="chip chip-ok">允许</span>{% else %}<span class="chip chip-zt">禁止</span>{% endif %}</td></tr>
<tr><td>allow_encrypted_document</td><td>是否允许带密码的加密 Office 文档(.docx/.xlsx/.pptx)通过</td><td>{% if policy.allow_encrypted_document %}<span class="chip chip-ok">允许</span>{% else %}<span class="chip chip-zt">禁止</span>{% endif %}</td></tr>
<tr><td>白名单扩展名</td><td>允许上传的文件扩展名(不在此列表的直接拒绝)</td><td style="font-family:sans-serif;">
{% for e in policy.allowed_extensions %}
<span class="chip chip-info">{{ e }}</span>
{% endfor %}
</td></tr>
</table>
</div>
{% if policy.keyword_rules %}
<div class="card">
<h3>词表规则({{ policy.keyword_rules|length }} 条)</h3>
{% for kw in policy.keyword_rules %}
<div class="chip chip-info">{{ kw.name }}</div>
{% endfor %}
</div>
{% endif %}
</div>
<!-- ============ YARA 规则库 ============ -->
<div id="panel-yara" class="panel">
<div class="card">
<h3>YARA 规则库({{ yara_files_count }} 个文件 · {{ yara_loaded_count }} 条已编译)</h3>
{% if yara_rules %}
<table class="versions">
<thead>
<tr>
<th>规则名</th>
<th>分类</th>
<th>风险等级</th>
<th>误报率</th>
<th>权重</th>
<th>触发次数</th>
<th>参考</th>
</tr>
</thead>
<tbody>
{% for r in yara_rules %}
<tr>
<td style="font-family:monospace;font-size:12px;" title="{{ r.name }}">{{ r.name }}</td>
<td>
<span class="chip chip-info" title="分类代码 {{ r.category }}">{{ r.category_cn }}</span>
</td>
<td>
{% if r.risk_level_cn %}<span class="chip {{ r.risk_level_chip }}">{{ r.risk_level_cn }}</span>
{% else %}<span class="chip chip-ok">{{ r.risk_level }}</span>{% endif %}
</td>
<td style="color:#888;font-size:12px;">{{ r.false_positive_rate }}</td>
<td><b>{{ r.weight }}</b></td>
<td>
{% if r.trigger_count > 0 %}<span class="chip chip-zt">{{ r.trigger_count }}</span>
{% else %}<span style="color:#bbb;">0</span>{% endif %}
</td>
<td style="font-size:11px;color:#888;">{{ r.reference[:50] }}{% if r.reference|length > 50 %}...{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div style="color:#999;text-align:center;padding:30px;">
{% if yara_rules_dir %}
YARA 规则库目录:<code>{{ yara_rules_dir }}</code><br>
目录内 {{ yara_files_count }} 个 .yar 文件
{% if yara_files_count == 0 %}—— 请检查规则文件是否存在{% endif %}
{% else %}
YARA 规则库未加载(可能 yara-python 未安装或规则目录不存在)
{% endif %}
</div>
{% endif %}
</div>
<div class="card" style="margin-top:12px;">
<h3>权重分档说明(来自 policy.yaml → yara_rule_weights)</h3>
<div style="font-size:13px;color:#555;line-height:1.8;">
<span class="chip chip-zt">critical → weight=85</span>
<span class="chip chip-warn">high → weight=65</span>
<span class="chip chip-info">medium → weight=45</span>
<span class="chip chip-ok">low → weight=25</span>
<span style="color:#888;margin-left:12px;">未配置的规则默认 weight=50</span>
</div>
</div>
</div>
<!-- ============ 版本历史 ============ -->
<div id="panel-versions" class="panel">
<div class="card">
<h3>策略版本历史({{ versions|length }} 条)</h3>
<table class="versions">
<thead><tr><th>版本</th><th>SHA256</th><th>创建时间</th><th>操作</th></tr></thead>
<tbody>
{% for v in versions %}
<tr>
<td>{{ v.version }}</td>
<td>{{ v.sha256 }}</td>
<td>{{ v.created_at|bj_time if v.created_at else '' }}</td>
<td>
{% if user.role == 'admin' %}
<form method="post" action="/console/policy/rollback/{{ v.version }}" style="display:inline;">
<button type="submit" class="btn-danger" style="padding:4px 12px;font-size:12px;">回滚到此版本</button>
</form>
{% endif %}
</td>
</tr>
{% else %}
<tr><td colspan="4" style="text-align:center;color:#999;">暂无版本记录(首次部署执行 scripts.init_db)</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</main>
<script>
document.querySelectorAll('.tab').forEach(tab => {
tab.onclick = () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.panel').forEach(p => p.classList.remove('active'));
tab.classList.add('active');
document.getElementById('panel-' + tab.dataset.tab).classList.add('active');
};
});
</script>
</body>
</html>