security_policy_helper.go 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. package handler
  2. import (
  3. "context"
  4. "net/http"
  5. "strings"
  6. "time"
  7. "github.com/Wei-Shaw/sub2api/internal/pkg/ip"
  8. "github.com/Wei-Shaw/sub2api/internal/securityaudit"
  9. "github.com/Wei-Shaw/sub2api/internal/service"
  10. "github.com/gin-gonic/gin"
  11. )
  12. // checkGroupSecurityPolicy 是分组安全策略在网关审计链中的唯一入口,
  13. // 由 runSecurityAudit 在全局审计之前调用。开关关闭(默认)时为纯 no-op。
  14. // 返回 nil 表示放行;非 nil 且 AllowNextStage=false 时调用方必须拒绝请求。
  15. func checkGroupSecurityPolicy(c *gin.Context, securityPolicy *service.SecurityPolicyService, apiKey *service.APIKey, protocol, model string, body []byte) *securityaudit.Decision {
  16. if securityPolicy == nil || apiKey == nil {
  17. return nil
  18. }
  19. group := apiKey.Group
  20. if group == nil || !group.SecurityPolicyEnabled {
  21. return nil
  22. }
  23. mode := service.NormalizeSecurityPolicyMode(group.SecurityPolicyMode)
  24. verdict := securityPolicy.EvaluateRequest(c.Request.Context(), c, service.SecurityPolicyRequest{
  25. APIKey: apiKey,
  26. Protocol: protocol,
  27. Model: model,
  28. Endpoint: GetInboundEndpoint(c),
  29. Body: body,
  30. RequestID: c.Writer.Header().Get("X-Request-Id"),
  31. ClientIP: strings.TrimSpace(ip.GetClientIP(c)),
  32. UserAgent: c.GetHeader("User-Agent"),
  33. })
  34. if verdict == nil || verdict.Allowed {
  35. return nil
  36. }
  37. // 异步落库/封禁/邮件:本请求已决定拒绝,不阻塞响应。
  38. record := service.SecurityPolicyHitRecord{
  39. RequestID: c.Writer.Header().Get("X-Request-Id"),
  40. APIKey: apiKey,
  41. Protocol: protocol,
  42. Model: model,
  43. Endpoint: GetInboundEndpoint(c),
  44. Verdict: verdict,
  45. SessionMode: mode,
  46. }
  47. go func() {
  48. ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  49. defer cancel()
  50. securityPolicy.RecordHit(ctx, record)
  51. }()
  52. status := http.StatusForbidden
  53. if status < 400 || status > 599 {
  54. status = http.StatusForbidden
  55. }
  56. return &securityaudit.Decision{
  57. Kind: securityaudit.DecisionBlock,
  58. HTTPStatus: status,
  59. ErrorCode: verdict.ErrorCode,
  60. ClientMessage: verdict.ClientMessage,
  61. AllowNextStage: false,
  62. }
  63. }