问题: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
117 lines
3.6 KiB
Go
117 lines
3.6 KiB
Go
// 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 cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/uptrace/opentelemetry-go-extra/otellogrus"
|
|
"github.com/uptrace/uptrace-go/uptrace"
|
|
"github.com/urfave/cli/v2"
|
|
|
|
"github.com/tamakyi/TamaBox/internal/conf"
|
|
"github.com/tamakyi/TamaBox/internal/db"
|
|
"github.com/tamakyi/TamaBox/internal/route"
|
|
"github.com/tamakyi/TamaBox/internal/shared"
|
|
"github.com/tamakyi/TamaBox/internal/tracing"
|
|
)
|
|
|
|
var Web = &cli.Command{
|
|
Name: "web",
|
|
Usage: "Start web server",
|
|
Action: runWeb,
|
|
}
|
|
|
|
func runWeb(ctx *cli.Context) error {
|
|
if err := conf.Init(); err != nil {
|
|
return errors.Wrap(err, "load configuration")
|
|
}
|
|
|
|
// 解析可信代理网段(用于客户端 IP 解析,避免维护 Cloudflare 庞大 CIDR 清单)。
|
|
if err := shared.InitTrustedProxies(); err != nil {
|
|
return errors.Wrap(err, "init trusted proxies")
|
|
}
|
|
|
|
if conf.App.UptraceDSN != "" {
|
|
uptrace.ConfigureOpentelemetry(
|
|
uptrace.WithDSN(conf.App.UptraceDSN),
|
|
uptrace.WithServiceName("tamabox"),
|
|
uptrace.WithServiceVersion(conf.BuildCommit),
|
|
)
|
|
logrus.WithContext(ctx.Context).Debug("Tracing enabled.")
|
|
}
|
|
|
|
logrus.AddHook(otellogrus.NewHook(otellogrus.WithLevels(
|
|
logrus.PanicLevel,
|
|
logrus.FatalLevel,
|
|
logrus.ErrorLevel,
|
|
logrus.WarnLevel,
|
|
)))
|
|
|
|
dbType := conf.Database.Type
|
|
|
|
var dsn string
|
|
switch dbType {
|
|
case "mysql", "":
|
|
dsn = conf.MySQLDsn()
|
|
case "postgres":
|
|
dsn = conf.PostgresDsn()
|
|
default:
|
|
return errors.Errorf("unknown database type: %q", dbType)
|
|
}
|
|
conf.Database.DSN = dsn
|
|
|
|
_, err := db.Init(dbType, dsn)
|
|
if err != nil {
|
|
return errors.Wrap(err, "connect to database")
|
|
}
|
|
|
|
// 蜜罐命中记录保留期清理:honeypot_hits 是只追加的审计表,写入完全由
|
|
// 外部扫描流量驱动(不可预期、不可节流)。没有自动清理的话,表体积只增不减。
|
|
// 保留期复用 honeypot_retain_days(与 CLI prune 同一个口径),开关为 honeypot_auto_clean。
|
|
stopRetention := make(chan struct{})
|
|
if conf.Security.HoneypotAutoClean {
|
|
db.NewHoneypotRetention(db.Honeypots, conf.Security.HoneypotRetainDays, conf.Security.HoneypotMaxRows, 0).Start(stopRetention)
|
|
defer close(stopRetention)
|
|
}
|
|
|
|
logrus.WithContext(ctx.Context).WithField("external_url", conf.App.ExternalURL).Info("Starting web server")
|
|
|
|
// 站点默认语言初始化(default_lang 配置,未配置时回退 zh-CN)
|
|
shared.App.SetLang(conf.App.DefaultLang)
|
|
|
|
r := route.New()
|
|
r.Use(tracing.Middleware("TamaBox"))
|
|
|
|
// 最外层挂载 gzip 压缩,并在 http.Server 上配置超时:
|
|
// - ReadHeaderTimeout 防止慢速客户端占用连接
|
|
// - ReadTimeout / WriteTimeout 防止读写挂死
|
|
// - IdleTimeout 复用 keep-alive 连接
|
|
// listen 留空时回退到 ":port"(监听所有网卡);Cloudflare Tunnel 部署建议设为
|
|
// "127.0.0.1:<port>",使源站端口仅对本地 cloudflared 可见,即使防火墙误配也无法被外部直连。
|
|
addr := conf.Server.Listen
|
|
if addr == "" {
|
|
addr = fmt.Sprintf(":%d", conf.Server.Port)
|
|
}
|
|
server := &http.Server{
|
|
Addr: addr,
|
|
Handler: route.GzipHandler(r),
|
|
ReadHeaderTimeout: 10 * time.Second,
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 60 * time.Second,
|
|
IdleTimeout: 120 * time.Second,
|
|
}
|
|
|
|
logrus.WithContext(ctx.Context).WithField("port", conf.Server.Port).Info("Listening on")
|
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
return errors.Wrap(err, "start http server")
|
|
}
|
|
return nil
|
|
}
|