Files
tamabox/internal/conf/static.go
T
tamakyi 63e851ddae feat(honeypot): 自动保留期清理 + 行数上限 + VACUUM 空间回收 + size 子命令
问题:honeypot_hits 是只追加审计表,写入完全由外部扫描流量驱动(不可预期、
不可节流),而原实现只在手动跑 CLI prune 时才清理,站点运行期表体积只增不减。

实测取证(本地 PostgreSQL 建同构表灌 10.5 万行真实数据):
- 典型行 244 字节(含索引),10.5 万行 24 MB
- 保留期清理对照(行数恒定 10 万,每轮删 25% 插 25%):
  有 VACUUM 22→24MB 收敛;无 VACUUM 22→52MB 持续膨胀
- VACUUM 不缩小文件,只让空间可复用;二次实测(5 万行、连续 4 轮删插)
  行数恒定 5 万、文件稳定 15MB 不再增长

改动:
- 新增 HoneypotRetention 后台清理器(每小时一轮):时间闸 honeypot_retain_days
  + 数量闸 honeypot_max_rows,清理后调 Compact 回收空间
- db: 新增 Compact()(PG 执行 VACUUM (ANALYZE),走 GORM Exec 非事务路径)、
  DeleteOldestBeyond()(跨方言通用)、ListSinceByIP()、Size()
- 去掉 honeypot_hits.provider 无用索引(10 万行占 648kB,全代码库零查询使用),
  并在启动时显式清理既有库上的旧索引(GORM 去 tag 不会自动删)
- 修 CLI hits --ip 过滤 bug:原先取回最近 N 条再在 Go 侧筛,导致指定 IP 的
  历史命中基本查不出来 → 改到 SQL 层过滤
- 新增 CLI honeypot size 子命令:报告行数、磁盘占用、时间跨度,并与两道闸对照
- 新增配置 honeypot_auto_clean / honeypot_max_rows
2026-10-21 13:16:14 +08:00

177 lines
8.6 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 2022 E99p1ant. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package conf
// ⚠️ 仅应由 "-ldflags" 注入。
var (
BuildTime string
BuildCommit = "dev"
// StaticVersion 静态资源缓存版本号:base.css 与主题 css 共用,改样式只改这一处。
StaticVersion = "20261020g"
)
// 文本审核通道取值([security] text_censor_source)。定义在 conf 侧,
// 让配置校验(conf.Init)与通道装配(internal/security/censor)共用同一份字面量。
const (
CensorSourceAuto = "auto" // 七牛优先、阿里云兜底(历史行为,留空同义)
CensorSourceQiniu = "qiniu"
CensorSourceAliyun = "aliyun"
)
var (
App struct {
Production bool `ini:"production"`
ExternalURL string `ini:"external_url"`
ICP string `ini:"icp"`
DefaultLang string `ini:"default_lang"` // 站点默认语言(zh-CN/zh-TW/en/ja),留空回退简体中文
UptraceDSN string `ini:"uptrace_dsn"`
QiniuAccessKey string `ini:"qiniu_access_key"`
QiniuAccessSecret string `ini:"qiniu_access_secret"`
AliyunAccessKey string `ini:"aliyun_access_key"`
AliyunAccessKeySecret string `ini:"aliyun_access_key_secret"`
SentryDSN string `ini:"sentry_dsn"`
MaintenanceMode bool `ini:"maintenance_mode"`
ShareBackground string `ini:"share_background"`
FontPath string `ini:"font_path"`
}
// Theme 主题系统([theme] 段)。
// default :站点默认主题 slug,留空/非法回退内置 default(= 现在的外观)。
// available :启用哪些主题(逗号分隔),同时是箱主设置页的白名单;
// 列了但缺少 css 文件的会被忽略并告警(见 internal/theme)。
Theme struct {
Default string `ini:"default"`
Available string `ini:"available"`
}
Security struct {
EnableTextCensor bool `ini:"enable_text_censor"`
IPBlacklist string `ini:"ip_blacklist"`
IPBlacklistRedirectURL string `ini:"ip_blacklist_redirect_url"`
// TrustProxy 为 true 时信任 CDN 权威客户端头 CF-Connecting-IP / EO-Client-IP
// (以及受信代理设置的 X-Real-IP / X-Forwarded-For);否则一律用 RemoteAddr,
// 防止伪造 header 绕过速率限制与 IP 黑名单。
TrustProxy bool `ini:"trust_proxy"`
// TrustedProxies 逗号分隔的 IP / CIDR,仅限自己的反向代理 / LB。仅当 TCP 对端
// 落在该清单内才解析 X-Forwarded-For / X-Real-IP;留空则这两个头永不被信任
// (CDN 权威头仍会采用,前提是源站已由 Cloudflare Tunnel / 回源网段锁定,
// 从而免于维护上游 CDN 的庞大 CIDR 清单)。
TrustedProxies string `ini:"trusted_proxies"`
EnableAutoBan bool `ini:"enable_auto_ban"` // fail2ban 风格:检测到扫描特征后临时封禁来源 IP
// BanTrackNotFound 是否把未注册路径的 404 计入封禁统计。默认 false:仅命中后台
// 扫描特征路径(KindSensitive)才统计,避免共享出口(CDN 边缘 / CGNAT)下
// 正常用户被 404 探测流量连坐误伤。
BanTrackNotFound bool `ini:"ban_track_notfound"`
BanThresholdSensitive int `ini:"ban_threshold_sensitive"` // 窗口内敏感路径命中次数
BanThreshold404 int `ini:"ban_threshold_404"` // 窗口内 404 次数
BanWindowSeconds int `ini:"ban_window_seconds"` // 统计窗口(秒)
BanDurationMinutes int `ini:"ban_duration_minutes"` // 封禁时长(分钟)
// DefaultModerationStrictness 站点默认审核严格度(lax / standard / strict),
// 仅对未单独设置档位的账号生效(见 moderation.StrictnessFor)。留空或非法值回退
// strict(最严档),保证升级不会放宽拦截;默认拦截面可能较升级前略有扩大,
// 希望更少误伤则显式写 standard。
DefaultModerationStrictness string `ini:"default_moderation_strictness"`
// TextCensorSource 文本审核通道:qiniu / aliyun(内容安全 2.0 TextModeration,
// 个人实名账号即可开通)/ auto(默认,留空同义:七牛优先、阿里云复核)。
// 仅显式选定的通道会被调用,缺凭据时启动即报错(见 conf.Init)——
// 审核失败是 fail-closed(拒绝提交),会让发布与私信整站不可用且现象隐蔽。
TextCensorSource string `ini:"text_censor_source"`
// EnableHoneypot 开启「虚假后台登录界面」蜜罐:扫描器探测后台路径时
// 返回一个仿 WordPress 的假登录页,并记录其提交的用户名/密码等破解信息。
// 仅在「无任何路由匹配」的路径上生效,与真实业务路由零交集。
EnableHoneypot bool `ini:"enable_honeypot"`
// HoneypotPaths 追加自定义诱饵路径(逗号分隔),与内置清单并集。
HoneypotPaths string `ini:"honeypot_paths"`
// HoneypotRetainDays 蜜罐命中记录的保留天数,到期由 CLI 清理。
HoneypotRetainDays int `ini:"honeypot_retain_days"`
// HoneypotAutoClean 是否启用蜜罐记录的后台自动保留期清理。
// 默认 true:honeypot_hits 是只追加的审计表,写入完全由外部扫描流量驱动,
// 没有自动清理的话表体积只增不减(这是「数据库被扫描撑爆」的真实路径)。
// 关闭后仍可手动执行 TamaBox honeypot prune。
HoneypotAutoClean bool `ini:"honeypot_auto_clean"`
// HoneypotMaxRows 蜜罐命中记录的行数上限。留空或 <=0 表示不设上限。
// 保留期是时间维度的上界,但攻击者可以用海量源 IP 在保留期内把表撑爆,
// 这是第二道闸。实测单行约 281 字节(含索引,常规 UA),20 万行约 53 MB。
HoneypotMaxRows int `ini:"honeypot_max_rows"`
// HoneypotThreshold 窗口内蜜罐命中多少次即封禁。默认 1(一次即封):
// 蜜罐路径与真实业务路由零交集,正常访客不可能命中,一次即足以判定扫描器。
// 留空回退 1。
HoneypotThreshold int `ini:"honeypot_threshold"`
// HoneypotBanMinutes 蜜罐封禁时长(分钟)。留空则复用 ban_duration_minutes。
HoneypotBanMinutes int `ini:"honeypot_ban_minutes"`
SecurityAlertEmail string `ini:"security_alert_email"` // 触发封禁时告警收件人(留空不发送)
}
Server struct {
Port int `ini:"port"`
Listen string `ini:"listen"` // 绑定监听地址(含端口)。留空则默认 ":port"(监听所有网卡)。
Salt string `ini:"salt"`
XSRFKey string `ini:"xsrf_key"`
}
Database struct {
DSN string
Type string `ini:"type"`
User string `ini:"user"`
Password string `ini:"password"`
Host string `ini:"host"`
Port uint `ini:"port"`
Name string `ini:"name"`
Schema string `ini:"schema"`
DedupeUniqueFields bool `ini:"dedupe_unique_fields"`
}
Redis struct {
Addr string `ini:"addr"`
Password string `ini:"password"`
}
Recaptcha struct {
CaptchaType string `ini:"captcha_type"`
Domain string `ini:"domain"`
SiteKey string `ini:"site_key"`
ServerKey string `ini:"server_key"`
TurnstileStyle bool `ini:"turnstile_style"`
}
Pixel struct {
Host string `ini:"host"`
}
Upload struct {
DefaultAvatarURL string `ini:"default_avatar"`
DefaultBackground string `ini:"default_background"`
DefaultBackgroundImageURL string `ini:"default_qrbackgroundimage"`
AliyunEndpoint string `ini:"aliyun_endpoint"`
AliyunAccessID string `ini:"aliyun_access_id"`
AliyunAccessSecret string `ini:"aliyun_access_secret"`
AliyunBucket string `ini:"aliyun_bucket"`
AliyunBucketCDNHost string `ini:"aliyun_bucket_cdn_host"`
ImageEndpoint string `ini:"image_endpoint"`
ImageAccessID string `ini:"image_access_id"`
ImageAccessSecret string `ini:"image_access_secret"`
ImageBucket string `ini:"image_bucket"`
ImageBucketCDNHost string `ini:"image_bucket_cdn_host"`
}
Mail struct {
Account string `ini:"account"`
Password string `ini:"password"`
Port int `ini:"port"`
SMTP string `ini:"smtp"`
SkipTLSVerify bool `ini:"skip_tls_verify"` // 仅自签名 / 内网 SMTP 需要
}
Service struct {
Backends []struct {
Prefix string `ini:"prefix"`
ForwardURL string `ini:"forward_url"`
}
TLSInsecureSkipVerify bool `ini:"tls_insecure_skip_verify"` // 仅内网自签名后端需要
}
)