diff --git a/.workbuddy/memory/2026-10-23.md b/.workbuddy/memory/2026-10-23.md index 753ce29..b69ec7a 100644 --- a/.workbuddy/memory/2026-10-23.md +++ b/.workbuddy/memory/2026-10-23.md @@ -41,3 +41,45 @@ 两处「TTL 30 分钟」注释改成 1 小时(代码一直是 `time.Hour`)。 新增 `TestShareCardCacheKeysCoverDrawnFields`——逐字段变异断言 key 必须变, 去掉 Domain 立刻 FAIL。**以后改分享卡要加新字段时,先看这张测试。** + +## 正式环境「蜜罐封了但还能访问」排查(IPv6) + +本地用 IPv6 全程复现了一遍 autoban 链路(CF-Connecting-IP 注入 + trust_proxy=true): +封禁前 200 → POST 凭据后 `IsBanned`=true → 同地址 403 → 同 /64 换地址仍 403 → +不同 /64 放行 200。**判定逻辑与中间件都正确,不是代码 bug。** + +封禁键 = `shared.LimitKey(ResolveClientIP(req))`: +- IPv4 原样 +- IPv6 折成 `xxxx:xxxx:xxxx:xxxx::/64` +写入(honeypot.Serve / route.NotFound)与检查(autobanMiddleware)用的是同一个函数,一致。 + +⇒ 「邮件说封了但能访问」只能是**键不一致**或**进程重启(封禁纯内存态)**。 +三种可能:① 部署后重启过;② trust_proxy 未配 → 键是反代地址(`127.0.0.1` / `::/64`); +③ 前后请求地址族或 /64 不同(双栈、IPv6 前缀轮换、多实例)。 + +判定方法:`honeypot hits` / `data/honeypot-creds/creds-YYYY-MM-DD.jsonl` 里的 `ip` 字段 +就是**被封的键**,与本机 `curl -6 ifconfig.co` 比对即可。 + +**发现的真实取证缺陷**:`db.HoneypotHit.IP` 与 creds 文件存的是折叠后的 `/64` 键, +IPv6 丢掉了精确地址(IPv4 无损);邮件里也只给键,运维会误以为「这不是我的 IP」。 +建议:取证记精确 IP,封禁仍用 /64;建封与 403 命中各打一条日志。 + +### 据此动手改的三处(精确 IP / 封禁键分离 + 可观测性) + +1. `honeypotHandler.Serve` 拆两个变量:`clientIP = ResolveClientIP`(精确,取证/邮件用)、 + `banKey = LimitKey(clientIP)`(/64,封禁/采样/告警去重用)。DB 与凭据文件都写 clientIP。 +2. `autoban.Options.OnBan(key, duration, reason)` 回调(新增 `ReasonProbe` / `ReasonHoneypot`), + 保持 autoban 纯标准库、不再依赖 logrus。route.newBanner 注入 → 建封打 WARN + (ban_key / reason / duration / expires_at)。中间件 403 分支打 Debug。 +3. 邮件同时给「来源 IP」(精确)与「封禁范围」(ban_key,仅 `ne .ip .ban_key` 时渲染); + 顺手把模板里过时的「已入库」改成「写入取证文件」(上一轮凭据外置后没同步)。 + +副作用修掉:`honeypot stats` 的来源排行改按 `LimitKey` 合并(否则 IPv6 轮换地址会把排行碎成 +几十条各 1 次)。SQL 取 `limit*4` 再合并,属近似。 + +测试:`TestHoneypotRecordsExactIPv6ButBansByPrefix`(精确 IP + /64 键 + store 断言)、 +`TestOnBanCallback`、`TestHoneypotAlertTemplateRenders`、`TestMergeSourcesByKey`。 +四处变异验证有效(改回存聚合键 / 封禁用精确地址 / 去掉 notifyBan / 不合并排行)。 + +**变异验证的坑**:把 `shared.LimitKey(r.IP)` 改成 `r.IP` 会因 `shared` 未使用而 +**编译失败**(不是断言失败);变异要保证仍能编译(改成 `LimitKey(r.IP) + r.IP` 才测到逻辑)。 diff --git a/.workbuddy/memory/topics/build.md b/.workbuddy/memory/topics/build.md index 787fffe..10c5c09 100644 --- a/.workbuddy/memory/topics/build.md +++ b/.workbuddy/memory/topics/build.md @@ -27,3 +27,8 @@ 排查口诀:响应体是这句中文 → 一定是部署侧缺文件,不是代码回退。 `getFont` 现已支持 candidates(`font_path` → `static/fonts/*` → 系统 CJK 字体)且不缓存失败, 补文件后无需重启。 +- ⚠️ **`GOSUMDB=off` 会让 toolchain 自动下载失败**:go.mod 要 1.27.3 而 PATH 上的 `go` 是旧版时, + 会报 `golang.org/toolchain@...: verifying module: checksum database disabled by GOSUMDB=off` + (`go version` 显示的还是 shell 包装器的版本,容易误判成「版本没变」)。 + 解法:直接用缓存里的 toolchain 并禁用自动切换—— + `PATH="$HOME/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.27.3.windows-amd64/bin:$PATH" GOTOOLCHAIN=local go test ./...`