fix(gateway): 三处生产安全硬化(默认密钥/admin裸奔/CORS)

1) JWT 默认密钥:生产模式(APP_ENV=production|prod 或 GIN_MODE=release)下若未设
   JWT_SECRET 直接 log.Fatal,杜绝用开发默认值签发可伪造令牌;开发期警告并放行。
2) /admin 运维控制面(含模型 API 密钥管理)改挂 RequireAdmin:必须登录 +
   (设了 ADMIN_USER_IDS 则)uid 须在白名单;生产期未配置管理员直接 403。
3) CORS Allow-Origin 由 CORS_ALLOW_ORIGIN 配置(缺省 * 仅开发),非 * 时加 Vary。

build + auth 单测通过。仍属"小范围灰度"级,TLS/可观测/集成测试/HA 见 PROGRESS。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-18 12:55:04 +08:00
parent 9c19bb44f1
commit e05e6f5903
4 changed files with 82 additions and 6 deletions
@@ -2,6 +2,7 @@ package middleware
import (
"net/http"
"os"
"strings"
"github.com/gin-gonic/gin"
@@ -40,3 +41,45 @@ func RequireAuth() gin.HandlerFunc {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "需要登录"})
}
}
// RequireAdmin 保护运维控制面:必须登录,且(设了 ADMIN_USER_IDS 时)uid 须在白名单内。
// ADMIN_USER_IDS 为空:开发期放行任意登录用户;生产期(APP_ENV=prod/GIN_MODE=release)直接拒绝
// ——逼运维显式配置管理员,杜绝"任意账号改模型/密钥配置"。
func RequireAdmin() gin.HandlerFunc {
allow := splitEnv("ADMIN_USER_IDS")
prod := strings.EqualFold(os.Getenv("APP_ENV"), "production") || strings.EqualFold(os.Getenv("APP_ENV"), "prod") ||
strings.EqualFold(os.Getenv("GIN_MODE"), "release")
return func(c *gin.Context) {
uid, _ := c.Get(CtxUserID)
id, _ := uid.(string)
if id == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "需要登录"})
return
}
if len(allow) == 0 {
if prod {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "未配置管理员(ADMIN_USER_IDS"})
return
}
c.Next() // 开发期放行
return
}
for _, a := range allow {
if a == id {
c.Next()
return
}
}
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "需要管理员权限"})
}
}
func splitEnv(key string) []string {
var out []string
for _, p := range strings.Split(os.Getenv(key), ",") {
if p = strings.TrimSpace(p); p != "" {
out = append(out, p)
}
}
return out
}