floreabogdan/meerkat
GitHub: floreabogdan/meerkat
一个运行在路由器上的 Suricata 告警控制台,通过按源地址汇总告警并丰富地理信息,将海量事件转化为可操作的分源决策。
Stars: 0 | Forks: 0
# meerkat
**一个在你的路由器上_运行_的 [Suricata](https://suricata.io/) 控制台。**
它追踪 `eve.json`,通过 ASN、国家和城市丰富每一条告警,将其存储在
SQLite 中,并提供一个 web UI —— 其主页是**源地址**列表,
而不是事件列表。
以 *Suricata suricatta* —— 狐獴命名,那种站岗放哨并发出警报叫声的哨兵。
[](https://github.com/floreabogdan/meerkat/actions/workflows/ci.yml)
[](https://github.com/floreabogdan/meerkat/releases)
[](go.mod)
[](LICENSE)
[](https://buymeacoffee.com/floreabogdan)
## 缘由
在实际运行的路由器上测量:**四分钟内产生 891 条告警**,其中
| 占比 | 类别 | 具体是什么 |
| --- | --- | --- |
| 68.8% | ET CINS | “源地址在信誉列表中” |
| 16.3% | ET DROP | Dshield 黑名单,同上 |
| 10.6% | GPL ICMP | 有人在 ping |
| 2.6% | SURICATA | 引擎事件(无效 ack、校验和) |
| **1.4%** | **ET SCAN** | **实际扫描** |
| **0.3%** | **ET COMPROMISED** | **实际被攻破的标志** |
尝试过将日志通过管道输出到聊天频道,结果几分钟内就被淹没了。教训不在于“更严格地过滤”——而在于聊天频道是错误的工具。按源地址汇总后,这 891 行变成了几十行,每一行都是一个决定:**封禁、确认、加入白名单或忽略。**
这就是该产品的全部核心。其他一切皆由此衍生。
 One source: what it tripped, which ports, what was done about it. |
 The installed ruleset, joined to how much each rule has actually cost you. |
 The overview: volume, what is making the noise, where from, what is being probed. |
**观察**
- 源地址汇总:首次/最后出现时间、告警、签名、端口、最严重状态、状态
- 按国家、AS、端口、签名、严重程度、状态、时间窗口、最小数量进行过滤
- 实时追踪、操作员时间线、按签名细分明细
**操作**
- 封禁、解封、确认、加入白名单 —— 单个源地址或批量操作,附带原因和过期时间
- 封禁是对 **nftables** 的调用,绝不是 Suricata 的 `drop`
- 按签名配置处理方式:一键通知、摘要或静默信誉源
**管理传感器**
- 整个已安装目录,启用/禁用单条规则或整个类别,覆盖严重程度
- 通过使用 `suricata-update` 重建并实时重载来应用 —— 然后从磁盘重新读取
|
## 安装前必读
**不提供任何支持。**无担保、无 SLA、不保证适用于任何目的。欢迎提交 Issues 和 pull requests,但也可能被忽略。请参阅 [LICENSE](LICENSE)。
## 安装说明
meerkat 运行在路由器上,紧挨着 Suricata。选择以下方式之一。
Linux 软件包 (.deb / .rpm / .apk)
每个发布版本都提供 amd64、arm64 和 armhf 架构的软件包。它们会将二进制文件安装到
`/usr/bin/meerkat`,包含两个 systemd 单元和一个 path 单元,并创建一个属于
`adm` 组的 `meerkat` 系统用户 —— 这使得 Debian 下权限为 `0640 root:adm` 的 `eve.json` 变得可读:
```
# Debian / Ubuntu
sudo apt install ./meerkat_*_amd64.deb
# RHEL / Fedora
sudo dnf install ./meerkat-*.x86_64.rpm
```
该软件包**不会**启动 meerkat。请先进行设置:
```
sudo meerkat init # create the admin account
sudo meerkat doctor # check eve.json access, geo databases, nftably
sudo systemctl enable --now meerkat
```
它推荐安装 `suricata` 但不作强制要求:meerkat 对已停止的传感器依然有用,并且可以指向从其他机器复制过来的 `eve.json`。(提供 `.apk` 只是为了方便;Alpine 使用 OpenRC,因此你需要在它下面提供自己的服务。)
下载二进制文件
从
[最新发布版本](https://github.com/floreabogdan/meerkat/releases/latest)
中获取适用于你平台的归档文件(linux amd64/arm64/arm, freebsd, macOS),使用 `SHA256SUMS.txt` 进行校验,并将
二进制文件丢到路由器上:
```
tar -xzf meerkat_*_linux_amd64.tar.gz
sudo install meerkat /usr/local/bin/meerkat
```
go install
需要 Go 1.25+。该二进制文件是静态的 (`CGO_ENABLED=0`);SQLite 使用的是
[modernc.org/sqlite](https://modernc.org/sqlite),因此无需链接任何依赖库。
```
go install github.com/floreabogdan/meerkat/cmd/meerkat@latest
```
或者从任何地方交叉编译并复制一个文件到路由器:
```
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath \
-ldflags="-s -w -X github.com/floreabogdan/meerkat/internal/buildinfo.Commit=$(git rev-parse HEAD)" \
-o meerkat ./cmd/meerkat
scp meerkat root@router:/usr/local/bin/meerkat
```
Docker
多架构镜像已发布到 GitHub Container Registry。meerkat 必须**读取
宿主机的 `eve.json`**,因此请将其以只读方式挂载,并确保容器用户可以打开它:
```
# 一次性:创建数据库和 admin 账户
docker run --rm -it -v meerkat-data:/var/lib/meerkat \
ghcr.io/floreabogdan/meerkat:latest init --label edge1
# 运行它,仅可在 host 的 loopback 上访问
docker run -d --name meerkat --restart unless-stopped \
-p 127.0.0.1:8100:8100 \
-v meerkat-data:/var/lib/meerkat \
-v /var/log/suricata/eve.json:/var/log/suricata/eve.json:ro \
--group-add "$(getent group adm | cut -d: -f3)" \
ghcr.io/floreabogdan/meerkat:latest
```
规则管理在容器中不起作用:特权应用步骤需要宿主机的
`/etc/suricata` 和 Suricata 的控制 socket。控制台会如实说明这一点,而不是假装可用。
然后打开 `http://
:8100`。meerkat **在所有接口的 8100 端口上监听**,并且
**没有 TLS**,因此登录后要做的第一件事就是缩小谁能访问它的范围 ——
设置 → 访问控制,或者绑定回环地址并使用 SSH 隧道。在你这样做之前,控制台和启动日志中都会提示这一点。
## 目前可用的功能
**数据摄取**
- **追踪 `eve.json`**,就像 `tail -F` 那样:能够在轮转、截断、Suricata
停止运行、半写记录中存活下来,并记住其读取偏移量,因此重启
既不会重放文件,也不会跳过它宕机期间到达的内容。
- **在不解码的情况下拒绝非告警记录。**在繁忙的路由器上,98.5% 的 `eve.json` 是流和
统计记录;解码一个 40 KB 的统计块仅仅为了丢弃它,将会占据大部分开销。
- **应用背压而不是丢弃。**当写入器落后时,追踪器
停止读取 —— 数据依然在磁盘上。一个在负载下悄悄丢弃数据的控制台
比没有控制台更糟糕。
- **丰富**每个源的 ASN、组织、国家以及(使用城市数据库时的)城市
和坐标,数据来自本地 `.mmdb` 文件。查询永远不会离开本机。
- **留存**基于时间,并设有硬性上限作为洪水的最后防线。汇总数据在清理后依然存在:某个
被分流的源是一项决定,而决定的生命力比触发它们的告警更长。
**分流**
- **按源汇总**:首次和最后出现时间、告警数量、不同签名、不同
端口、最严重状态、分流状态。采用增量维护,因此即使
在洪水般的攻击下主页依然能保持快速响应。
- **源控制台** —— 按国家、AS、目标端口、签名、严重程度、状态、
时间窗口和最小数量进行过滤;按任意列排序;对结果进行分页。
- **源详情** —— 随时间的活动情况、涉及的签名、端口、geo/AS 身份、
带有协议上下文的单个告警,以及已执行操作的台账。
- **封禁、解封、确认、加入白名单** —— 单独或批量操作,附带原因和
可选的过期时间。封禁是对 nftably 的调用;只有在
该调用成功后,meerkat 才会将源标记为已封禁,并且每两分钟与 nftably 的真实黑名单进行核对,
以确保该状态真实有效。
- **按签名配置处理方式** —— 通知、摘要或静默。静默信誉源只需
一键即可完成,它改变的是打扰你的内容,而不是保留的内容。
- **实时追踪**用于实时监控传感器,并提供一个**操作员时间线**,记录每一次
登录、设置更改、分流决定和留存操作。
**传感器规则集**
- 整个已安装的目录 —— 该路由器上有 68,005 条规则 —— 从
Suricata 实际加载的文件中读取,并与每条规则消耗了你多少资源相关联。
- 启用或禁用某条规则或整个类别并附带原因,覆盖严重程度,或者标记
某条规则,使得任何触发它的事物都会**被即时封禁**(在 nftables 中,绝不是作为 Suricata 的
`drop`)。
- 通过使用 `suricata-update` 重建并实时重载传感器来应用更改,
然后**从磁盘重新读取**以检查是否生效。
- 计划的规则集更新,每次运行的历史记录,以及等待应用的更改差异。
**其他方面**
- **威胁地图发布** —— 将 gzip 压缩的批次发送到收集器,从持久化的游标读取,
屏蔽你自己的网络,并且绝不包含目标地址。默认关闭,
并且需要屏蔽的前缀由你设置:默认配置仅覆盖私有地址空间。
- **`meerkat doctor`** —— 检查实际会出错的地方:`eve.json` 是否可读(默认为
`0640 root:adm`)、是否是最新的、Suricata 是否在运行、geo 数据库是否
已加载并解码、nftably 是否可连通以进行封禁、数据库是否
可写、应用 path 单元是否已启用。
- 基于用户的主题保存在你的账户中,而不是浏览器上:**浅色 / 深色 / 系统** 外加
一种强调色。
## 四项已定原则
这些决定是基于测量数据而非个人偏好做出的。改变它们需要比原来更好的证据。
**1. Suricata 保持仅告警模式。**它运行在 NFQUEUE 的 inline 模式下,如果保持默认设置,它会
丢弃 **2,676,291 个数据包中的 258,101 个 —— 占传输流量的 9.6%**。这并非由任何规则引起:而是因为
`exception-policy: auto` 解析为了 drop-flow。一个悄悄吞掉其检测流量十分之一的 IDS
比没有 IDS 更糟糕。
**2. 封禁通过 nftables 进行,绝不通过 Suricata。**meerkat 将封禁请求推送到
[nftably](https://github.com/floreabogdan/nftably) 的基于 token 验证的 `POST /api/block`,这会将
该地址添加到一个命名集中,并将其推送到实时内核集中。nftably 的 README 将此
指定为预期的切入点:*“连接你自己的检测系统,让 nftably 来执行丢弃操作。”*
这在面临规则管理时依然有效。`suricata-update` 可以将规则的操作重写为
`drop` —— 配置文件中的一行 —— 而 meerkat 绝不会这么做。“始终封禁此规则”
意味着当规则触发时,meerkat 会将源地址推送到 nftably,并且如果字符串 `drop.conf` 出现在编写这些文件的代码中,测试将使构建失败。
**3. 公共威胁地图永远不显示客户 IP。**目标报告为站点
名称加上端口,绝不是地址。
**4. “已检测”和“已封禁”即其字面意思。**一条告警是*已检测*。只有确实在
nftables 中被封禁的地址才是*已封禁* —— 失败的封禁调用会将源地址保留在其真正
所在的状态,并且操作台账会记录远端的响应。告警表
逐字显示 Suricata 自己的判定,在仅告警的传感器上,这始终是 `allowed`,即使
对于最恶劣的行为也是如此。
## 配置
所有设置都存在于数据库中并在 UI(设置)中进行编辑,因此不存在会发生漂移的配置
文件。`meerkat init` 会设置合理的默认值;在单次运行中,标志会覆盖监听地址、
数据库路径和 `eve.json` 路径。
第一天值得设置的四件事:
| 设置 | 原因 |
| --- | --- |
| **访问控制** | meerkat 绑定所有接口且无 TLS。请缩小范围,或者绑定回环地址并使用 SSH 隧道。 |
| **数据丰富** | 开启每月的 DB-IP Lite 下载,或者将 `.mmdb` 文件放入数据目录。如果没有它们,源将没有国家或 AS,过滤栏的作用将大打折扣。 |
| **封禁** | nftably 的 URL 和在其 设置 → 自动化 API 下生成的 API token。在生成 token 之前,它的 `/api/block` 会返回 404 而不是 401 —— 这意味着该功能是关闭的,而不仅仅是被锁定。 |
|Suricata** | 规则集存放的位置,是否按计划获取新规则,以及规则是否可以即时封禁。在你开启之前,这两项都是关闭的。 |
## 在不以 root 身份运行的情况下更改规则
meerkat 的控制台完全不具备任何 capabilities。它整天都在解析受攻击者影响的输入,
因此它无法写入 `/etc/suricata`,无法运行 `suricata-update`,也无法打开 Suricata
属于 root 的控制 socket —— 规则管理也改变不了这一点。这项工作被拆分开了:
```
console (user meerkat) meerkat-apply.service (root)
renders disable.conf/enable.conf
into /var/lib/meerkat/suricata/
writes apply.request ─────► meerkat-apply.path notices
install the filter files
run suricata-update
count the rebuilt ruleset
reload Suricata over its socket
reads apply.result ◄───── write apply.result
re-indexes from the file on disk
```
一个文件构成了整个协议。sudoers 条目意味着要在确切读取恶意输入的
进程上放宽 `NoNewPrivileges`;root daemon 意味着需要设计和
维护一个 IPC 攻击面。特权步骤不接受任何尚未给出的参数,不做出
任何决定,并且会自行退出。
有两点后果值得一提。控制台报告控制 socket **不可达** ——
这是正常的,因为 Suricata 创建它时权限为 `0660 root:root`;应用步骤以 root 身份运行,
可以正常访问。如果 `meerkat-apply.path` 未启用,更改将被暂存而不会被
拾取,因此规则页面会在十五分钟后提示这一点,而不是永远等待。
`meerkat doctor` 会检查这两者。
此后的任何内容都不被信任:一旦应用步骤报告完成,meerkat 会从磁盘重新读取构建好的
规则集,并将每个决定与传感器实际持有的内容进行比较。
当启用的规则依赖于某个已禁用规则的 flowbits 时,`suricata-update` 会保持该禁用规则处于活动状态,因此
“我禁用了 299 条规则”和“有 299 条规则被禁用了”是不同的说法,控制台只会做出
第二种陈述。
## 存储
一条告警记录大约为 200 字节。因此,一个每天产生 30 万条告警的传感器
约为 60 MB/天,默认的 7 天留存期将其限制在约 450 MB。`max_events` 上限是
应对会提前耗尽时间窗口的洪水般攻击的最后防线。确切的 `eve.json` 行
被刻意不存储 —— 在如此巨大的数据量下,它每天会产生数百兆字节的
几乎重复的 JSON,而分流所需的所有信息都已经是其中的一列,变化多端的
协议上下文(HTTP host、TLS SNI、DNS name、SSH banner)则保存在其旁边的一个小 blob 中。
## 安全性
**meerkat 默认监听所有接口并提供明文 HTTP。**它故意采用这种发布方式 ——
一个需要先编辑配置文件才能响应的控制台是一个没人会去设置的控制台 ——
但这意味着登录后要做的第一件事就是缩小谁能访问它的范围。
- 在所有内容之前设置**IP 白名单**(设置 → 访问控制)。白名单之外的客户端
其连接将被直接关闭,而不是收到 403 错误,因此扫描器无法判断是否有
服务在监听。回环地址始终被允许,因此 SSH 隧道不会将你锁在
外面。这并不是加密:要么传递 `--tls-cert`/`--tls-key` 以使用原生 HTTPS(TLS 1.2+),要么使用
`--listen 127.0.0.1:8100` 运行并进行隧道传输。
- 带有 bcrypt 哈希的本地账户;服务端会话存储为 SHA-256 哈希值,因此读取
数据库并不会交出可用的 bearer token。
- 失败的登录会**按源 IP** 进行限流 —— 绝不按用户名限流,否则会允许任何人
故意将管理员锁定。
- 严格的 CSP,没有内联脚本或样式,`SameSite=Strict` cookie,并且每次
写入操作都会在服务端进行同源检查。
- 服务账户**完全不具备任何 Linux capabilities**。它通过组成员身份读取 `eve.json`
并通过 HTTP 与 nftably 通信。它本身无法更改防火墙。
请参阅 [`SECURITY.md`](SECURITY.md) 了解威胁模型以及如何报告问题。
## 开发说明
```
go test -race ./...
```
纯 Go 编写 —— 使用 `modernc.org/sqlite`,无 cgo —— 因此它可以在
没有工具链的情况下交叉编译到路由器上。UI 是通过服务端渲染的 `html/template`,结合了 `go:embed` 和少量原生
JavaScript。没有 node 构建步骤,以后也不会有。
[`PLAN.md`](PLAN.md) 是最初的设计文档,作为意图的记录保留;该产品
后来已经超越了它。[`CONTRIBUTING.md`](CONTRIBUTING.md) 包含了内部的惯例 ——
特别是 CSP 会静默丢弃内联样式,因此 `style="width:…"` 的进度条在
服务端测试中看起来没问题,但在浏览器中渲染却是错的。
上面的截图是由
[`internal/web/preview_test.go`](internal/web/preview_test.go) 中的测试工具生成的,它会初始化一个数据库,在其上提供
**真实的**控制台,并驱动 headless Chrome 浏览各个页面 —— 因此截图
不会偏离产品实际渲染的效果:
```
MEERKAT_PREVIEW=/usr/bin/chromium MEERKAT_PREVIEW_OUT=docs/screenshots \
go test ./internal/web -run TestPreview -v
```
这些截图中的每个地址都属于文档说明空间
([RFC 5737](https://www.rfc-editor.org/rfc/rfc5737),
[RFC 3849](https://www.rfc-editor.org/rfc/rfc3849)),每个 AS 编号都来自
[RFC 5398](https://www.rfc-editor.org/rfc/rfc5398) 的文档范围。流量*混合比例*是
在路由器上测量到的真实情况;但地址不是。
## 姊妹项目
针对同一个路由器的三个工具,每个只做一件事,并且互不干扰:
| | | |
| --- | --- | --- |
| [**birdy**](https://github.com/floreabogdan/birdy) | BGP | BIRD 2.x 的 web UI —— 建立配置模型,预览,并通过带有武装的自动回退来应用 |
| [**nftably**](https://github.com/floreabogdan/nftably) | firewall | nftables 规则和集合,包含 meerkat 通过其进行封禁的 API |
| **meerkat** | IDS | 本项目 |
## 许可证
[BSD Zero Clause](LICENSE) —— 等同于公共领域。随你怎么处理;无需
署名,也不提供任何担保。
内置的 web 字体为 [IBM Plex](https://github.com/IBM/plex),版权归 IBM Corp. 所有,根据
SIL Open Font License 1.1 使用 —— 请参阅
[`internal/web/static/fonts/LICENSE.txt`](internal/web/static/fonts/LICENSE.txt)。该许可
仅涵盖字体,不包含 meerkat。GeoIP 数据为 DB-IP Lite,CC-BY-4.0,在运行时下载,
此处不进行再分发。标签:EVTX分析, Go, PB级数据处理, Ruby工具, SQLite, Suricata, WSL, 威胁情报, 安全运维, 开发者工具, 日志审计, 现代安全运营, 自动化扫描, 请求拦截