fix(conf): 样例配置去掉行尾注释(会被并入值导致静默失效)

conf.Init 用 ini.LoadOptions{IgnoreInlineComment: true},该选项的语义是
**不剥离**行尾注释:`port = 1440 ; 注释` 解析出来的值是 "1440 ; 注释",
int 解析失败,而 MapTo 仍然返回 nil error —— 配置项静默回退零值。

实测照抄 app.sample.ini 的后果:
- text_censor_source 的值带注释,被 validateTextCensorSource 拒绝,直接起不来;
- ip_blacklist / security_alert_email 变成带注释的垃圾字符串;
- honeypot_max_rows / honeypot_auto_clean 静默失效(后者靠 sanitize 兜底才没出事)。

改动:
- 17 处 `key = value ; 注释` 改成「注释独占一行 + 裸值」,保留 CRLF;
- 蜜罐段补 honeypot_cred_dir,以及「提交凭据才封禁」的语义说明;
- 新增 TestSampleIniValuesHaveNoInlineComment:遍历所有段的键,断言值不含 ';'
  (比逐字段断言彻底,新增配置项自动纳入)。
  变异验证:给 ban_window_seconds 加回行尾注释,测试按预期失败;
- keynorm_test 补 HoneypotBanMinutes 的缺省值断言(缺省 1440 / 显式 0 交给 autoban 回退)。
This commit is contained in:
tamakyi
2026-10-21 15:43:13 +08:00
parent bfdc5089c5
commit 566b13eedd
2 changed files with 147 additions and 44 deletions
+58
View File
@@ -236,6 +236,7 @@ func TestSanitizeHoneypotDefaults(t *testing.T) {
wantMaxRows int
wantAutoClean bool
wantThreshold int
wantBan int
}{
{
name: "全未配置时回退安全默认值",
@@ -244,6 +245,7 @@ func TestSanitizeHoneypotDefaults(t *testing.T) {
wantMaxRows: DefaultHoneypotMaxRows,
wantAutoClean: true,
wantThreshold: DefaultHoneypotThreshold,
wantBan: DefaultHoneypotBanMinutes,
},
{
name: "显式关闭自动清理被尊重",
@@ -252,6 +254,7 @@ func TestSanitizeHoneypotDefaults(t *testing.T) {
wantMaxRows: DefaultHoneypotMaxRows,
wantAutoClean: false,
wantThreshold: DefaultHoneypotThreshold,
wantBan: DefaultHoneypotBanMinutes,
},
{
name: "显式保留期被采用",
@@ -260,6 +263,7 @@ func TestSanitizeHoneypotDefaults(t *testing.T) {
wantMaxRows: DefaultHoneypotMaxRows,
wantAutoClean: true,
wantThreshold: DefaultHoneypotThreshold,
wantBan: DefaultHoneypotBanMinutes,
},
{
name: "保留期写成 0 回退默认而非永不清理",
@@ -268,6 +272,7 @@ func TestSanitizeHoneypotDefaults(t *testing.T) {
wantMaxRows: DefaultHoneypotMaxRows,
wantAutoClean: true,
wantThreshold: DefaultHoneypotThreshold,
wantBan: DefaultHoneypotBanMinutes,
},
{
name: "行数上限显式 0 表示不限",
@@ -276,6 +281,27 @@ func TestSanitizeHoneypotDefaults(t *testing.T) {
wantMaxRows: 0,
wantAutoClean: true,
wantThreshold: DefaultHoneypotThreshold,
wantBan: DefaultHoneypotBanMinutes,
},
{
name: "显式封禁时长被采用",
src: "[security]\nhoneypot_ban_minutes = 90\n",
wantRetain: DefaultHoneypotRetainDays,
wantMaxRows: DefaultHoneypotMaxRows,
wantAutoClean: true,
wantThreshold: DefaultHoneypotThreshold,
wantBan: 90,
},
{
// 0 是逃生口:留着让 autoban 回退 ban_duration_minutes,
// sanitize 不做覆盖(只告警),故期望值仍是 0。
name: "封禁时长写 0 交给 autoban 回退 ban_duration_minutes",
src: "[security]\nhoneypot_ban_minutes = 0\n",
wantRetain: DefaultHoneypotRetainDays,
wantMaxRows: DefaultHoneypotMaxRows,
wantAutoClean: true,
wantThreshold: DefaultHoneypotThreshold,
wantBan: 0,
},
}
@@ -290,6 +316,7 @@ func TestSanitizeHoneypotDefaults(t *testing.T) {
Security.HoneypotMaxRows = 0
Security.HoneypotAutoClean = false
Security.HoneypotThreshold = 0
Security.HoneypotBanMinutes = 0
File = f
if err := f.Section("security").MapTo(&Security); err != nil {
t.Fatalf("map security: %v", err)
@@ -308,6 +335,9 @@ func TestSanitizeHoneypotDefaults(t *testing.T) {
if Security.HoneypotThreshold != c.wantThreshold {
t.Errorf("HoneypotThreshold = %d, want %d", Security.HoneypotThreshold, c.wantThreshold)
}
if Security.HoneypotBanMinutes != c.wantBan {
t.Errorf("HoneypotBanMinutes = %d, want %d", Security.HoneypotBanMinutes, c.wantBan)
}
})
}
}
@@ -358,6 +388,34 @@ func TestSampleIniHasNoUnknownKeys(t *testing.T) {
}
}
// TestSampleIniValuesHaveNoInlineComment 守住「行尾注释会被并入值」的陷阱。
//
// conf.Init 用 ini.LoadOptions{IgnoreInlineComment: true},该选项的语义是
// **不剥离**行尾注释(实测 `port = 1440 ; 注释` → 值 "1440 ; 注释",
// int 解析失败,而 MapTo 仍然返回 nil error)。后果是该配置项静默回退零值,
// 日志里只会说「配置值不对」,根本联想不到是注释造成的。
//
// 样例配置曾经全线是 `key = value ; 注释` 的写法:text_censor_source 因此
// 让 conf.Init 直接拒绝启动,ip_blacklist / security_alert_email 变成带注释的
// 垃圾字符串,honeypot_max_rows / honeypot_auto_clean 静默失效。
//
// 直接检查每个键的值里没有 ';',比逐个字段断言更彻底,也不会漏掉新加的配置项。
func TestSampleIniValuesHaveNoInlineComment(t *testing.T) {
path := filepath.Join("..", "..", "conf", "app.sample.ini")
f, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, path)
if err != nil {
t.Skipf("跳过:无法读取样例配置 %s(%v)", path, err)
}
for _, sec := range f.Sections() {
for _, k := range sec.Keys() {
if strings.Contains(k.Value(), ";") {
t.Errorf("样例配置 [%s] %s 的值含 ';':%q —— 行尾注释会被并入值,该项将静默失效",
sec.Name(), k.Name(), k.Value())
}
}
}
}
// TestNormalizeAllSectionKeysEndToEnd 走完整链路:
// 加载带错拼键的配置 → 归一化 → MapTo → 拿到正确的保留期。
//