Skip to content

Latest commit

 

History

History
145 lines (109 loc) · 6.89 KB

README_zh-CN.md

File metadata and controls

145 lines (109 loc) · 6.89 KB

knockkit

用 Go 编写的现代认证式端口敲击工具集:一个守护进程(knockd)负责监听敲击端口并执行动作,一个客户端(knock)负责执行序列。

cmd/knockd    服务端:敲击端口 → AEAD 认证 → 规则动作 → 延迟清扫
cmd/knock     客户端:发送序列,打印密封响应

与经典 knockd 不同,端口序列本身不携带任何秘密——它只是路由用的装饰:每个数据包仍必须通过基于逐包 HKDF 派生密钥的 XChaCha20-Poly1305 认证。攻击者即便知道端口顺序也一无所获。

English: README.md

为什么不用原版 knockd?

经典 knockd knockkit
传输 TCP 或 UDP 两者皆可,且可在同一序列内自由混用
秘密 序列本身 每客户端 32 字节 PSK,逐包 AEAD
重放 未覆盖 nonce 缓存,失败关闭(fail-closed)
响应 无 密封状态 + trace id(对外部观察者无 oracle)
动作 防火墙开/关 allow、allow-close、exec、webhook、db-update、log
延迟关门 依赖外部 cron 内置调度器:现在敲门,N 分钟后自动关闭

快速开始

make build

# 1. 密钥材料
./bin/knockd -gen-psk > psk.key          # 32 字节随机数,base64

# 2. 服务端配置(参见 examples/knockd.yaml),然后:
./bin/knockd -check -config knockd.yaml  # 仅校验
./bin/knockd -config knockd.yaml

# 3. 客户端配置(参见 examples/knock.yaml),然后:
./bin/knock -config knock.yaml -rule ssh -v
ok rule=ssh trace=2b6c6061

knock 的退出码:0 接受,1 拒绝/无应答,2 用法错误。

敲击序列

一条规则就是一个有序步骤列表;每个步骤由端口和传输协议组成。步骤之间可以混用传输协议:

rules:
  - name: ssh
    action: allow
    params: {ports: "22", proto: tcp}
    steps:
      - {port: 7001, proto: udp}
      - {port: 7002, proto: udp}
      - {port: 7003, proto: tcp}   # 末步:密封应答在此返回

若某条序列是另一条的前缀(同一数据包会产生两种含义),规则会在启动时被拒绝。敲击的每一步都是一完整、重新封装的数据包——离路(off-path)攻击者无法通过注入单个伪造报文来污染服务端状态,因为其载荷无法通过认证。

动作(Actions)

动作是服务端白名单;客户端只能选择某条规则,永远无法指定命令、URL 或 SQL 语句。

动作 用途 幂等
allow 在防火墙中开放端口(带 TTL) 是
allow-close 再次关闭它们——最自然的清扫动作 是
log 仅写审计日志 是
exec 以 argv 数组(无 shell)运行白名单内的可执行文件 仅当 allow_as_cleanup: true
webhook POST 到预先声明的 URL,不跟随重定向 否
db-update 执行预注册的 SQL 语句并绑定占位符 否

参数处理规则:

  • 配置参数是权威的——可使用 $CLIENT_ID、$SOURCE_IP、$RULE、$TRACE,由服务端展开;
  • 客户端只有在键被列于该规则的 allow_params 中、且动作 schema 接受该键时,才能追加参数(启动时即校验);
  • 每个参数——无论来自客户端还是配置——都会对照动作 schema 校验;未知键一律拒绝。

清扫(延迟执行动作 B)

cleanup:
  after: 3m
  action: allow-close
  params: {ports: "22", proto: tcp}
  policy: refresh   # refresh(默认) | ignore
  retry: {max_attempts: 5, base_delay: 5s, max_delay: 1m}
  • refresh(默认):重新敲击会重置计时器——只要客户端持续敲门端口就保持开放,并在最后一次敲门 3 分钟后关闭。
  • ignore:首个计时器优先生效;重复敲击不会推迟它。
  • 任务在计时器启动之前就已持久化(scheduler.store: file),因此守护进程崩溃不会丢失任何任务;逾期任务会在下次启动时执行。
  • 只有幂等动作可用作清扫动作——启动时强制校验。
  • allow 规则上的 ttl: 10m 正是上述配置块的语法糖。

安全模型

  • AEAD 是安全边界。 对整个数据包使用 XChaCha20-Poly1305;头部(端口、时间戳、客户端 id、标志位)作为附加认证数据(AAD)。
  • 逐包密钥: HKDF-SHA256(psk, salt=knockkit-v1‖nonce, info=aead/knock)——nonce 泄露绝不会暴露 PSK。
  • 统一拒绝: 任何认证失败都产生完全相同的 auth: rejected;机器可读的失败原因仅存在于本地指标中。线上不存在任何 oracle。
  • 重放防护: nonce 只有在解密成功后才进入缓存,因此没有 PSK 的攻击者无法填满它;缓存有容量上限且失败关闭。
  • 先准入控制: 封禁表 → 每 IP 令牌桶 → 全局 PPS → 解析 → AEAD。只有通过限流的流量才会进入开销较高的处理环节。
  • 关键路径失败关闭: nonce 缓存已满或敲击执行器饱和时直接丢包,而不是放行未验证的工作。

运维

  • knockd -check —— 校验配置并退出。
  • 指标:metrics.listen 暴露 /metrics(Prometheus 文本格式)与 /healthz。关键序列:knockd_auth_fail_by_reason_total、knockd_knock_ok_total、knockd_cleanup_err_total、knockd_rate_drop_total。
  • 每次敲击都会记录一个 trace id,该 id 会回显给客户端并被清扫任务复用,因此一个 id 即可关联动作 A、动作 B 与客户端重试。
  • 日志绝不包含密钥材料或密文。

防火墙后端

  • nftables(Linux 默认):表 knockkit,带 timeout 的 concat set,优先级 -10 的链——即使守护进程挂掉,TTL 仍由内核强制执行。
  • iptables:注入到 INPUT 1 并带 knockkit 注释;依赖应用层清扫来关门。
  • noop:测试、非 Linux 环境,或防火墙由别处管理。

flush_on_exit: true(默认)会在退出时撤回守护进程添加的全部规则。

目录结构

cmd/knockd, cmd/knock      二进制文件
pkg/protocol               线格式、params TLV、响应、成帧(已 fuzz)
pkg/auth                   HKDF、AEAD、时效性、重放缓存、密钥环(已 fuzz)
pkg/seq                    每 IP 敲击状态机 + 歧义性校验
pkg/action                 动作注册表、schema、内置动作
pkg/firewall               nftables / iptables / noop 后端
pkg/scheduler              延迟任务:epoch、持久化、重试
pkg/ratelimit              令牌桶、全局 PPS、封禁表
internal/config            严格 YAML(KnownFields)、默认值、校验
internal/server            管线:准入 → 认证 → 序列 → 执行 → 应答

开发

make test    # 单元测试 + 端到端
make race
make vet
make fuzz    # 每个解析器 30 秒

Go ≥ 1.27(见 go.mod),无 CGO。服务端面向 Linux;客户端跨平台。