fix(setup): 首个管理员的创建只允许 Gateway 本机
# 之前的缺口 `POST /api/v1/setup/admin` 是公开路由,唯一的门是「系统还没有任何用户」。 Gateway 监听 `*:8180`,于是局域网里任何人可以绕开 nginx 直接调它。 本机已初始化时它只回 409,所以这条是纵深防御;但在**尚未初始化**的部署上, 它是「谁先提交谁成为管理员」——一个可被抢注的管理员入口。 # 为什么不是收紧监听地址 `.106` 上的反代(公网 `mail.jianfgit.xyz`)与本机鸿蒙客户端都直连 `192.168.2.60:8180`,把监听收到 127.0.0.1 会把这两条入口一起切断。 缺口在端点本身,不在监听面,所以只收紧端点。 # 改动 - 新增 `middleware.LocalOnly`:只有真实 TCP 对端为回环地址才放行。 - 新增 `middleware.CapturePeerAddress`,**注册在 `chimw.RealIP` 之前**。 RealIP 会信任 `X-Forwarded-For` 并改写 `RemoteAddr`,直接读它等于让外部 调用者用一个请求头冒充本机;所以先存原始连接地址,安全判断只认那份。 - `SetupAdmin` 的注释同步:本机限制在路由层,`NeedsSetup` 保留为第二道防线。 - 测试(`localonly_test.go`)按生产中间件顺序组装链,覆盖: IPv4/IPv6 回环放行、局网拒绝、**伪造 X-Forwarded-For 仍拒绝**、 非法地址拒绝,以及未装 CapturePeerAddress 时 fail closed。 # 验证 - 回环 `/setup/admin` → 409(进入处理器,系统已初始化) - LAN `/setup/admin` → 403 - LAN + `X-Forwarded-For: 127.0.0.1` → 403 - LAN `/setup/status` → 200(登录页判断是否显示向导仍正常) - 登录 + 收件箱 → 200;Go 全量测试与 vet 通过;已部署,四桥/SSE 正常
This commit is contained in:
@ -71,6 +71,9 @@ func main() {
|
||||
r.Use(chimw.Logger)
|
||||
r.Use(chimw.Recoverer)
|
||||
r.Use(chimw.RequestID)
|
||||
// 必须在 RealIP 之前保存真实 TCP 对端;否则外部请求可伪造
|
||||
// X-Forwarded-For: 127.0.0.1 绕过 /setup/admin 的本机限制。
|
||||
r.Use(middleware.CapturePeerAddress)
|
||||
r.Use(chimw.RealIP)
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: cfg.CORSOrigins,
|
||||
@ -89,9 +92,12 @@ func main() {
|
||||
})
|
||||
|
||||
r.Route("/api/v1", func(r chi.Router) {
|
||||
// ---- 首次初始化(公开;仅系统无用户时可用) ----
|
||||
// ---- 首次初始化 ----
|
||||
// status 公开,供登录页判断是否显示初始化向导;真正创建管理员只允许
|
||||
// Gateway 本机调用。标准部署会在监听前由 bootstrapAdmin 创建管理员,
|
||||
// 手工初始化则需在服务器上访问 127.0.0.1,LAN/反代请求一律拒绝。
|
||||
r.Get("/setup/status", handler.SetupStatus)
|
||||
r.Post("/setup/admin", handler.SetupAdmin)
|
||||
r.With(middleware.LocalOnly).Post("/setup/admin", handler.SetupAdmin)
|
||||
|
||||
// ---- 认证(公开) ----
|
||||
r.Post("/auth/login", handler.Login)
|
||||
|
||||
Reference in New Issue
Block a user