Files
graylog-ops/README.md
T

28 KiB
Raw Blame History

graylog-ops

基于 Graylog + F5 访问日志 的恶意 IP 检测与封禁清单服务。

它做三件事:

  1. 特征检测:用 12 条规则识别扫描器、漏洞探测、登录爆破、恶意 UA 等攻击行为;
  2. 行为检测:独立于规则和名单,按 24 小时访问行为给 IP 打分;
  3. 融合输出:只保留"建议封禁"的 IP,形成一个去重、可分页、可推送的清单,并提供监控面板。

服务是单进程的:检测线程 + 行为线程 + Web 面板跑在同一个进程里,数据落在本地 SQLite。

列表页

监控页


功能特性

  • 特征 + 行为双检测,融合后按 first_banned_at 倒序展示,同一 IP 只出现一次
  • 只展示建议封禁:共享出口(移动/教育网/CGNAT)、疑似多用户、归属不明的 IP 不进入清单、不推送
  • 归属清晰:国家/地区 + ASN(厂商/运营商,如阿里云、腾讯云、中国移动、教育网)+ 网络类型
  • 分页 + 服务端搜索:默认 20 条/页,支持 20/50/100,按 IP、归属、理由搜索
  • 详情可复核:行为画像、规则命中、请求频率/状态码/方法/域名分布、按需从 Graylog 拉取证据
  • 融合推送:一个 IP 24 小时内最多一条,仍活动可 24h 后再提醒;失败自动退避重试并记录审计
  • 自动补测:启动时发现检测缺口(默认 >30 分钟)自动后台回扫,补录期间漏掉的 IP
  • 监控:页面右上角状态灯 + 「监控」页面 + Prometheus /metrics
  • Docker 交付:镜像内可带数据快照,首次启动自动初始化 /data
  • 零第三方依赖:纯 Python 标准库(geo/asn 查询用 SQLite)

架构与数据流

外部客户端
   │
   ▼
F5(访问日志) ──syslog──► Graylog 4.0.x
                              │  API(只读)
                              ▼
                 ┌──────────────────────────┐
                 │  graylog-ops(单进程)   │
                 │                          │
                 │  特征检测线程(60s)      │
                 │  行为检测线程(15min)    │
                 │  自动补测(启动时)       │
                 │  Web 面板 / API          │
                 └────────────┬─────────────┘
                              │ SQLite(data/ips.db)
                              ▼
                  融合名单(建议封禁)
                              │
                              ├── 钉钉/企微/飞书/Slack 推送
                              ├── blocklist.txt 导出
                              └── /metrics 监控

Graylog 是旁路日志系统,本服务只读:不建流、不改索引、不下发封禁。封禁执行由 F5 / AD / 防火墙侧完成。


快速开始

环境要求

  • Python 3.9+(Docker 镜像使用 Python 3.12)
  • 能访问 Graylog API(默认端口 9002)
  • 可选:data/geo/geo.db、data/geo/asn.db(不提供也能跑,只是没有归属信息)

配置 Graylog

复制配置模板并填入 Graylog API token:

cp config.example.json graylog-ops.json
chmod 600 graylog-ops.json
# 编辑 graylog-ops.json:url / token

也可以用环境变量(优先级:命令行 > 环境变量 > 配置文件):

export GRAYLOG_URL=http://graylog.example:9002
export GRAYLOG_TOKEN='你的 API Token'

Token 认证方式是 token 当用户名、字面量 token 当密码,工具内部已处理,不需要额外配置。

只读权限即可:messages:read、searches:*、streams:read 等,详见文末「权限」。

启动服务

./bin/graylog-ops --config graylog-ops.json watch \
  --stream F5_Accesslog \
  --profile profiles/f5.json \
  --interval 60 \
  --window 3600 \
  --forget 604800 \
  --db data/ips.db \
  --host 0.0.0.0 \
  --port 8088 \
  --geo-db data/geo/geo.db \
  --asn-db data/geo/asn.db \
  --allow allowlist.example.txt \
  --export data/blocklist.txt

打开:

http://127.0.0.1:8088/

如果不加 --path-prefix,根路径就是面板;用于公网分享时建议加前缀(见「安全」)。

可选:桌面/服务器常驻

按当前约定不写 launchd/systemd,需要时手动起;Docker 部署可加 --restart unless-stopped。


检测机制

1. 特征检测

每 --interval 秒(默认 60)跑一轮,所有规则一次性聚合,查询次数与 IP 数量无关。

规则 标题 权重 饱和点 说明
probe_java Spring/中间件探测 60 2 /actuator、/druid、/nacos、/swagger、/heapdump、/weblogic 等
probe_config 配置/凭证探测 60 2 /.env、/.git、/.ssh、/backup、/WEB-INF 等
probe_cms CMS 探测 60 2 /wp-*、/phpmyadmin、/administrator、/thinkphp 等
probe_iot IoT/路由器探测 60 2 /ISAPI、/HNAP1、/boaform、/cgi-bin/luci 等
probe_devops DevOps/监控探测 60 2 /jenkins、/grafana、/kibana、/zabbix、/harbor 等
probe_webshell WebShell/上传探测 60 2 /shell.php、/cmd.php、/phpinfo、/vendor/phpunit 等
exploit_payload 注入/穿越载荷 65 2 etc/passwd、jndi:、union select、%2e%2e、base64_decode 等
login_attack 登录失败尝试 55 8 登录类 URI 上的 401/403
login_burst 登录接口高频 15 2000 只作上下文,单独不足以定性
bad_ua 扫描器 UA 55 1 sqlmap / nikto / nuclei / nmap / zgrab 等
bot_ua 爬虫 UA 50 20 DotBot 等(按 UA 判断)
not_found 404 探测 0 — 不直接计分,按 404 占比计分(≥50% 且 ≥20 次时最多加 25)

评分分级:

分级 分数 是否进入融合名单
malicious 恶意 ≥ 60 是
suspicious 可疑 ≥ 30 否
automation 自动化 — 单一接口高频的自动化客户端,降级处理
observe 观察 ≥ 10(--min-observe-score) 否

安全约束:

  • 内网地址(RFC1918 / 回环 / 链路本地 / CGNAT)默认排除
  • --allow 白名单文件(IP/CIDR,一行一个,# 注释)硬排除
  • 纯"请求量大"不会判恶意,必须有攻击证据或行为异常

2. 行为检测

每 --behavior-interval 秒(默认 900)跑一次,分析 最近 24 小时 的访问行为,不依赖规则命中,也不依赖已有名单。

评分项(>= 60 且至少 2 个信号才算行为违规):

信号 加减分
4xx 失败率 ≥50% / ≥30% +30 / +15
不同 URI ≥20 / ≥8 +20 / +8
短时爆发(覆盖 ≤30% 且峰值 ≥25%) +20
登录接口占比 ≥20% +15
访问多个 Host(≥2) +5
单一 UA +5
自动化 UA(SDK/伪造浏览器/其他) +10
4xx 数量 ≥100 且失败率 ≥20% +10
非 GET 为主(<30%) −25
低失败率且 2xx ≥80% −20
长期稳定单接口 −20
状态字段缺失 −10

行为违规后再结合 ASN 出口属性 给结论:

出口属性 结论 是否进融合名单
移动网络 / 教育网(共享出口) 仅告警(共享出口) 否
宽带 + 多 UA/多 Host(疑似多用户) 仅告警(疑似多用户) 否
宽带 + 单 UA 且行为分 ≥75 建议封禁 是
宽带 + 单 UA 且行为分 <75 建议人工确认 否
云主机 / IDC 机房 建议封禁 是
归属不明 建议人工确认 否

行为检测的候选范围:24 小时访问量前列 IP + 额外 4xx 较多的 IP,最多约 600 条进入画像;这是为了控制 Graylog 查询量,必要时可调整代码中的上限。

3. 融合名单

fused = 特征 ever_malicious=1  OR  行为 verdict = 建议封禁
  • 来源:feature(特征)、behavior(行为)、both(双来源)
  • 首次发现时间 first_banned_at 固定:一个 IP 只会在进入名单时排一次序,后续再命中不会把它重新顶到最上面
  • 历史永久保留:一旦进入过融合名单,即使后来不再活跃也不会被删除
  • 只有建议封禁会展示/推送:其他行为结论只留在画像表里供详情页查看

4. 推送

所有推送走同一条融合链路,同一 IP 不会因为多个来源收到多条消息。

推送条件:

  • 该 IP 最近 24 小时内仍有活动(last_activity_at)
  • 距离上次推送 ≥ 24 小时(last_pushed_at)
  • 不在失败退避期内(push_next_at)

消息内容:

**建议封禁 · 融合检测**

**113.194.199.54**(中国 · 江西)
归属:🏠 中国联通 · AS4837
来源:特征检测 · 发现:10-18 04:29
评分 100 · 请求 42 次
⏰ 该 IP 仍在活动(第 2 次提醒)
依据:特征命中:漏洞利用 payload 8 次、Spring/中间件探测 4 次、404 探测 2 次
[查看详情](http://<host>:<port>/s/<secret>/#/ip/113.194.199.54)
  • 首次部署/首次同步时,历史 IP 会被标记为已推送,不会补推
  • 推送失败:写入 notifications 表,按 60s → 120s → 240s → … → 1h 指数退避重试
  • 连续失败 3 次会记录告警;可选 NOTIFY_ALERT_URL 发到独立 webhook
  • 详情链接前缀由 link_prefix(或 NOTIFY_LINK_PREFIX)控制

本地配置:复制 notify.example.json 为 notify.json(watch 默认读取该文件,存在才启用),填入 webhook / 加签密钥 / 链接前缀;Docker 部署改用 NOTIFY_* 环境变量。示例:

{
  "type": "dingtalk",
  "url": "https://oapi.dingtalk.com/robot/send?access_token=<token>",
  "secret": "SEC<加签密钥>",
  "link_prefix": "http://<host>:<port>/s/<secret>/",
  "title": "新增恶意 IP"
}

不配置推送也能正常检测;需要验证 webhook 时用:

./bin/graylog-ops --config graylog-ops.json watch --notify-test   --stream F5_Accesslog --profile profiles/f5.json --db data/ips.db

5. 自动补测

每轮检测成功后会写入 meta.detect_last_success。服务启动时:

  • 缺口 ≤ --backfill-threshold(默认 1800 秒):正常启动,不补
  • 缺口 > 阈值且 ≤ --backfill-max-gap(默认 7 天):后台线程自动回扫,不阻塞页面
  • 缺口 > 7 天:跳过并打印日志

自动补测会:

  1. 按规则回扫缺口时间段(自动做时间切片,避免聚合条数上限漏 IP)
  2. 跑一次该时间段的行为分析(仅缺口 1–48 小时时)
  3. 补录的 IP 全部抑制推送,不会补发钉钉

参数:

--backfill-threshold 1800   # 缺口阈值(秒)
--backfill-max-gap 604800   # 最大允许补测的缺口(秒)
--no-auto-backfill          # 关闭自动补测

Web 界面

列表页

  • 列:IP 地址 / 位置 · 归属 / 来源 / 评分 / 请求数 / 频率 / 发现时间 / 最近活动 / 判定理由
  • 位置 / 归属 两行展示:第一行国旗 + 国家·省,第二行图标 + 厂商/运营商(悬停看 ASN 全名)
  • 分页:默认 20 条/页,可切 20 / 50 / 100;页码、上一页/下一页、总数
  • 搜索:服务端搜索,支持 IP、国家/地区、厂商、ASN、来源、判定理由;250ms 防抖
  • 点击 IP 复制;点击行进入详情
  • 右上角状态灯:绿=正常、红=检测失败、黄=状态过期、灰=未知(悬停看提示)
  • 每 30 秒自动刷新(可关闭)

详情页

  • 顶部:评分环、发现时间、最近活动、建议
  • 行为检测画像:结论、人话理由、出口(厂商 + ASN + 类型)、24h 请求/失败率/URI/UA 数、信号列表
  • 判定依据:命中的规则和次数
  • 证据:请求频率分布、状态码分布、请求方法分布、访问域名分布、请求样本;证据按需从 Graylog 拉取并缓存,索引轮转后回退到本地快照

详情页

监控页

入口:页面右上角「监控」,或 #/monitor。

展示:

  • Graylog 连接状态、最近成功、距上次成功、最近错误
  • 特征检测轮次、失败次数、最近耗时
  • 行为分析次数、最近耗时、最近违规数
  • 推送 24h 成功率、成功/失败数、连续失败、最近错误
  • 名单总数、今日新增、双来源、行为新发现
  • 自动补测次数、上次时间/区间/新增
  • 最近推送记录(成功/失败 + 错误)

复制 / 导出

  • 「复制 IP」:逗号分隔 / 换行分隔
  • 「导出」:CSV(完整字段) / 仅 IP 列表
  • 都作用于当前搜索条件下的全部记录,不是只导出当前页

HTTP API

服务启动后(假设无路径前缀):

接口 说明
GET /healthz 健康检查,返回 {"ok": true}(不受路径前缀限制)
GET /api/status Graylog 连通状态、最近成功时间、错误
GET /api/stats 名单/分级的聚合统计
GET /api/ips 融合名单分页接口
GET /api/ip/<ip> 单 IP 详情(含行为画像、融合记录、证据)
GET /api/export.csv 特征库导出(CSV,legacy,按 scope=core/all)
GET /api/export.txt 特征库导出(纯 IP,每行一个)
GET /metrics Prometheus 指标
GET /api/monitor 监控页数据(JSON)

/api/ips 参数

参数 说明
page 页码,默认 1
page_size 每页条数,默认 50,最大 2000(兼容旧参数 limit)
offset 自定义偏移,优先于 page
q 搜索关键词(IP/归属/ASN/来源/理由)

返回:

{
  "count": 20,
  "total": 106,
  "page": 1,
  "page_size": 20,
  "pages": 6,
  "q": "",
  "updated_at": "2026-10-19T05:25:03Z",
  "items": [
    {
      "ip": "121.41.167.124",
      "sources": "feature",
      "tier": "malicious",
      "score": 60.0,
      "feature_score": 60.0,
      "behavior_score": null,
      "total": 6,
      "rules": {"probe_iot": 6},
      "reasons": ["特征命中:IoT/路由器探测 6 次"],
      "first_seen": "2026-10-19T02:14:07.000Z",
      "last_seen": "2026-10-19T02:18:56.000Z",
      "window": 3600,
      "country_name": "中国",
      "region": "浙江",
      "asn": "37963",
      "asn_brand": "阿里云",
      "asn_type": "cloud",
      "asn_icon": "☁️"
    }
  ]
}

/metrics 指标

ipsec_detect_rounds_total
ipsec_detect_errors_total
ipsec_detect_last_success_timestamp
ipsec_detect_gap_seconds
ipsec_detect_last_duration_seconds
ipsec_behavior_runs_total
ipsec_behavior_errors_total
ipsec_behavior_last_duration_seconds
ipsec_behavior_last_violations
ipsec_fused_total
ipsec_fused_new_24h
ipsec_fused_both
ipsec_push_sent_total
ipsec_push_failed_total
ipsec_push_consecutive_failures
ipsec_push_success_ratio_24h
ipsec_graylog_up

数据模型

SQLite 文件默认 data/ips.db,开启 WAL。主要表:

表 作用
ip_stats 特征检测结果:IP、分级、评分、规则命中、原因、首次/最近时间、Geo/ASN、ever_malicious
behavior_profiles 行为画像:评分、结论、信号、理由、ASN/网络类型、24h 指标、historical(补测标记)
ip_fused 融合名单:来源、首次封禁时间、最近活动、推送时间/次数、失败退避、融合理由
meta 运行状态:最近成功检测、各计数器、自动补测记录
notifications 推送审计:IP、通道、成功/失败、尝试次数、错误、时间
events 检测事件历史

时间字段统一存 UTC ISO8601(如 2026-10-19T05:41:59Z),页面和推送按浏览器/服务器本地时区展示。


命令行

主命令

命令 说明
probe 连通性/认证/接口可用性矩阵
system 版本、节点、索引器健康、license
streams stream 列表
inputs / fields / indices / alerts 环境信息
discover 采样日志,推断真实客户端 IP 字段
judge 只读判断:聚合信号 → 封禁候选
top-ips / terms / messages / histogram 查询/聚合
snapshot 环境快照落盘
raw 直接调用任意 API
collect 一条命令产出 probe/discover/judge/snapshot
watch 持续检测 + Web 面板(本服务主入口)

通用参数:--json、--out FILE、-v。

watch 常用参数

参数 默认 说明
--stream — Graylog stream 名称或 ID
--profile — 字段映射(profiles/f5.json)
--interval 60 特征检测间隔(秒)
--window 900 特征检测窗口(秒);F5 场景建议 3600
--forget 7200 多久未出现从观察列表移除(秒);恶意 IP 永久保留
--db — SQLite 路径
--host / --port 127.0.0.1 / 8088 面板监听
--path-prefix — 对外访问密钥路径(根路径 404)
--allow — 白名单文件,可重复
--export — 每轮导出恶意 IP 到纯文本
--behavior-interval 900 行为分析间隔(秒)
--no-behavior — 关闭行为检测
--geo-db / --asn-db data/geo/*.db 归属库;空字符串禁用
--backfill-threshold 1800 启动自动补测阈值(秒)
--backfill-max-gap 604800 最大补测缺口(秒)
--no-auto-backfill — 关闭自动补测
--notify-config — webhook 配置(notify.json)
--notify-test — 发一条测试推送后退出
--once — 只跑一轮后退出
--serve-only — 只提供面板,不检测
--fix-times — 用日志修正历史时间字段
--warm-only — 预生成证据快照后退出
--min-observe-score 10 低于该分数的观察级不入库
--size 200 每类聚合返回 IP 上限
--verify 8 对前 N 个候选做 URI 核验

维护/补测工具

# 特征检测补测(支持自动时间切片)
python3 tools/backfill_gap.py \
  --from "2026-10-18 04:00" --to "2026-10-19 03:55" [--dry-run]

# 行为检测补测(历史画像会标记 historical=1,不会被实时分析覆盖)
python3 tools/backfill_behavior.py \
  --from "2026-10-18 04:00" --to "2026-10-19 03:55" [--dry-run]

时间参数使用本地时间;补测结果默认抑制推送。


Docker 部署

镜像结构

docker-build/ 是构建上下文:

docker-build/
├── Dockerfile
├── entrypoint.sh
├── app/                  # 代码(构建时生成/复制,gitignore)
│   ├── graylog_ops/
│   ├── bin/
│   ├── profiles/
│   ├── allowlist.example.txt
│   └── asn_brand_map.json
└── seed/                 # 数据快照(构建时生成/复制,gitignore)
    ├── ips.db
    ├── blocklist.txt
    └── geo/
        ├── geo.db
        └── asn.db

启动时如果 /data/ips.db 不存在,entrypoint 会把 /app/seed/ 复制到 /data;已有数据卷则继续使用卷内数据。

构建

# 1) 准备 app/ 和 seed/(示例)
mkdir -p docker-build/seed/geo
rsync -a --exclude '__pycache__' graylog_ops docker-build/app/
rsync -a bin profiles docker-build/app/
cp allowlist.example.txt asn_brand_map.json docker-build/app/

# 2) 复制当前数据库快照(推荐用 sqlite backup 保证一致)
python3 - <<'PY'
import sqlite3
src = sqlite3.connect("data/ips.db")
dst = sqlite3.connect("docker-build/seed/ips.db")
with dst:
    src.backup(dst)
src.close(); dst.close()
PY
cp data/geo/geo.db data/geo/asn.db docker-build/seed/geo/
cp data/blocklist.txt docker-build/seed/

# 3) 构建并导出离线包
docker build -t malicious-ip-watch:YYYY-MM-DD docker-build
docker save malicious-ip-watch:YYYY-MM-DD | gzip > malicious-ip-watch-YYYY-MM-DD.tar.gz

运行时环境变量

变量 默认 说明
GRAYLOG_URL — Graylog 地址(必填)
GRAYLOG_TOKEN — API token(必填)
GRAYLOG_STREAM F5_Accesslog stream 名称
APP_PORT 8088 容器内监听端口
APP_PATH_PREFIX — 对外访问路径前缀(建议设置)
CHECK_INTERVAL 60 检测间隔
WINDOW 3600 检测窗口
FORGET 604800 观察列表过期时间
BEHAVIOR_INTERVAL 900 行为分析间隔
NO_BEHAVIOR 0 设为 1 关闭行为检测
WARM_LIMIT 5 每轮证据快照数量
DATA_DIR /data 数据目录
NOTIFY_TYPE generic dingtalk/wecom/feishu/slack/generic
NOTIFY_URL — webhook 地址
NOTIFY_SECRET — 钉钉加签密钥(SEC...)
NOTIFY_LINK_PREFIX — 详情链接前缀
NOTIFY_TITLE 新增恶意 IP 推送标题
NOTIFY_ALERT_URL — 推送连续失败的独立告警 webhook(可选)
NOTIFY_ALERT_TYPE / NOTIFY_ALERT_SECRET — 告警通道类型/加签密钥

镜像内不含任何凭证,graylog-ops.json、notify.json 不会打进镜像。

运行

docker load -i malicious-ip-watch-YYYY-MM-DD.tar.gz

docker run -d --name malicious-ip-watch --restart unless-stopped \
  -p 8088:8088 \
  -v malicious-ip-data:/data \
  -e GRAYLOG_URL=http://10.180.254.17:9002 \
  -e GRAYLOG_TOKEN='你的 token' \
  -e GRAYLOG_STREAM=F5_Accesslog \
  -e APP_PORT=8088 \
  -e APP_PATH_PREFIX='/s/<随机密钥>' \
  -e BEHAVIOR_INTERVAL=900 \
  -e NOTIFY_TYPE=dingtalk \
  -e NOTIFY_URL='https://oapi.dingtalk.com/robot/send?access_token=...' \
  -e NOTIFY_SECRET='SEC...' \
  -e NOTIFY_LINK_PREFIX='http://<公网地址>/s/<随机密钥>/' \
  malicious-ip-watch:YYYY-MM-DD
  • 想使用镜像内数据快照:使用新的数据卷名,或先 docker volume rm 旧卷
  • 想保留线上数据:继续使用原数据卷,镜像里的 seed 不会覆盖
  • 健康检查:/healthz,容器状态应为 healthy

归属数据(GeoIP / ASN)

GeoIP

  • 数据库:data/geo/geo.db(DB-IP Lite City,约 144MB,360 万 IPv4 段)
  • 列表显示中文国家名 + 省/州;详情显示城市
  • 更新(每月):
curl -O https://download.db-ip.com/free/dbip-city-lite-2026-10.csv.gz
gunzip -kf dbip-city-lite-2026-10.csv.gz
python3 -m graylog_ops.geo \
  --city-csv data/geo/dbip-city-lite-2026-10.csv \
  --out data/geo/geo.db

关闭:--geo-db ""

ASN / 厂商

  • 数据库:data/geo/asn.db(DB-IP ASN Lite,约 18MB)
  • 品牌映射:asn_brand_map.json(38+ 条关键词 → 厂商/运营商 + 图标 + 类型)
  • 更新:
curl -O https://download.db-ip.com/free/dbip-asn-lite-2026-10.csv.gz
gunzip -kf dbip-asn-lite-2026-10.csv.gz
python3 -m graylog_ops.asn \
  --csv data/geo/dbip-asn-lite-2026-10.csv \
  --out data/geo/asn.db

新增厂商:在 asn_brand_map.json 的 items 里加关键词、品牌名、类型、图标即可,不用改代码。

类型取值:cloud(云服务商)、idc(IDC/机房)、mobile(移动网络)、broadband(宽带)、education(教育网)、enterprise(企业/政府)、unknown(未知)。

关闭:--asn-db ""


目录结构

graylog-ops/
├── bin/graylog-ops              # 启动脚本
├── graylog_ops/
│   ├── cli.py                   # 参数解析/子命令注册
│   ├── commands.py              # 各子命令实现
│   ├── watch.py                 # 主服务:检测、存储、Web、API、监控
│   ├── behavior.py              # 行为检测 + 融合推送
│   ├── backfill.py              # 自动补测/历史回扫
│   ├── notify.py                # 推送通道(钉钉加签等)
│   ├── geo.py                   # GeoIP 构建/查询
│   ├── asn.py                   # ASN 构建/查询/品牌映射
│   ├── client.py / http.py      # Graylog API 客户端
│   ├── detect.py / judge.py     # 字段推断/判断层(早期命令)
│   └── config.py / render.py    # 配置/终端渲染
├── profiles/f5.json             # F5 字段映射
├── tools/                       # 补测脚本
├── docker-build/                # Dockerfile + entrypoint(app/seed 构建时生成)
├── tests/                       # 86 项测试 + 假 Graylog
├── docs/screenshots/            # 界面截图
├── data/                        # 运行时数据(gitignore)
├── allowlist.example.txt
├── asn_brand_map.json
├── config.example.json
├── notify.example.json
└── README.md

开发与测试

# 全部单元 + 端到端测试(不需要真实 Graylog)
python3 -m unittest discover -s tests

# 用假 Graylog 预览各命令输出
python3 tests/demo.py

测试覆盖:字段推断、判断层、GeoIP/ASN、推送(含钉钉加签)、融合合并、推送退避、分页/搜索、监控指标、历史画像保留等。

提交前建议至少跑一次 python3 -m unittest discover -s tests。


已知限制

  • 只读:不下发封禁,不写 Graylog;封禁执行需对接 F5/AD/防火墙
  • 单机 SQLite:适合单实例;多实例部署需要换成共享数据库或只让一个实例写
  • 面板鉴权:默认无登录,靠监听地址/路径前缀/反代保护;公网暴露请务必设置随机 --path-prefix 并加 TLS/访问控制
  • 行为候选上限:行为分析只取 24h 访问量前列 + 4xx 较多 IP(约 600 条),极端大流量日可能漏掉长尾
  • 证据依赖 Graylog 保留期:日志过期后详情页回退到本地快照;更久的历史证据需要自行归档
  • 自动补测上限:缺口超过 7 天默认跳过,避免一次回扫过久
  • 共享出口不封:移动/教育网/CGNAT 的行为异常只告警,这是有意的策略
  • 时区:数据库和 API 使用 UTC,页面/推送使用本地时区;定时任务请按本地时区换算

排错

现象 处理
连接超时 Graylog 端口通常是 9002;检查网络/VPN
401 token 错误或已吊销;确认是 API token
403 账号缺少权限(messages:read、searches:* 等)
页面一直“加载失败” 查看 docker logs / 终端日志;确认 Graylog 可达
状态灯红色 最近一轮检测失败;看 /api/status 的 last_error
钉钉推送 310000 签名不匹配 机器人开了「加签」,补 NOTIFY_SECRET=SEC...,或用关键词/IP 白名单模式
名单没有新 IP 先看 /api/status 和 /api/monitor;确认窗口内确有攻击流量;检查白名单/内网排除
启动没有自动补测 meta.detect_last_success 不存在时会回退到 ip_stats.updated_at;缺口 ≤ 阈值或 > 7 天都会跳过
行为检测结果为空 24h 内没有达到 60 分且 ≥2 信号的 IP;或访问量太小(<10)
页面分页空白 数据量变化后页码会自动回退;刷新即可
Docker 启动后数据没变 旧数据卷优先;换新卷名或删除旧卷才会用镜像内 seed
时间看起来差 8 小时 DB/API 是 UTC,页面按本地时区展示;核对时注意时区

权限

只读 token 至少需要:

messages:read
searches:relative
searches:absolute
searches:keyword
streams:read

(probe 会逐项显示哪些接口可用。)


安全建议

  • graylog-ops.json、notify.json 已加入 .gitignore,权限设为 600
  • 公网分享时使用随机 --path-prefix,并通过反向代理加 TLS/访问控制
  • 钉钉/企微 webhook 密钥不要提交到仓库;迁移后建议轮换一次
  • 镜像不包含凭证,部署时用环境变量注入
  • 定期备份 data/ips.db(SQLite 可用 .backup 或停服后复制)