fix(conf): 配置键名容错 + 未知键告警 + 蜜罐保留期缺省值修正

现场故障:配置写 `honeypot_retention_days <= 30`,日志却报
「蜜罐自动清理未启用:honeypot_retention_days <= 0」。

根因(实测 ini v1.67.0 行为确认):
1. 键名多一个 t(retention vs retain)→ ini 对未知键**静默忽略**,保留期成 0;
2. `<= 30` 这种写法让 ini 把键名解析成 "honeypot_retention_days <"
   (带尾随比较符),更加匹配不上;
3. 警告文案里写的也是错键名,等于在误导运维往错的键上配。

修复(三层):
- 键名归一化(internal/conf/keynorm.go):修剪首尾空白、剥掉尾随 `<` `>`
  `=` 等比较符残留,并按别名表纠正常见误拼。归一化结果必须命中该段
  合法键名白名单才会被采用 —— 不认识的键不猜,交给未知键告警。
- 未知键告警(internal/conf/unknown.go):启动时逐段比对键名,无法识别的
  键 WARN 出来并给出最接近的合法键名建议。合法键名由反射从结构体 ini tag
  得出(单一事实来源,新增配置项自动纳入,不会脱节)。
- 缺省值语义修正(internal/conf/honeypot_defaults.go):
  * 保留期缺省 30 天(原先零值 0 = 永不清理);显式 <= 0 也回退 30。
    要关闭自动清理请用 honeypot_auto_clean = false。
  * honeypot_auto_clean 缺省 true。原先靠 bool 零值,ini 的 bool 解析是
    「有键才赋值」,未配置与显式关闭无法区分 → 老配置升级上来自动清理
    实际是关着的。现改用 HasKey 判定。
  * honeypot_max_rows 缺省 200000;显式 0 = 不限(会告警提醒只增不减)。
  * honeypot_threshold 缺省 1。

实测 ini 行为(探针程序,已删):
- 键缺失或值非法时 MapTo **不覆盖**预置默认值 → 可安全预置缺省值;
- 键名大小写敏感(Honeypot_Auto_Clean 不生效);
- HasKey 能区分「缺失」与「显式 0」;
- 段不存在时 MapTo 返回 nil 且不修改目标。

顺带清理 app.sample.ini 里的两条死配置(app.title / server.xsrf_expire):
代码中从未读取(站点标题走 i18n),留着会让新检测对官方模板误报。

验证:
- 用现场原始配置端到端复现 → 归一化后保留期 30 天、自动清理开启、上限 20 万,
  日志明确说明「配置键名已自动纠正」;
- 误拼键(honeypot_autoclean / honeypot_max_row)自动纠正;未知键(totally_bogus)
  WARN 告警;
- 显式 honeypot_auto_clean = false 被尊重为关闭;完全未配置时缺省开启;
- 新增 TestSampleIniHasNoUnknownKeys 回归检测:官方样例配置零误报;
- go build ./... + go test -timeout 600s ./... 全绿,go vet 无输出。
This commit is contained in:
tamakyi
2026-10-21 13:41:19 +08:00
parent cbb0c51a14
commit d300167a30
8 changed files with 896 additions and 10 deletions
+11 -4
View File
@@ -85,20 +85,27 @@ var (
EnableHoneypot bool `ini:"enable_honeypot"`
// HoneypotPaths 追加自定义诱饵路径(逗号分隔),与内置清单并集。
HoneypotPaths string `ini:"honeypot_paths"`
// HoneypotRetainDays 蜜罐命中记录的保留天数,到期由 CLI 清理。
// HoneypotRetainDays 蜜罐命中记录的保留天数,到期自动清理。
// ⚠️ 键名是 honeypot_retain_days(没有 tion)。写成 honeypot_retention_days
// 会被 ini 静默忽略 —— 现场踩过这个坑,表现为「自动清理未启用」。
// 缺省 30 天(见 sanitizeHoneypot);显式 <= 0 也回退 30。
// 要关闭自动清理请用 honeypot_auto_clean = false。
HoneypotRetainDays int `ini:"honeypot_retain_days"`
// HoneypotAutoClean 是否启用蜜罐记录的后台自动保留期清理。
// 默认 true:honeypot_hits 是只追加的审计表,写入完全由外部扫描流量驱动,
// 缺省 true:honeypot_hits 是只追加的审计表,写入完全由外部扫描流量驱动,
// 没有自动清理的话表体积只增不减(这是「数据库被扫描撑爆」的真实路径)。
// 关闭后仍可手动执行 TamaBox honeypot prune。
// ⚠️ 缺省值靠 HasKey 判定(见 sanitizeHoneypot):ini 的 bool 解析是
// 「有键才赋值」,若只看零值就无法区分「未配置」与「显式关闭」。
HoneypotAutoClean bool `ini:"honeypot_auto_clean"`
// HoneypotMaxRows 蜜罐命中记录的行数上限。留空或 <=0 表示不设上限。
// HoneypotMaxRows 蜜罐命中记录的行数上限。缺省 200000(约 53 MB)。
// 显式写 0 表示不设上限(逃生口,会告警提醒表只增不减)。
// 保留期是时间维度的上界,但攻击者可以用海量源 IP 在保留期内把表撑爆,
// 这是第二道闸。实测单行约 281 字节(含索引,常规 UA),20 万行约 53 MB。
HoneypotMaxRows int `ini:"honeypot_max_rows"`
// HoneypotThreshold 窗口内蜜罐命中多少次即封禁。默认 1(一次即封):
// 蜜罐路径与真实业务路由零交集,正常访客不可能命中,一次即足以判定扫描器。
// 留空回退 1。
// 缺省回退 1(见 sanitizeHoneypot)。
HoneypotThreshold int `ini:"honeypot_threshold"`
// HoneypotBanMinutes 蜜罐封禁时长(分钟)。留空则复用 ban_duration_minutes。
HoneypotBanMinutes int `ini:"honeypot_ban_minutes"`