fix(ops): 访问日志脱敏、优雅关闭、Redis 可降级并启动即探活
三处都属于「故障时才发现」的运维缺口,一起改。
1) 访问日志泄露凭证
flamego.Classic() 内部 Use(Logger()),而 Logger 记的是 RequestURI —— 含 query。
实测日志出现 path=/_/tblocal/4?t=SECRETTOKEN123:?t= 是私密提问钥匙,
?code= 是一次性改密凭证,?q= 是搜索词,全都会被明文落盘,凡是能看日志的人
都能直接读私密提问或重置密码。internal/tracing 早就为同一原因只取 URL.Path
并留了注释,漏的是框架自带那层。
→ 改 flamego.New() + accessLogMiddleware,只记 Method/Path/Status/Duration/IP;
静态资源成功请求不记(噪音),4xx/5xx 照记(排查裂图靠它)。
2) 没有优雅关闭
直接 ListenAndServe,Go 对 SIGTERM 是立即退出,每次部署都会把正在写库的 POST
就地处死,留下「提问已 INSERT、图片没传完」这类半成品。
→ signal.NotifyContext(SIGTERM/SIGINT) + srv.Shutdown(15s),超时才 Close;
退出前关连接池。实测 8 个并发分享图请求中发 SIGTERM,8/8 全部成功。
3) Redis 单点
session 建在 Redis 上,load 失败 flamego/session 会 panic → 每请求 500(含匿名访客);
而 cache.Cacher 没判空 Addr,go-redis 会回退 localhost:6379,于是「不配 Redis」
这种被允许的部署里找回密码整体不可用、私信邮件节流失效,且启动零提示。
→ cache 跟随开关(未配则 MemoryIniter);启动 PingRedis fail-fast,显式 2s 超时。
实测停掉 Redis 后启动立即失败并明确指出是 Redis。
验证:单测 2 项(query 永不入日志 / 静态资源过滤口径);重启日志出现
「收到退出信号」→「HTTP 服务已关闭」;启动打「Redis 连接正常」。
This commit is contained in:
+36
-11
@@ -184,7 +184,17 @@ func newBanner() *autoban.Banner {
|
||||
}
|
||||
|
||||
func New() *flamego.Flame {
|
||||
f := flamego.Classic()
|
||||
// 不用 flamego.Classic():它内部注册的 Logger() 记录 RequestURI(含 query),
|
||||
// 会把 ?t=<私密题钥匙> 与 ?code=<改密凭证> 明文写进日志(见 accesslog.go)。
|
||||
// Classic 提供的三件套在这里逐一显式装配:
|
||||
// accessLogMiddleware() 替代 Logger(),只记 URL.Path
|
||||
// flamego.Recovery() 行为不变
|
||||
// flamego.Static() 已在下方按 /static 前缀显式注册(原来就重复注册过)
|
||||
f := flamego.New()
|
||||
// 访问日志放在 Recovery 外层:panic 被兜成 500 后仍能记到状态码。
|
||||
f.Use(accessLogMiddleware())
|
||||
f.Use(flamego.Recovery())
|
||||
|
||||
if conf.App.Production {
|
||||
flamego.SetEnv(flamego.EnvTypeProd)
|
||||
}
|
||||
@@ -281,6 +291,30 @@ func New() *flamego.Flame {
|
||||
// 之前执行,否则超大 multipart 会在 Bind 的路由级限制生效前落盘临时文件耗尽磁盘。
|
||||
f.Use(requestBodyLimitMiddleware())
|
||||
|
||||
// 缓存层:配了 Redis 用 Redis,没配就用进程内内存缓存。
|
||||
//
|
||||
// 原来无条件走 cacheRedis,而 go-redis 对空 Addr 会回退到 localhost:6379
|
||||
// (go-redis/v9/options.go)。于是「不配 Redis」这种被配置允许的部署形态下,
|
||||
// 每个依赖缓存的功能都在连一个不存在(或不属于本站)的本地实例:
|
||||
// - route/auth/recover.go 读不回 code → 找回密码整体不可用,只看到「内部错误」
|
||||
// - chat_handler 的邮件每小时节流失效 → 每条私信都发一封邮件
|
||||
// 而这一切在启动期没有任何提示,排查时也看不出跟 Redis 有关。
|
||||
var cacheHandler flamego.Handler
|
||||
if conf.Redis.Addr != "" {
|
||||
cacheHandler = cache.Cacher(cache.Options{
|
||||
Initer: cacheRedis.Initer(),
|
||||
Config: cacheRedis.Config{
|
||||
Options: &cacheRedis.Options{
|
||||
Addr: conf.Redis.Addr,
|
||||
Password: conf.Redis.Password,
|
||||
DB: 0,
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
cacheHandler = cache.Cacher(cache.Options{Initer: cache.MemoryIniter()})
|
||||
}
|
||||
|
||||
reqUserSignOut := context.Toggle(&context.ToggleOptions{UserSignOutRequired: true})
|
||||
reqUserSignIn := context.Toggle(&context.ToggleOptions{UserSignInRequired: true})
|
||||
|
||||
@@ -524,16 +558,7 @@ func New() *flamego.Flame {
|
||||
f.Get("/site-stats", rateLimitMiddleware(60, 1*time.Minute), route.SiteStatsAPI)
|
||||
}, context.APIEndpoint)
|
||||
},
|
||||
cache.Cacher(cache.Options{
|
||||
Initer: cacheRedis.Initer(),
|
||||
Config: cacheRedis.Config{
|
||||
Options: &cacheRedis.Options{
|
||||
Addr: conf.Redis.Addr,
|
||||
Password: conf.Redis.Password,
|
||||
DB: 0,
|
||||
},
|
||||
},
|
||||
}),
|
||||
cacheHandler,
|
||||
recaptcha.V3(
|
||||
recaptcha.Options{
|
||||
Secret: conf.Recaptcha.ServerKey,
|
||||
|
||||
Reference in New Issue
Block a user