乐于分享
好东西不私藏

"Knock 敲门" 软件工作原理分析

"Knock 敲门" 软件工作原理分析
"Knock 敲门"是一款飞牛下的安全软件,也支持在普通Linux下进行部署。
从它的官网上来看,这是一个安全网关产品,但是看不出来它的原理是什么。它只解释说是通过零信任授权的方式,只让授权用户访问公开的端口。
我想知道它是如何阻断IP的。
先看一下它的功能描述:

https://docs.fnknock.cn/origin/why-knock

为什么做敲门 knock

买 NAS 的人,初衷往往是把照片、文档和私有服务掌握在自己手里。可一旦将设备裸露在公网,它的性质就变了。对你而言那是家庭数据中心;但在自动化扫描器眼中,那只是一个处于活跃状态的 IP、一组开放的端口,以及一扇没关严的门。

敲门 knock 的初心从来不是“再造一个反向代理面板”或堆砌花哨的配置项。我们只为解决一个基础但长期被忽视的问题:在飞牛 OS 面向公网之前,先把防盗门装上,再谈访问体验。

直连模式下,我们仍然支持将公网访问收敛至独立的 7999 端口,在系统底层阻断外部对其他业务端口的直连尝试。用户完成身份验证后,系统才会通过动态白名单机制,放行当前受信任客户端的 IP。但它更适合少量必须保留原始端口访问的场景,不推荐作为多数新部署的首选。

我们不再去猜测某个 HTTP 请求“像不像攻击”,而是默认拒绝所有未经授权的连接。这种 Deny-by-default(默认拒绝)机制,从网络架构层面直接熔断了诸如 /app-center-static 越权访问这类漏洞的触发链。

这样的零信任机制的设计思维,使我们默认准备暴露出去的服务本身是存在漏洞的,基于这样的前提而设计的knock,我们能肯定及确信的说,即便你继续安装旧版本存在漏洞的飞牛OS,也仍然是安全的。

它的代码地址:

https://github.com/kci-lnk/fn-knock-turborepo

使用 claude code 分析 IP 阻断的工作原理。
问:先了解一下当前项目,我想知道它是如何阻断攻击IP的,有没有用到iptables?
答:我来帮你了解这个项目是如何阻断攻击IP的。让我先探索一下项目结构和相关代码。

据对代码的详细分析,这个项目确实用到了 iptables,但不是直接在 Node.js 中调用 iptables 命令,而是通过一个 Go 后端服务 作为中间层来操作 iptables。下面是完整的架构梳理:

iptables 防火墙层(通过 Go 后端)

项目在 iptables 中创建了两个自定义链:

Go 后端提供的 iptables API(见 go-backend.ts):

  • initIptables() → 初始化自定义链和规则

  • allowIP(ip) → 放行 IP(白名单)

  • blockIP(ip) → 阻断 IP

  • removeIP(ip) → 移除 IP 规则

  • blockTCPPortForIP(ip, port) → 阻断特定端口的 IP 访问

  • syncSSHFirewall() → 批量同步 SSH 防火墙策略

  • cleanIptables() → 清除所有 fn-knock 相关规则

三大攻击检测机制

SSH 安全服务 (ssh-security/service.ts)

检测 SSH 暴力破解和非法访问,直接写入 iptables 阻断

  • 实时监控 SSH 登录日志(支持 journalctl 和 auth.log)

  • 封禁原因:

     failed_login_threshold — 失败登录次数超限(如5分钟内失败N次)cidr_not_allowed — 即使登录成功,但 IP 不在允许的 CIDR 地域范围内

  • 封禁后调用 goBackend.syncSSHFirewall() 将 blocked IPs 写入 FN-KNOCK-SSH 链

  • 支持自动过期(定时器到期后自动解封)

HTTP 扫描检测 (scan-detector.ts)

检测恶意扫描行为,应用层阻断

分析请求路径,判断是否为"非常见路径"

  • 利用 Redis 有序集合记录每个 IP 的可疑访问记录

  • 在配置的时间窗口内(默认5分钟),如果非正常路径访问次数超过阈值(默认5次),则加入黑名单

  • 黑名单存在 Redis 中,通过 isBlacklisted() 方法在请求进入时判断是否阻断

  • 这是应用层阻断,不走 iptables,而是在 Node.js 请求处理层面拒绝

登录退避保护 (login-backoff.ts)

防止登录接口被暴力破解

  • 基于 Redis 记录每个 IP 的登录失败次数

  • 使用指数退避算法(2^attempts × baseDelay + 随机抖动)

  • 基础延迟 2 秒,最大延迟 1 小时,最大尝试次数 8 次

  • 超过 8 次失败后触发"硬阻断"(shouldHardBlock)

  • 这也是应用层阻断,在认证逻辑中检查 ensureNotBlocked()

白名单机制 (whitelist-manager.ts)

白名单 IP 通过 iptables 放行:

  • 支持三种目标类型:IP、CIDR(如 192.168.1.0/24)、CNAME(域名,自动解析为 IP)

  • 添加白名单时调用 goBackend.allowIP(target) 写入 iptables FN-KNOCK-FW 链

  • 移除时调用 goBackend.removeIP(target) 从 iptables 删除

  • 支持手动添加和登录成功后自动添加(source: "auto")

  • 自动过期机制(Redis 有序集合跟踪过期时间)

原理:

项目用到了 iptables。 SSH 安全和主防火墙的 IP 阻断最终是通过 iptables 的自定义链实现的(间接通过 Go 后端调用),而 HTTP 扫描检测和登录退避则是在 Node.js 应用层通过 Redis 状态管理实现的。

目的 IP 阻断方式是:

  • iptables 层:通过 Go 后端直接操作 iptables 自定义链(FN-KNOCK-FW 和 FN-KNOCK-SSH),每个 IP 直接作为独立的 iptables 规则添加

  • 应用层:通过 Redis 维护黑名单状态

这意味着当前每个被封禁的 IP 都是一条独立的 iptables 规则。如果被封禁的 IP 数量较大(比如几百上千),这种逐条规则的方式会比 ipset 效率低——ipset 的优势在于将大量 IP 放入一个集合,然后 iptables 只需一条规则匹配该集合即可,内核层面的查找复杂度是 O(1) 哈希查找,而逐条规则则是线性匹配。

Go 后端的核心实现在另外一个项目中:

https://github.com/kci-lnk/Go-Reauth-Proxy

Go 后端是通过直接调用 iptables / ip6tables 命令行来操作防火墙的,完全没用 ipset。具体方式:

逐条规则操作(FN-KNOCK-FW 链)

// 允许 IP — 每个IP一条规则m.runTable(table, "-I", m.Chain, insertPos, "-s", ip, "-j", "ACCEPT")// 阻断 IP — 每个IP一条规则m.runTable(table, "-I", m.Chain, insertPos, "-s", ip, "-j", "DROP")// 移除规则 — 删除ACCEPT和DROP两条m.runTable(table, "-D", m.Chain, "-s", ip, "-j", "ACCEPT")m.runTable(table, "-D", m.Chain, "-s", ip, "-j", "DROP")

批量重建(FN-KNOCK-SSH 链)

SSH 安全链用了 iptables-restore 做批量写入,这比逐条高效一些,但仍然不是 ipset:

func(m *Manager) applyTCPPortAccessPolicy(table string, policy TCPPortAccessPolicy) error {    var builder strings.Builder    builder.WriteString("*filter\n")    builder.WriteString("-F " + chain + "\n")          // 先清空    // 逐条写入 localCIDRs → blockRules → allowRules → defaultAction    for _, cidr := range localCIDRs { ... }    for _, source := range blockRules { ... }    for _, source := range allowRules { ... }    builder.WriteString("COMMIT\n")    m.runTableRestore(table, builder.String())          // 一次性 commit

当前架构的潜在问题

FN-KNOCK-SSH 链因为用了 iptables-restore 全量重建,暂时还能应对。但 FN-KNOCK-FW 链的白名单管理是逐条 -I / -D,当白名单 IP 数量较多时,每条规则都是独立的 iptables entry,匹配效率会下降。如果后续要优化,引入 ipset 是一个方向。

大部分人对 iptables 都比较抗拒,特别是混合了Docker的情况。这个项目的前后端都比较复杂,如果要部署到内网,理解起来可能会比较费劲。

我两年前也写了一个基于 iptables 的放行应用,非常简单,可以对照着看一下。

https://github.com/hyang0/ip_allow

"Knock 敲门"提供了一个非常漂亮的前端页面,感兴趣的可以去搜一下它的官网。

全文完。