izm1chael/goban

GitHub: izm1chael/goban

一款用 Go 编写的高性能 fail2ban 替代品,通过原生 Netlink 对接内核防火墙实现极低开销的日志监控与 IP 封禁。

Stars: 0 | Forks: 0

# GoBan [![CI](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/ce6709edff225947.svg)](https://github.com/izm1chael/goban/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Go Report](https://goreportcard.com/badge/github.com/izm1chael/goban)](https://goreportcard.com/report/github.com/izm1chael/goban) 一个类似 fail2ban 的日志监控与 IP 封禁工具,使用 Go 编写。 GoBan 可以从文件、Docker 容器日志或 systemd journal 中追踪 (tail) 日志流; 根据每条规则的 regex (正则表达式) 匹配对应的行;并通过 `iptables` + `ipset`(针对 IPv6 则使用 `ip6tables`) 在内核层面封禁违规 IP。它附带了一个控制客户端(`goban-client`),用于查看状态、列表以及手动封禁/解封。 与经典的 fail2ban 相比,它在以下三个方面有所不同: 1. **单一二进制文件,静态链接的 Go 程序。** 无需 Python 运行时,镜像大小约 10 MB。 2. **容器原生支持。** 在 Portainer 堆栈中以兄弟容器 (sibling container) 的形式运行,加上 `--cap-add NET_ADMIN --network host` 参数,即可保护宿主机上的所有其他服务。 3. **通过直接 netlink 由 ipset 支持的封禁。** 在成千上万次封禁中提供 O(1) 查找,并带有内核侧的 TTL 过期机制 —— 无需 Go 侧的解封计时器,也无需每次封禁都 fork-and-exec。 ## 基准测试 在同一内核、相同工作负载和相同规则下对 GoBan v1.1 与 fail2ban v1.0.2 进行对比(`benchmark/` 目录中包含完整的可复现测试工具 —— 合成的 sshd-fail 生成器、基于 /proc 的采样器,以及每个单元格 60 秒的稳态测试)。 **原生,真实 iptables(生产场景):** | 行速率 | GoBan CPU | fail2ban CPU | 加速比 | GoBan RSS | fail2ban RSS | RSS 比率 | |-----------|-----------|--------------|---------|-----------|--------------|-----------| | 100/sec | **0.08%** | 2.37% | **30x** | 12.7 MB | 51.3 MB | 4.0x | | 1K/sec | **0.75%** | 25.03% | **33x** | 14.8 MB | 51.1 MB | 3.5x | | 10K/sec | **7.49%** | 119.88% (饱和) | **16x** | 15.4 MB | 51.4 MB | 3.3x | **容器,真实 iptables (Docker,`--network host`):** | 行速率 | GoBan CPU | fail2ban CPU | 加速比 | |-----------|-----------|--------------|---------| | 100/sec | 0.08% | 2.37% | 30x | | 1K/sec | 0.79% | 7.96% | 10x | | 10K/sec | 7.68% | 61.62% | 8x | **饱和状态下的吞吐量准确度(实际处理的行数):** 仅看 CPU 和 RSS 数据会产生误导,因为 fail2ban 在跟不上时会静默丢弃日志行。`benchmark/accuracy.sh` 测量了每个守护进程实际看到的生成行比例。 | 行速率 | GoBan 原生 | GoBan 容器 | fail2ban 原生 | fail2ban 容器 | |-----------|--------------|-----------------|-----------------|--------------------| | 1K/sec | 96.1% | 98.4% | 98.4% | 98.4% | | 5K/sec | 97.3% | 98.3% | 98.1% | **86.6%** | | 10K/sec | 97.2% | 97.7% | **54.2%** | **43.8%** | GoBan 在所有速率下都保持在约 97-98%(缺失的 2-3% 是采样时仍在处理管线中的最后几行 —— 延长测试后的稳定窗口可以缩小这一差距)。fail2ban“在 10K 速率下达到 119% CPU 饱和”,实际上是在饱和的同时丢弃了近一半的输入。 这在实际应用中的意义是: - **在 10K/sec 时,GoBan 每秒处理的行数比 fail2ban 原生多约 80%**,而使用的 CPU 却减少了约 16 倍(7.49% vs 119%)。GoBan 仍然保留了单核约 92% 的余量。 - **在饱和点上,GoBan 的效率比 fail2ban 高约 28 倍**(每个 CPU 百分比处理的行数):GoBan 约为 1300 行/%,而 fail2ban 约为 46 行/%。 - **内存:常驻内存集小约 3-4 倍。** GoBan 的 RSS 随负载平缓增长(更多的追踪器状态);fail2ban 固定的约 51 MB 大部分是 Python 运行时开销 —— 这也是空闲状态下的基础占用。 - **对于 GoBan,iptables 的开销几乎为零**,因为自 v1.1 起,此版本中新增的 netlink 直连 ipset 客户端将每次封禁时的 `ipset` 二进制文件的 fork-and-spawn 替换为一次内核系统调用。在 1K bans/sec 的速率下,iptables 模式的 CPU 开销从 7.73 个百分点 (v1) 降至 0.03 个百分点 (v1.1)。 - **Docker 对 GoBan 的开销可以忽略不计** —— 原生与容器环境下的数据在采样噪声范围内重叠。相比之下,在相同输入速率下,fail2ban 容器比 fail2ban 原生丢弃了更多的行。 在你自己的机器上复现: ``` make build sudo bash benchmark/full-suite.sh 60 # CPU/RSS matrix, ~30 min sudo bash benchmark/accuracy.sh 30 # line-processing accuracy, ~10 min ``` ## 安装 (Debian / Ubuntu / Fedora / RHEL / Arch) 每次发布 (release) 都附带了主要格式的预构建包: ``` # Debian / Ubuntu / Mint / Raspbian / Kali sudo apt install ./goban_1.0.0_amd64.deb # Fedora / RHEL / Rocky / Alma / openSUSE sudo dnf install ./goban-1.0.0-1.x86_64.rpm # 或者:sudo rpm -i ./goban-1.0.0-1.x86_64.rpm # Arch / Manjaro / EndeavourOS / SteamOS sudo pacman -U ./goban-1.0.0-1-x86_64.pkg.tar.zst ``` 该软件包会安装: - `/usr/local/bin/goban-daemon` 和 `/usr/local/bin/goban-client` - `/etc/goban/goban.yaml`(示例配置;升级时保留) - `/etc/goban/rules.d/*.yaml`(10 个内置规则文件) - `/usr/lib/systemd/system/goban.service`(以及可选的 `goban-persist.service`,用于 ipset 重启持久化) - 用于控制套接字所有权的系统 `goban` 用户/组 `iptables` 和 `ipset` 被声明为依赖项,因此包管理器会自动拉取它们。守护进程 **不会** 自动启动 —— 请先检查配置,然后: ``` sudo systemctl enable --now goban sudo systemctl status goban ``` **其他发行版:** Alpine、Void、Gentoo 及类似系统 —— GitHub Release 中的静态二进制文件可在任何安装了 `iptables` 和 `ipset` 且内核版本 ≥3.x 的 Linux 上运行。将 `bin/goban-daemon` 放入 `/usr/local/bin/` 并从 `deploy/goban.service` 复制 systemd 单元(如果适用)。在容器中运行守护进程的 Alpine 用户应使用容器镜像仓库中的 `goban:latest` —— 目前 nfpm 生成的 `.apk` 文件与 Alpine 的 `apk-tools` 之间存在已知的兼容性问题,我们尚未解决。 ## 从源代码构建 ``` git clone https://github.com/izm1chael/goban cd goban make build # produces bin/goban-daemon, bin/goban-client (CGO-free) make test-race # all packages green with race detector make docker-build # alpine image with iptables + ipset baked in ``` 要支持 journald(构建标签 `journald`,需要 `libsystemd-dev` + CGO): ``` sudo apt install libsystemd-dev # Debian/Ubuntu make build-journald ``` 对于本地包构建(`.deb`, `.rpm`, `.pkg.tar.zst`),请安装 nfpm: ``` go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.42.1 make package VERSION=1.0.0 ls dist/ # three packages, ready to install or distribute ``` ## 快速入门 (容器) ``` git clone https://github.com/izm1chael/goban cd goban make docker-build docker run --rm --network host \ --cap-add NET_ADMIN --cap-add NET_RAW \ -v /var/log:/var/log:ro \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ -v $(pwd)/examples/goban.yaml:/etc/goban/goban.yaml:ro \ -v $(pwd)/examples/rules.d:/etc/goban/rules.d:ro \ -v /run/goban:/run/goban \ goban:latest ``` 在另一个 shell 中: ``` goban-client status goban-client rules goban-client list ``` ## 快速入门 (宿主机安装,systemd) ``` make build sudo install -m 0755 bin/goban-daemon /usr/local/bin/ sudo install -m 0755 bin/goban-client /usr/local/bin/ sudo install -d -m 0755 /etc/goban /etc/goban/rules.d sudo cp examples/goban.yaml /etc/goban/goban.yaml sudo cp examples/rules.d/*.yaml /etc/goban/rules.d/ sudo cp deploy/goban.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now goban.service ``` ## 概念 - **Source(来源)**:一个日志流。可以是 `file`(任何路径)、`docker`(按名称或标签选择的容器)或 `journal`(systemd journal,仅限 journald 构建)之一。 - **Rule(规则)**:附加到来源的 regex (正则表达式) 过滤器。当同一 IP 在 `findtime` 内触发 regex 的次数超过 `max_retries` 时,规则会请求 banner 封禁该 IP `bantime` 时长。 - **Banner(封禁器)**:内核防火墙后端。内置两种实现: - `iptables`(默认):两个 ipset(`goban-ban-v4` + `goban-ban-v6`)以及引用它们的 iptables/ip6tables INPUT 规则。热路径操作直接与 netlink 通信,对接 ipset 子系统。 - `nftables`:原生 netlink 连接到 `NFNL_SUBSYS_NFTABLES`。创建一个 `inet goban` 表、两个带超时的集合、一个 input 挂钩链和两条 drop 规则。无外部二进制文件依赖。在 goban.yaml 中设置 `banner.backend: nftables`。 - **Allowlist(白名单)**:永不被封禁的 CIDR。默认列表包含回环地址和所有 RFC1918 范围,启动时 GoBan 会自动将绑定到本地接口的所有地址加入白名单,因此宿主机无法封禁自身。 ## 配置 分层加载:内置默认值 → YAML 文件 → 环境变量。以下文件足以保护 SSH: ``` # /etc/goban/goban.yaml log_level: info sock_path: /run/goban/goban.sock defaults: max_retries: 3 findtime: 10m bantime: 24h sources: - type: file name: auth-log path: /var/log/auth.log rules: - name: sshd source: auth-log regex: 'Failed password for (?:invalid user )?\S+ from (?P\S+) port' ``` 对于更丰富的设置,可将 `rules_dir:` 指向 `/etc/goban/rules.d/` 并放入内置的规则文件。它们涵盖 sshd(基本 + 激进模式)、nginx(noscript、http-auth、badbots、常见探测)、apache(auth、noscript、overflows)、WordPress(wp-login + xmlrpc)、Nextcloud(login + bruteforce)、Postfix(SASL + RBL)、Dovecot、vsftpd、Traefik(auth + ratelimit)以及 Portainer。 有用的环境变量覆盖: ``` GOBAN_LOG_LEVEL=debug GOBAN_REPLAY_ON_START=false GOBAN_SOCKET_MODE=0660 GOBAN_DEFAULT_FINDTIME=10m GOBAN_DEFAULT_BANTIME=1h GOBAN_IPV6=true GOBAN_ALLOWLIST=10.0.0.0/8,192.168.0.0/16 ``` ## 热配置重载 编辑 `/etc/goban/rules.d/my-rule.yaml`(或 `goban.yaml` 中的任何内容)并让守护进程在不丢弃任何内容的情况下获取更改: ``` sudo systemctl reload goban # uses SIGHUP under the hood # 或者 sudo goban-client reload # equivalent, via the control socket ``` 守护进程在应用任何内容之前会完整验证新配置。如果出现任何故障(YAML 格式错误、regex 无效、缺少源),正在运行的守护进程将 **保持不变**,并且错误会被记录(或返回给 `goban-client reload`)。 `name`、`regex`、`max_retries`、`findtime`、`bantime` 和白名单均未更改的规则,在重载后会保留其追踪器状态。 语义发生变化的规则将获得全新的追踪器(这是符合操作员预期的行为 —— 因为规则对“什么算作一次触发”的解释发生了变化)。 **仅限重启** 的字段(重载会拒绝更改,必须重启守护进程): `sock_path`、`socket_mode`、`socket_group`、`ipset_name_v4`、`ipset_name_v6`、 `ipv6`、`dry_run`、`batch_bans`、`state_path`、`audit_log`。 ## 持久化触发状态 进行中的触发计数(失败了一两次但尚未达到阈值的 IP)会在守护进程重启后保留。状态每 30 秒转储一次到 `/var/lib/goban/state-.gob`,并在优雅关闭时保存,在启动时重新加载。已被封禁的 IP 会一直保留在内核 ipset 中,因此这只会影响“差一点就被封禁”的攻击者 —— 但它阻止了坚定的攻击者通过等待守护进程重启来重置其触发计数。 可在 YAML 中调节: ``` state_path: /var/lib/goban/state.gob # empty disables persistence state_save_interval: 30s ``` ## 审计追踪 每次通过 `goban-client` 成功的手动封禁或解封,都会向审计文件追加一行 JSON: ``` audit_log: /var/log/goban/audit.log # empty disables auditing ``` 示例行: ``` {"time":"2026-05-11T13:42:01Z","action":"ban","ip":"192.0.2.99","rule":"manual","ttl":"5m0s","source":"manual"} ``` 失败的尝试不会被故意记录,因此该文件始终是一个准确的“实际应用了什么”的时间线。请使用 logrotate 来管理其大小。 ## 累犯 —— 重复违规者 内置规则(`examples/rules.d/recidive.yaml`,由安装包发送到 `/etc/goban/rules.d/`)会监控 GoBan 自己的审计日志,并对在过去 24 小时内被封禁 5 次以上的任何 IP 重新封禁一周。 要启用它,请定义一个指向审计日志的文件源: ``` sources: - type: file name: audit path: /var/log/goban/audit.log ``` (包中附带的示例 `goban.yaml` 已经这样做了。)该规则具有反馈循环安全性 —— 既通过 中显式的 `excludes` 过滤器,又通过针对任何精确命名为 `recidive` 的规则的硬编码自动排除来保障。 在 `/etc/goban/rules.d/recidive.yaml` 中调整阈值: ``` - name: recidive max_retries: 5 # # of past bans within findtime findtime: 24h # window over which past bans count bantime: 168h # 1 week — typically much longer than per-service bans ``` ## 部署前测试规则 要在不实际封禁任何人的情况下针对真实日志验证新的 regex,请将该规则加载到正在运行的守护进程中(任何守护进程,例如预发布主机上的),然后通过客户端进行 dry-run (干跑/试运行): ``` goban-client test --rule sshd /var/log/auth.log ``` 客户端从守护进程获取规则的 regex + 阈值 + findtime + bantime,将它们应用于日志文件(如果指定 `-` 则从标准输入读取),并打印哪些 IP 以及何时会被封禁: ``` === test results for rule "sshd" against /var/log/auth.log === Lines read: 27412 Lines matched: 312 Unique IPs seen: 18 Simulated bans: 11 Rule settings: max_retries=5, findtime=10m, bantime=1h IP FIRST_SEEN BANNED_AT EXPIRES 198.51.100.7 12:03:11 12:05:42 2026-05-11T13:05:42Z 198.51.100.42 12:08:20 12:09:55 2026-05-11T13:09:55Z ... ``` 这对于在将规则推广到生产环境之前,根据实际攻击模式调整阈值很有用。时间戳使用读取每行时的挂钟时间,而不是日志内部的时间戳 —— 因此旧的 auth.log 将产生“将在 <现在> 封禁”而不是原始事件时间。这适用于判断“此规则是否会触发得太频繁”;它不是一个重放工具。 ## 编写规则 规则是 YAML 格式。regex 必须包含一个 `(?P...)` 命名捕获组;该值会通过 `netip.ParseAddr` 解析,并且必须解析为有效的 IP。规则可以选择性地携带其自身的 `allowlist:` CIDR,这些 CIDR 将绕过该特定规则(全局白名单仍然适用)。 ``` rules: - name: my-app-failures source: my-app-container # name of a defined source regex: 'login failed.*from (?P[^\s]+)' max_retries: 5 findtime: 10m bantime: 6h allowlist: # optional per-rule allowlist - 203.0.113.50/32 # VPN endpoint — bypass THIS rule only - 10.0.0.0/16 ``` GoBan 使用 Go 的 RE2 regex 引擎。**它不支持反向引用、前瞻/后顾或像 fail2ban 的 `` 这样的命名宏。** 要移植 fail2ban 过滤器,请将 `` 替换为 `(?P\S+)`(如果字段被引号或括号括起来,则使用更严格的字符类)。 **日期感知规则。** 匹配回填或重放的日志(例如 journald 在启动时追赶到最新)时,请使用规则的 `datepattern` 字段并在 regex 中添加 `(?P
标签:CISA项目, Docker容器, EVTX分析, fail2ban替代, Go语言, ipset, iptables, IPv6, IP封禁, PE 加载器, PowerShell, 入侵防御, 单文件二进制, 原生Netlink, 子域名枚举, 封禁守护进程, 恶意流量拦截, 攻击面发现, 日志审计, 程序破解, 系统安全, 网络安全, 请求拦截, 隐私保护, 静态链接