Files
tamabox/internal/security/autoban/honeypot_test.go
T
tamakyi 7d52155411 fix(honeypot): 蜜罐封禁与 enable_auto_ban 解耦 + 泄露扫描后缀匹配
- autoban: 新增 RecordHoneypot 独立通道(不受 enabled 约束)+ 独立阈值/时长;
  IsBanned 不再因 enable_auto_ban=false 而忽略已有封禁
- honeypot: 新增 honeypotSuffixes 后缀匹配,覆盖 /backup.zip、/db.sql、/id_rsa.pem
- 新增配置 honeypot_threshold / honeypot_ban_minutes
- 补测试:autoban/honeypot_test.go、route 侧禁用自动封禁仍封禁
2026-10-21 12:36:33 +08:00

124 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright 2026 tamakyi. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package autoban
import (
"testing"
"time"
)
// TestHoneypotBanIndependentOfAutoBan 覆盖 P0:蜜罐封禁不得被 enable_auto_ban 连坐。
//
// 背景(线上实测发现):蜜罐命中 70+ 次,站点一次都没封禁。
// 根因是蜜罐复用了 Record(),而 Record() 第一行就 `if !b.enabled { return }` ——
// 运维一旦关掉 enable_auto_ban(它对共享出口误伤多,常被关掉),
// 蜜罐就退化成「只记录不封禁」的旁观者,全部价值(威慑 + 阻断)消失。
//
// 修复:蜜罐走独立的 RecordHoneypot 通道,只看 HoneypotEnabled。
func TestHoneypotBanIndependentOfAutoBan(t *testing.T) {
b := New(Options{
Enabled: false, // 自动封禁关闭
HoneypotEnabled: true, // 但蜜罐开启
Window: 60 * time.Second,
Duration: 15 * time.Minute,
})
// 常规敏感路径不再计数(enable_auto_ban 关闭)
b.Record("1.2.3.4", "/wp-admin", KindSensitive)
if b.IsBanned("1.2.3.4") {
t.Error("enable_auto_ban 关闭时,常规敏感路径不应产生封禁")
}
// 蜜罐命中必须立即封禁
if !b.RecordHoneypot("5.6.7.8", "/wp-login.php") {
t.Error("RecordHoneypot 应在首次命中时封禁")
}
if !b.IsBanned("5.6.7.8") {
t.Error("蜜罐命中后 IsBanned 应为 true(蜜罐封禁不得被 enable_auto_ban 连坐)")
}
// 其它 IP 不受影响
if b.IsBanned("9.9.9.9") {
t.Error("无关 IP 不应被封禁")
}
}
// TestHoneypotDisabledNoBan 覆盖反向:蜜罐关闭时不应封禁。
func TestHoneypotDisabledNoBan(t *testing.T) {
b := New(Options{
Enabled: true,
HoneypotEnabled: false,
Window: 60 * time.Second,
Duration: 15 * time.Minute,
})
if b.RecordHoneypot("1.2.3.4", "/wp-login.php") {
t.Error("蜜罐关闭时不应封禁")
}
if b.IsBanned("1.2.3.4") {
t.Error("蜜罐关闭时不应产生封禁")
}
}
// TestHoneypotThreshold 覆盖蜜罐独立阈值:默认 1 次即封,可配为 N 次。
func TestHoneypotThreshold(t *testing.T) {
b := New(Options{
Enabled: false,
HoneypotEnabled: true,
HoneypotThreshold: 3,
Window: 60 * time.Second,
Duration: 15 * time.Minute,
})
if b.RecordHoneypot("1.2.3.4", "/wp-login.php") {
t.Error("第 1 次不应封禁(阈值 3)")
}
if b.RecordHoneypot("1.2.3.4", "/wp-login.php") {
t.Error("第 2 次不应封禁(阈值 3)")
}
if !b.RecordHoneypot("1.2.3.4", "/wp-login.php") {
t.Error("第 3 次应封禁(阈值 3)")
}
if !b.IsBanned("1.2.3.4") {
t.Error("达到阈值后 IsBanned 应为 true")
}
}
// TestHoneypotDurationIndependent 覆盖蜜罐独立封禁时长。
func TestHoneypotDurationIndependent(t *testing.T) {
b := New(Options{
Enabled: false,
HoneypotEnabled: true,
Duration: 15 * time.Minute, // 常规时长
HoneypotDuration: 5 * time.Minute, // 蜜罐时长
Window: 60 * time.Second,
})
b.RecordHoneypot("1.2.3.4", "/wp-login.php")
b.mu.Lock()
until := b.banned["1.2.3.4"]
b.mu.Unlock()
if until.IsZero() {
t.Fatal("未写入封禁记录")
}
remaining := time.Until(until)
if remaining > 6*time.Minute {
t.Errorf("蜜罐封禁时长应约 5 分钟,实际剩余 %v", remaining)
}
}
// TestIsBannedRespectsExistingBan 覆盖:enabled 关闭时,已有的封禁依然被尊重。
// 这是蜜罐封禁能真正挡住后续请求的前提。
func TestIsBannedRespectsExistingBan(t *testing.T) {
b := New(Options{
Enabled: false,
HoneypotEnabled: true,
Window: 60 * time.Second,
Duration: 15 * time.Minute,
})
b.RecordHoneypot("1.2.3.4", "/wp-login.php")
if !b.IsBanned("1.2.3.4") {
t.Error("enabled 关闭时仍应尊重已存在的蜜罐封禁")
}
}